Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ tmp-php.ini
# ------------------------------------------------------------------------------
*.gcda
*.gcno
/pgo-data/
/gcovr.xml
/gcovr_html/
/lcov_html/
Expand Down Expand Up @@ -311,6 +312,7 @@ tmp-php.ini
# Special cases to invert previous ignore patterns
# ------------------------------------------------------------------------------
!/ext/bcmath/libbcmath/src/config.h
!/build/tests/*.sh
!/ext/fileinfo/libmagic/config.h
!/ext/fileinfo/libmagic.patch
!/ext/fileinfo/magicdata.patch
Expand Down
36 changes: 32 additions & 4 deletions build/Makefile.global
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,30 @@ distclean: clean
rm -f ext/phar/phar/phar.inc; \
fi

PGO_DATA_DIR = $(top_builddir)/pgo-data
LLVM_PROFDATA = llvm-profdata
PROF_GEN_FLAGS = $(PHP_PGO_GENERATE_FLAGS)
PROF_USE_FLAGS = $(PHP_PGO_USE_FLAGS)

prof-gen:
CCACHE_DISABLE=1 $(MAKE) PROF_FLAGS=-fprofile-generate all
find . -name \*.gcda | xargs rm -f
@test "$(PHP_PGO_COMPILER)" != unsupported || { echo "PGO requires GCC or Clang."; exit 1; }
$(MAKE) prof-clean
$(MAKE) prof-reset
CCACHE_DISABLE=1 $(MAKE) PROF_FLAGS='$(PROF_GEN_FLAGS)' all
$(MAKE) prof-reset

prof-reset:
find . -name \*.gcda -type f -exec rm -f {} +
mkdir -p "$(PGO_DATA_DIR)"
find "$(PGO_DATA_DIR)" -name \*.profraw -type f -exec rm -f {} +
rm -f "$(PGO_DATA_DIR)/php.profdata" "$(PGO_DATA_DIR)/profiles.list"

prof-merge:
@test "$(PHP_PGO_COMPILER)" = clang || { echo "prof-merge requires a Clang build."; exit 1; }
mkdir -p "$(PGO_DATA_DIR)"
find "$(PGO_DATA_DIR)" -name \*.profraw -type f -print > "$(PGO_DATA_DIR)/profiles.list"
@test -s "$(PGO_DATA_DIR)/profiles.list" || { echo "No Clang profiles found; train the instrumented build first."; exit 1; }
"$(LLVM_PROFDATA)" merge --input-files="$(PGO_DATA_DIR)/profiles.list" --output="$(PGO_DATA_DIR)/php.profdata"

prof-clean:
find . -name \*.lo -o -name \*.o | xargs rm -f
Expand All @@ -150,7 +171,14 @@ prof-clean:
rm -f libphp.la $(SAPI_CLI_PATH) $(SAPI_CGI_PATH) $(SAPI_LITESPEED_PATH) $(SAPI_FPM_PATH) $(SAPI_PHPDBG_PATH) $(SAPI_PHPDBG_SHARED_PATH) $(OVERALL_TARGET) modules/* libs/*

prof-use:
CCACHE_DISABLE=1 $(MAKE) PROF_FLAGS=-fprofile-use all
@test "$(PHP_PGO_COMPILER)" != unsupported || { echo "PGO requires GCC or Clang."; exit 1; }
@if test "$(PHP_PGO_COMPILER)" = clang; then \
$(MAKE) prof-merge; \
else \
test -n "`find . -name \*.gcda -type f -print -quit`" || { echo "No GCC profiles found; train the instrumented build first."; exit 1; }; \
fi
$(MAKE) prof-clean
CCACHE_DISABLE=1 $(MAKE) PROF_FLAGS='$(PROF_USE_FLAGS)' all

%_arginfo.h: %.stub.php
@if test -e "$(top_srcdir)/build/gen_stub.php"; then \
Expand All @@ -163,4 +191,4 @@ prof-use:
fi; \
fi;

.PHONY: all clean install distclean test prof-gen prof-clean prof-use
.PHONY: all clean install distclean test prof-gen prof-reset prof-merge prof-clean prof-use
40 changes: 40 additions & 0 deletions build/tests/pgo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
+----------------------------------------------------------------------+
*/

#include "sapi/embed/php_embed.h"
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv)
{
php_embed_module.php_ini_ignore = 1;
if (php_embed_init(argc, argv) == FAILURE) {
return 1;
}

int status = 0;
zend_first_try {
if (zend_eval_string(
"$rows = [];"
"for ($i = 0; $i < 1000; $i++) { $rows[] = ['id' => $i, 'value' => $i * 3]; }"
"$sum = array_sum(array_column($rows, 'value'));"
"echo json_encode(['sum' => $sum, 'count' => count($rows)]), PHP_EOL;",
NULL, "PGO smoke test") == FAILURE) {
status = 1;
}
} zend_catch {
status = 1;
} zend_end_try();

php_embed_shutdown();
fflush(stdout);
_Exit(status);
}
40 changes: 40 additions & 0 deletions build/tests/pgo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/sh
set -eu

