pug-php / pug

Pug template engine for PHP
https://www.phug-lang.com
MIT License
391 stars 42 forks source link

Musings on performance #168

Closed wolfgang42 closed 6 years ago

wolfgang42 commented 7 years ago

I've just spent the evening profiling my application, and have a few ideas about improving performance which might be interesting. (All of this is very firmly in the realm of premature optimization for me right now, but I got distracted.) The below tests were run on PHP 7; timings are with xdebug enabled, which makes them several times slower than a production instance would be but they're all comparable to each other.

Enabling caching is, obviously, the biggest improvement; it takes the render time down from 26,000 ms to 170 ms. As an additional improvement, the docs suggest disabling upToDateCheck (actually the docs seem to be wrong, the code in Render.php calls it up_to_date_check so that's what I used); in practice this makes no appreciable difference. I think this is because the information is being cached by the kernel, and possibly also in PHP's stat cache. (This situation may be different on Windows; I seem to recall that their equivalent of stat() has much worse performance in certain cases.)

Even with the file cached, there seem to be major improvements possible. It seems that a great deal of time is spent in new Pug(), loading and constructing the Renderer and related objects. In fact some 120 ms is spent inside Pug\Pug->construct() and its callees; most of this work is spent initializing the renderer, compiler, and so on, but since the file is cached none of this is necessary! If we load the cache file directly, the template only takes 50 ms to render, which is much faster.

I imagine, therefore, a pre-cached mode to be used in production, where new Pug() returns a 'neutered' instance which has skipped most of its initialization, and only supports the renderFile() method with already cached views (possibly it will be necessary to use a factory function to implement this instead of calling the constructor directly). This would allow significant performance improvements beyond those which can be achieved currently by enabling caching.

[Edit] Actually, thinking about this further, it might be even better (but probably is a lot harder to implement) to lazy-load the lexer/compiler/etc only if the template is not already cached—this way the performance improvements are available to everyone, not just programs that implement precaching.

In fact, for my use-case it would suffice to have cacheDirectory() return a mapping of .pug files to cache files, so I could save it and write my own render function which decides between instantiating Pug and calling the cache directly; this is simpler to implement but has the obvious disadvantage that each application must implement such a feature separately instead of simply being able to pass an option to Pug and let it take care of the rest.

Since I have suggested this I'm happy to have a go at implementing it myself and sending in a pull request, but I thought I would see what you thought of the idea before I dumped a complete implementation in your lap.

kylekatarnls commented 7 years ago

Thanks for all this. Indeed you can see you can call the cached files without Pug at all ; have stand-alone cached file was one of our goal when we created the phug renderer (https://github.com/phug-php/renderer).

To understand why the Pug constructor is slow, there are 2 different steps:

So when we will release the phug documentation, using directly Phug\Renderer with JsPhpizePhug module (or use PHP expressions) can optimize the initialization.

About the lazy-load, it's not impossible. We could have a minimal facade to which you could pass an options array or a callback to init Pug that would only be called if needed, then a method to renderFile that call the cache first if possible.

Ideally, I would even imagine pug-php/pug as a dev dependency (a composer package that would be only downloaded and used in developement) with a binary (or phar) to cache directory. But I did not yet find good solution for this.

I would really appreciate to get your pull-request, just before moving forward with a direct-cache-call optimization, keep in mind that read a template is not a simple file_get_contents, for the moment, we call the FileLocator from the Compiler to find a template path according to the options (basedir or paths that are the directories to search in, and extensions that the locator will try to append if the file is not found without extension) and framework adapters (symfony or laravel) will use it. A cacheDirectory array dump could avoid the problem, but else the path resolution should be available with cache enabled.

Last thing, ->share is usefull to set some global parameters. Theses values should be available even for rendered cached templates.

kylekatarnls commented 7 years ago

I forgot: as much as possible, this optimization feature should be done in https://github.com/phug-php/renderer or https://github.com/phug-php dependencies, to make tale-pug benefit of it.

kylekatarnls commented 7 years ago

If you want to join our slack chat, you can send your e-mail address to contact@phug-lang.com.

kylekatarnls commented 6 years ago

Hi, we're about to solve this problem with https://github.com/phug-php/phug/pull/23

I will save more than 60% of the pug processing in production.

For the compilation (non-cached rendering part), we yet did a lot of improvements and now I'm thinking of a watcher daemon or live-reload implementation to make the compilation time impact lighter: https://github.com/pug-php/pug/issues/188#issuecomment-362944981

kylekatarnls commented 6 years ago

Here is the production optimizer:

https://phug-lang.com/#usage

You feedback is welcome.