$30 off During Our Annual Pro Sale. View Details »

Preloading and Symfony, a love story

Preloading and Symfony, a love story

Preloading is a hot topic these days, yet the first versions of PHP 7.4 crashed when it was enabled. Since September with the first experiments (and crash reports), Nicolas is tracking the beast: PHP 7.4.5 finally makes it usable in all contexts and a 75% boost has been measured on a "Hello World" app. Can you expect the same benefits on your apps? Preloading comes with its own challenges. Let's review them and figure how you can get the most out of it for the benefit of your server's efficiency and response time.

First presented at https://online.live.symfony.com/

Nicolas Grekas

April 17, 2020
Tweet

More Decks by Nicolas Grekas

Other Decks in Technology

Transcript

  1. & Preloading
    A love story

    View Slide

  2. Agenda
    •A short history of OPcache
    •Preloading, the theory
    •Preloading, the practice
    •Conclusion

    View Slide

  3. A short history of OPcache

    View Slide

  4. Lambda-style before it was hype
    • PHP has a run-and-forget execution model
    • Every request has to reload everything all-the-time
    • Memory leaks and shared state are hard problems
    • This is great for developer efficiency
    • This is great for robustness of apps
    • This makes hosting great
    • This makes scalability easy

    View Slide

  5. Shared memory (SHM) to the rescue
    • The code is immutable
    • The compilation steps can be cached
    • APC, eAccelerator, Turck MMCache, Zend OpCache FTW
    • But which compilation steps?

    View Slide

  6. Opcode caches
    • The source code is compiled into a list of instructions,
    the opcodes:
    • ADD, CONCAT, ASSIGN, …, DECLARE_CLASS, DECLARE_FUNCTION,
    DECLARE_INHERITED_CLASS, ADD_INTERFACE, etc.
    • Shared memory holds a hashmap map of (file => opcodes) pairs

    View Slide

  7. Opcode caches++
    • PHP 5.4: interned strings
    • PHP 5.5: OPcache becomes open-source and builtin
    • PHP 5.6: array declarations stay in shared memory
    • PHP 7.0:
    • all static arrays stay in shared memory
    • post-compilation optimizations
    • PHP 7.1, 7.2, 7.3
    • more post-compilation optimizations

    View Slide

  8. What remains before 7.4?
    • Check freshness
    • Transfer opcodes into each request’s memory
    • Link parent classes, interfaces and traits
    • Validate signatures, covariance and contravariance

    View Slide

  9. PHP 7.4
    • Immutable opcodes stay in shared memory
    • And…

    View Slide

  10. Preloading
    The theory, from https://wiki.php.net/rfc/preload

    View Slide

  11. Out of scope
    • Bootstraping the kernel or equivalent
    • Loading the services (persistent connections)
    • Computing the response

    View Slide

  12. Preloading removes
    • Freshness checks
    • Everything related to loading and declaring classes and functions
    • Everything is available out-of-the-box, feels like native
    • (RIP function autoloading, you’ll never have any purpose)

    View Slide

  13. • BTW, constants are not preloaded
    Constants were not declared
    when preloading was used

    View Slide

  14. Preloading 101
    • opcache.preload=/some/preloading/script.php in your php.ini
    opcache.preload_user=www-data if running as root (Docker anyone?)
    • Any classes or functions included by this script
    Any classes or functions opcache_compile_file()’ed
    • php-fpm / php -S will load them before serving any requests

    View Slide

  15. (symbol => opcodes) pairs in SHM
    • Incompatible with several apps on the same FPM server
    • Is really immutable: reboot the server to reload the code
    • opcache_get_status() gives all the info
    • Declarations nested in « if » statements are not preloaded
    • Only fully-resolved classes are preloaded
    (you can ignore the warnings)
    • (oh, it’s not available on Windows)

    View Slide

  16. Preloading
    The practice

    View Slide

  17. Which strategy for your preload.php?
    • opcache_compile_file()
    or
    • include() + autoloading?

    View Slide

  18. opcache_compile_file()?

    View Slide

  19. https://github.com/symfony/symfony/pull/32032

    View Slide

  20. https://github.com/composer/composer/issues/7777

    View Slide

  21. opcache_compile_file()?
    1. Deploy without preloading
    2. Get some HTTP traffic
    3. Dump preload.php from opcache_get_status()
    4. Restart prod with preloading
    5. Automate for every deployment
    • Still loads too many classes (that’s theory)
    • Not practical – requires advanced tooling
    • Not compatible with class_alias()

    View Slide

  22. Composer to the rescue?

    View Slide

  23. include() + autoloading?
    • Allows for complex loading strategies
    src/ vendor/
    var/cache/

    View Slide

  24. The obvious part: preload all services
    List all classes used by services
    (autoloading will cascade to
    parent classes/traits/interfaces)

    View Slide

  25. Cascade to types used
    on the public API
    (skip protect/private, they lead
    to over-preloading)

    View Slide

  26. List classes used by
    the implementation

    View Slide

  27. View Slide

  28. List classes used by
    the implementation
    Exclude warmup-time services

    View Slide

  29. List cache warmer artifacts
    (e.g. compiled Twig templates,
    annotation classes, etc.)

    View Slide

  30. View Slide

  31. include() + autoloading!
    • The configuration drives the useful classes
    • Inline class_exists() declare local sidekicks
    • container.preload/.no_preload for fine tuning
    • Make cache warmers report their artifacts

    View Slide

  32. • opcache.preload=var/cache/prod/App_KernelProdContainer.preload.php
    • Profit
    • (optionally, fine tune with opcache_get_status() and commit the result)
    • (go patch open-source bundles and libs to maximize their preloading potential)
    include() + autoloading?

    View Slide

  33. Conclusion
    The practice

    View Slide

  34. Kudos Dmitry and Nikita
    • include() + autoloading
    • opcache_compile_file()
    • Hello World with Twig and without preloading: 360 req/s
    • with preloading, no cache warmers: 560 req/s (+55%)
    • with preloading, yes cache warmers: 630 req/s (+75%)
    • A typical app spends 15% loading code
    • It’s worth it! (but not worth a complex deployment process)
    PS: there is no memory to share when running on the CLI
    PPS: autoloading + Composer FTW and here to stay

    View Slide

  35. Thank you!
    See you at Disneyland Paris in December or earlier hopefully!

    View Slide