srcdir=$(CDPATH= cd -- "${1:?Usage: pgo.sh /path/to/php-src (from a configured embed build)}" && pwd)
builddir=$(pwd)
test -f Makefile || { echo "Run this test from a configured build directory." >&2; exit 1; }
test -f "$srcdir/build/tests/pgo.c"
test -f libs/libphp.so || { echo "Build PHP with --enable-embed first." >&2; exit 1; }

make_pgo() {
"${MAKE:-make}" LLVM_PROFDATA="${LLVM_PROFDATA:-llvm-profdata}" "$@"
}

cc=$(sed -n 's/^CC = //p' Makefile)
test -n "$cc"
$cc -I"$builddir" -I"$builddir/main" -I"$builddir/Zend" \
-I"$srcdir" -I"$srcdir/main" -I"$srcdir/Zend" -I"$srcdir/TSRM" \
"$srcdir/build/tests/pgo.c" -L"$builddir/libs" -Wl,-rpath,"$builddir/libs" \
-lphp -o pgo-host

expected='{"sum":1498500,"count":1000}'
test "$(./pgo-host)" = "$expected"
make_pgo prof-reset
before=$(cksum sapi/cli/php)
if make_pgo prof-use > pgo-missing-profile.log 2>&1; then
echo "prof-use accepted an untrained build." >&2
exit 1
fi
test "$(cksum sapi/cli/php)" = "$before"

make_pgo prof-gen
test -z "$(find . -name '*.gcda' -o -name '*.profraw')"
test "$(./pgo-host)" = "$expected"
test -n "$(find . -name '*.gcda' -o -name '*.profraw')"

make_pgo prof-use
make_pgo prof-reset
test "$(./pgo-host)" = "$expected"
test -z "$(find . -name '*.gcda' -o -name '*.profraw')"
echo "PGO build transitions and embedded profile flushing passed."
22 changes: 22 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,28 @@ AC_PROG_CPP
AC_USE_SYSTEM_EXTENSIONS
AC_PROG_LN_S

AC_CACHE_CHECK([whether the C compiler is Clang], [php_cv_pgo_clang],
[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#ifndef __clang__
# error Not Clang
#endif
]], [])], [php_cv_pgo_clang=yes], [php_cv_pgo_clang=no])])

AS_VAR_IF([php_cv_pgo_clang], [yes], [
PHP_PGO_COMPILER=clang
PHP_PGO_GENERATE_FLAGS='-fprofile-generate=$(PGO_DATA_DIR) -fprofile-update=atomic -DPHP_PGO_GENERATE'
PHP_PGO_USE_FLAGS='-fprofile-use=$(PGO_DATA_DIR)/php.profdata -DPHP_PGO_USE'
], [
AS_VAR_IF([GCC], [yes], [
PHP_PGO_COMPILER=gcc
PHP_PGO_GENERATE_FLAGS='-fprofile-generate -fprofile-update=atomic -DPHP_PGO_GENERATE'
PHP_PGO_USE_FLAGS='-fprofile-use -fprofile-correction -DPHP_PGO_USE'
], [PHP_PGO_COMPILER=unsupported])
])
PHP_SUBST([PHP_PGO_COMPILER])
PHP_SUBST([PHP_PGO_GENERATE_FLAGS])
PHP_SUBST([PHP_PGO_USE_FLAGS])

