Getting the most out of the PHP 7 engine — the example of Symfony

Getting the most out of the PHP 7 engine — the example of Symfony

PHP 7.0 is already history. But do you know how to take full advantage of it? If the engine is faster on all operations in general, some of them are particularly optimized. With PHP 5, you may have taken some habits that are no longer topical to write faster code?

I propose to review with you the various optimization techniques implemented in Symfony, which make the v4 the fastest ever published. This will be an opportunity to twist the puzzle around a few preconceived ideas, and give you a few more for the day when you'll try to squeeze the last few milliseconds out of this intensive loop. Benchmark in support of course.

6baa34bc1e5c347b1003f6abe8691de1?s=128

Nicolas Grekas

May 13, 2020
Tweet

Transcript

  1. 3.
  2. 4.
  3. 5.

    • Born in 1995 – v7 end of 2016 •

    870,000 C lines of code • Distributed leadership The PHP engine
  4. 6.

    • Since 2011 – v5 end of 2019 • PHP

    ^7.2 • 141 packages • 4.4B downloads Symfony
  5. 8.

    Synchronous and parallel Master Child 1 Child 2 Child n

    index.php index.php index.php index.php index.php index.php PHP’s memory manager insulates each scripts
  6. 9.
  7. 10.
  8. 15.
  9. 16.
  10. 17.

    First steps are immutable index.php Lexing + parsing AST opcodes

    Compiling Executing result Shared memory* *append-only
  11. 18.

    Compile time optimizations AST opcodes Compiling Shared memory • Compile-time

    evaluation 'foo'."bar" • Dead-code elimination if (false) {…}
  12. 19.

    Compile time optimizations AST opcodes Compiling Shared memory • Compile-time

    evaluation 'foo'."bar" • Dead-code elimination if (false) {…} • Interned strings "foobar" len=6 hash=Ox1234 • Immutable arrays [123, ["ab" => 34]] len=2 len=1
  13. 20.

    Compile time optimizations AST opcodes Compiling Shared memory • Compile-time

    evaluation 'foo'."bar" • Dead-code elimination if (false) {…} • Interned strings "foobar" len=6 hash=Ox1234 • Immutable arrays [123, ["ab" => 34]] len=2 len=1
  14. 21.

    • Works for constants 'cli' === PHP_SAPI • Works for

    some native functions 1 === count([123]) • Needs fully-qualified identifiers namespace App; \count([123]); Compile time evaluation
  15. 22.

    namespace App; is_array($a); \is_array($a); Compile time inlining https://phpinternals.net/articles/optimising_internal_functions_via_new_opcode_instructions#the-in_array-function https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/3048 strlen(),

    is_{type}(), {type}val() casts, defined(), chr(), ord(), call_user_function(), call_user_function_array(), get_class(), get_called_class(), gettype(), count(), in_array(), array_slice(), func_num_args(), func_get_args(), function_exists(), is_callable(), extension_loaded(), constant(), dirname()
  16. 25.

    Let’s split the container in one file per service PHP

    7 is slow… at compiling https://github.com/symfony/symfony/pull/23678
  17. 26.

    • Loading opcodes is really fast • Still takes time

    and memory* • « require » is also really fast opcodes move from shared memory to process’* https://github.com/symfony/symfony/pull/23678 * PHP 7.4 did fix that
  18. 29.

    • Shared memory is read-only $a = […]; $a[0] =

    123; • Duplication happens on « write » $a = "abc"; $b = $a; $b .= "ghi"; by the way, scalars are free, and declared properties make objects very compact https://wiki.php.net/rfc/token_as_object https://blog.blackfire.io/php-7-performance-improvements-ints-floats-free.html ❤ Copy-on-write
  19. 33.

    • gc_collect_cycles() autotriggered past 10k objects • gc_mem_caches() PHP7 memory

    manager is lazy • Much improved in PHP 7.3 the 10k threshold is auto-raised PHP garbage collector
  20. 34.

    • Coalesce operator A ?? B vs isset(A) ? A

    : B https://github.com/symfony/symfony/pull/26161 • Packed arrays (incrementing keys only) https://blog.blackfire.io/php-7-performance-improvements-packed-arrays.html • Ropes for encapsed strings "$a and $b" vs $a." and ".$b https://blog.blackfire.io/php-7-performance-improvements-encapsed-strings-optimization.html • Class constants can be slow (fixed in PHP 7.3) https://github.com/symfony/symfony/pull/25474 - https://github.com/twigphp/Twig/pull/2636 More goodies
  21. 35.

    • Copy-on-write is not triggered by (string)/(array) cast • Since

    PHP 7.2, « switch » statements use a hashmap • Closure::fromCallable([$this, 'method']) • is_file() is always cached, file_exists() not • Public all the things – @internal only! • Remove type hints – @internal only! • PHP 5 reference mismatches are gone https://blog.blackfire.io/php-7-performance-improvements-references-mismatch.html Even more goodies
  22. 36.

    It removes: • Loading classes, including resolving inheritance • Freshness

    checks Is sensitive to: • Over-preloading • Under-preloading It requires: • Writing a preloading script • One app per FPM server PHP 7.4.5 – Preloading!
  23. 37.

    • include() + autoloading • opcache_compile_file() • Symfony generates preload.php

    out of the box from services+cache warmers • Hello World with Twig: +75% • A typical app spends 15-20% autoloading code Kudos Dmitry and Nikita
  24. 39.

    Conclusion • PHP 7 • Source ideas in PHP’s C

    • Use only in tight loops • Never stop measuring