AS_VAR_IF([cross_compiling], [yes],
[AC_CHECK_PROGS([BUILD_CC], [gcc clang c99 c89 cc cl], [none])
AC_MSG_CHECKING([for native build C compiler])
Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
miscellaneous/stubs
miscellaneous/writing-tests
miscellaneous/running-tests
miscellaneous/pgo

Welcome to the php-src documentation!

Expand Down
116 changes: 116 additions & 0 deletions docs/source/miscellaneous/pgo.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#############################
Profile-guided optimization
#############################

Profile-guided optimization (PGO) uses measurements from an instrumented PHP build to optimize a
second build. The ``prof-gen`` and ``prof-use`` make targets support GCC and Clang. These targets
are optional; a normal ``make`` does not enable PGO.

Use the same source tree, compiler, architecture, configure options and optimization flags for
both phases. Keep the build directory in place while collecting profiles and rebuilding. GCC
records counters beside the object files; Clang records raw profiles in ``pgo-data/`` under the
build directory.

**************************
Build, train and rebuild
**************************

Configure PHP with the features required by the training applications. For example:

.. code:: shell

./buildconf --force
./configure --enable-zts --enable-embed
make -j8 prof-gen

``prof-gen`` removes old objects and profiles, builds an instrumented interpreter with atomic
counter updates, then removes profiles produced by build-time tools. Run the instrumented PHP
binary or embedded library against the training applications before rebuilding:

.. code:: shell

sapi/cli/php -d opcache.jit=disable -d opcache.jit_buffer_size=0 training.php
make -j8 prof-use

``prof-use`` checks for training profiles before removing the instrumented objects. It then
builds an optimized interpreter without instrumentation. Training applications and configurations
must also keep JIT disabled when comparing interpreter build optimizations.

For Clang, configure with the chosen compiler and select the corresponding ``llvm-profdata``
executable if it is not named ``llvm-profdata``:

.. code:: shell

CC=clang ./configure --enable-zts --enable-embed
make -j8 prof-gen
sapi/cli/php -d opcache.jit=disable -d opcache.jit_buffer_size=0 training.php
make -j8 prof-use LLVM_PROFDATA=llvm-profdata

``prof-use`` automatically merges the raw Clang profiles into ``pgo-data/php.profdata``. To merge
and inspect them before rebuilding:

.. code:: shell

make prof-merge LLVM_PROFDATA=llvm-profdata
llvm-profdata show --all-functions --counts pgo-data/php.profdata

``make prof-clean`` removes compiled objects and binaries while preserving training data.
``make prof-reset`` removes GCC counters, raw Clang profiles and the merged Clang profile while
preserving compiled objects. ``make prof-gen`` starts a new training cycle and discards the
previous cycle's profiles.

The make variables ``PROF_GEN_FLAGS`` and ``PROF_USE_FLAGS`` allow custom compiler profiling
flags. Retain ``-DPHP_PGO_GENERATE`` in generation flags to flush profiles at PHP module shutdown,
and ``-DPHP_PGO_USE`` in use flags to keep the shutdown path excluded from profiling in both phases.
``PGO_DATA_DIR`` controls the Clang profile directory; use the same value for generation,
training, merging and profile use. If ``LLVM_PROFILE_FILE`` is set during Clang training, keep its
output in that directory so that ``prof-merge`` can discover the profiles.

*************************
Representative training
*************************

Training should cover the work performed by the applications that will use the optimized build.
Include application startup, persistent workers, fresh application instances, CLI jobs,
validation, serialization, template rendering, container resolution and instrumentation as
appropriate. Exercise successful and failing inputs, common and infrequent branches, and the
native extensions required by the applications. Train each SAPI that will be deployed.

Choose the relative frequency of these operations to reflect the expected application mix.
Separate training inputs from evaluation inputs, and include workloads absent from training to
check whether the gains generalize. A small smoke test checks the build machinery; it is not a
representative performance training workload.

Inspect profile coverage and compiler diagnostics. A profile generated by a different source
revision or compiler can be incompatible even when its files exist. Regenerate profiles when
changing PHP, the compiler, architecture, extensions, configure options or optimization flags.

****************
Embedded hosts
****************

Instrumented builds flush counters during ``php_module_shutdown()``. An embedded host must call
PHP's shutdown routines after training and before terminating. This also writes profiles when
the host exits without invoking the C runtime's exit handlers. Atomic counters support training
with multiple PHP threads; the host must stop those threads before shutting down PHP.

Only instrumented builds contain this flushing hook. The optimized build does not add a
userland function, and the hook is absent from normal builds.

***************************
Build workflow smoke test
***************************

The Linux shared-embed smoke test verifies that an untrained ``prof-use`` fails without replacing
the current interpreter, that the generation and use phases rebuild objects, and that an
embedded process writes profiles before calling ``_Exit()``. It also checks identical output and
the absence of new profiles after an optimized run.

From a fresh build configured with ``--enable-embed`` and with ``make`` completed:

.. code:: shell

MAKEFLAGS=-j8 sh /path/to/php-src/build/tests/pgo.sh /path/to/php-src

For Clang, set ``LLVM_PROFDATA`` to the matching executable if necessary. The test replaces the
current build with an instrumented build and then a profile-use build, and clears profiling data.
19 changes: 19 additions & 0 deletions main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@
#include "main_arginfo.h"
/* }}} */

#ifdef PHP_PGO_GENERATE
# ifdef __clang__
extern int __llvm_profile_dump(void);
# else
extern void __gcov_dump(void);
# endif
#endif

PHPAPI int (*php_register_internal_extensions_func)(void) = php_register_internal_extensions;

#ifndef ZTS
Expand Down Expand Up @@ -2488,6 +2496,9 @@ int php_module_shutdown_wrapper(sapi_module_struct *sapi_globals)
/* }}} */

/* {{{ php_module_shutdown */
#if defined(PHP_PGO_GENERATE) || defined(PHP_PGO_USE)
__attribute__((no_profile_instrument_function))
#endif
void php_module_shutdown(void)
{
int module_number=0;
Expand Down Expand Up @@ -2563,6 +2574,14 @@ void php_module_shutdown(void)
#endif

zend_observer_shutdown();

#ifdef PHP_PGO_GENERATE
# ifdef __clang__
__llvm_profile_dump();
# else
__gcov_dump();
# endif
#endif
}
/* }}} */

Expand Down