http://drupal.org/node/1747970 allows to write PHP files securely.
Twig is really fast if templates are compiled and run from the Cache.
This works really well, but it needs to be integrated with the way Drupal allows reading/writing PHP files.
Follow-up from: #1696786: Integrate Twig into core: Implementation issue
@todos here:
* Think about how to clear out the PHP cache files again
* Fix class name TwigEnvironment, it is confusing
* Fix documentation and run through docs review
* Benchmark
* Merge back into main issue as separate patch
Comments
Comment #1
fabianx commented*DONE*
http://drupalcode.org/sandbox/pixelmord/1750250.git/commit/7b49741
Patch out of that diff is coming once Twig is in core.
Comment #2
fabianx commentedHere is the diff: http://drupalcode.org/sandbox/pixelmord/1750250.git/commitdiff/1541e5810...
Comment #3
podarok+class TwigEnvironment extends \Twig_Environment {this is so weird ^(
its like
green grin greeen grean
Comment #4
fabianx commentedHere is a first patch that includes the #1696786: Integrate Twig into core: Implementation issue inline with it, so only the relevant bits can be discussed.
I hope dreditor supports that.
Properties:
* Drupal PHP Storage is used
* The template cache can virtually be cleaned by cc all as all last modified times are stored in the cache.
** Such no mtime() method needs to be implemented on the drupal_php_storage() stores.
Comment #5
moshe weitzman commentedI didn't test it out, but the cache code looks good to me. It is so small, that I recommend incorporating it into #1696786: Integrate Twig into core: Implementation issue. Benchmarks with cache enabled would be nice.
Comment #6
webchickWell, I think Fabian's hoping for a bit of dedicated discussion around this portion before incorporating it into the "mega" patch.
Comment #7
webchickBtw, for those wanting to review the relevant changes, cmd+F for "2/2".
Comment #8
fabianx commentedSee: http://drupal.org/node/1811418 for how to rework a patch that is having two patches in one.
Comment #9
podarok#4 patch 2/2 looks good
and I agree with #5
RTBC?
Comment #10
webchickEr, no. :) #3 is a good point, and there is at least one glaring "@todo document me" thing remaining. ;)
Comment #12
podaroklatest HEAD w/o patch
drupal minimal profile install
apachebench second pass
the same with #4 patch
Comment #13
podarok#12 shows speed-up +~51%
did I miss something?
Comment #14
fabianx commented#4: twig_in_core-Twig-enable-drupal-cache-1806546-4.diff queued for re-testing.
Comment #15
fabianx commented#13 Nice. Did you clear caches after applying patch and loading page once?
Did you repeat the tests several times?
Comment #16
podarok#15
yup
logs displayed here second or third passes for better stability results
server with LoadAverage ~0 :)
cache cleared before testing
Comment #17
Crell commentedIs this not injectable yet?
This probably isn't injectable until #1764474: Make Cache interface and backends use the DIC lands, but that's really close. When that happens, this should be injected. That would then enable unit testing of this class, I think.
I agree, though, that this is small enough to just bake into the Twig patch itself. Especially given the impressive numbers above (yowza!)
Comment #19
swentel commentedAre you sure about those benchmarks ? Trying to run the patch gives me fatal error here , unless I'm doing something completely stupid.
Comment #20
moshe weitzman commentedConsidering that Drupal spends less than 50% of its time in the theme layer, I'm pretty sure the benchmark is wrong.
Comment #21
fabianx commentedRe-rolling patch in #4. I am not sure why it fails, but it works locally for me ...
I guess if it really fatals out then that is the reason for the "speedup".
Comment #22
fabianx commentedAny thoughts on how to call this class? I was using the same convention as for the others.
Comment #23
fabianx commentedTestbot: Get to work!
Comment #23.0
fabianx commentedAdd proper link
Comment #24
fabianx commentedThis is injected internally, but I am using the public API as per http://drupal.org/node/1747970.
Yes, I would think so. However I thought the cache() wrapper would remain? Could you clarify how you think this class should be using the DIC?
On what kind of Unit tests did you think?
I think this needs more reviews, more testing and more thought. I would rather have a little longer discussion here, proper benchmarks, etc., than "blow up" the main thread with discussions about everything.
Comment #25
fabianx commentedOkay, I found the problem.
definitely helps ;-) and I agree with #3.
For Benchmarking:
* Enable stark theme
* Benchmark :-)
Comment #26
fabianx commentedAnd here is the new patch.
Comment #28
podarokwith patch #26
template caching works
Comment #29
swentel commentedFrontpage, memory and ab -c1 -n100 - done on bartik
- Before: age execution time was 111.33 ms. Memory used at: devel_boot()=1.19 MB, devel_shutdown()=5.07 MB, PHP peak=5.25 MB.
Requests per second: 10.07 [#/sec] (mean)
- After: Page execution time was 115.26 ms. Memory used at: devel_boot()=1.19 MB, devel_shutdown()=5.35 MB, PHP peak=5.5 MB.
Requests per second: 9.65 [#/sec] (mean)
Node 1, memory and ab -n5 c1000
- Before: Page execution time was 65.1 ms. Memory used at: devel_boot()=1.19 MB, devel_shutdown()=4.75 MB, PHP peak=5 MB.
Requests per second: 15.97 [#/sec] (mean)
- After: Page execution time was 65.89 ms. Memory used at: devel_boot()=1.19 MB, devel_shutdown()=5.02 MB, PHP peak=5.25 MB.
Requests per second: 15.38 [#/sec] (mean)
So with the compilation cache, the difference is *much* less then without, but it's still a tiny little bit slower, and eating a little more memory. I haven't done xhprof, but my guess this is because of the autoloading and two theme engines being enabled.
So I'd really prefer to enable cache by default for the first commit. I'd post this patch on #1696786: Integrate Twig into core: Implementation issue and mark this one duplicate.
Comment #30
fabianx commentedRegardless of the exceptions throwing test (the test is broken).
Here are the first performance results:
I optimized the code paths and now can get with 100 nods tested on /node with xhprof:
Core: 2,064,251
Core + optimized Twig: 2,161,547
=> around 100ms slower
=> around 5% slower
The overhead now only for the node.tpl.php:
Node.tpl.php: 1,053,896
Node.twig (compiled): 1,095,220
=> around 42 ms slower => 4-5% slower
The overhead comes mostly from our hugest gain:
TwigTemplate::getAttribute() which allows the nice twig syntax with the '.':
Twig_Template::getAttribute: 25 ms
The rest of the overhead is from the twig_render() function:
twig_render: 14ms (4 ms the is_? questions, the rest overhead of the function).
It gets called 1600 times ...
The rest is mainly overhead added by twig that comes from the conversion.
So, while there is overhead, I think it justifies the gains :-).
Comment #31
fabianx commentedCurrently done already:
* Removed the __callStatic magic and made this explicit in functions. It can be kept, but is rather slow due to call_user_func_array.
Next steps for optimizing this:
* Remove the _convert_to_reference and do on runTime (40 ms)
* Extend function definition and make references explicit:
* Parse for functions needing Reference in the NodeVisitor and change the getContext Node to getReferenceContext, which returns a TwigReference.
* Functions need to implement to check for instance of TwigReference
* Change the NodeVisitor to re-include show/hide tags
TBC
Comment #32
Crell commentedcache() is for legacy support in procedural code. Instead, the cache object for the appropriate bin should be passed into the object's constructor, and saved to an object propertly. Then instead of
$obj = cache()->get($cid);, you call$this->cache->get($id);That makes it possible to pass in a completely fake cache object, such a memory-backed cache object or a cache object that is hard coded to return values relevant for testing, without modifying the global environment.
Comment #33
fabianx commentedChanged title so we can do performance in here.
Comment #34
fabianx commented@Crell: Thank you very much for this explanation. That makes sense.
So the call in TwigFactory would give the cache and drupal_php_storage objects to the TwigEnvironment and the TwigFactory would get the objects from the DIC, where the cache is set as parameter?
Comment #35
fabianx commentedNew numbers:
With optimization and 100 nodes rendered the slowdown is now 90 ms, but its barely testable ...
Around 10-15 ms of that are the getting from DIC ...
The rest is:
27 ms: get Attribute
18 ms: get ContextReference // creation of TwigReference objects on demand
23 ms: twig_render tests and getReference
1 ms: hide call to getReference
Without making calls to render() explicit again it is almost not possible to optimize that more.
But given that this is 70-100 ms for 100 calls, this is a slowdown of 0.7 - 1 ms per render call; the rest is DIC overhead ... (static, once, O(1))
Patch is coming once cleaned up ...
AB numbers with 100 nodes
Same codebase:
Core with node.tpl.php: 599-603 ms
Core with node.twig: 609-613 ms
=> around 10 ms slowdown with 100 nodes => 0.01 ms slowdown per render call
I think that is totally acceptable :-D.
Comment #36
fabianx commentedIf ever we need to compile time optimize this templates more, here is some code for that.
Comment #37
fabianx commentedOkay, here are the newest Apache AB numbers with one optimization that might need to be removed:
ab -c 5 -n 100 http://127.0.0.1/node/ # test takes 60 seconds
Core with node.tpl.php: 599-602 ms
Core with node.twig: 607-609 ms
0.82% - 1.64% slowdown :-D
I found a performance optimization for render() for the case that an empty array is submitted. In that case $var == null, which is very cheap to test.
To further make this faster the TwigCompiler would need to know what are references and what are not.
For critical code paths like theme link or theme field we could remove the reference logic like:
But that would be premature opt. at this point ...
Patch coming soon ...
Comment #38
fabianx commentedAnd here is the new Patch, which will still fail as I did not fix the Test, yet.
Changes here:
Interpret "needs reference at compile time
Change from call like $func(&arg) to $func($arg), where the definition of the function defines that it needs a reference.
New code:
This move the processing from runtime to compile time as much as possible.
The logic is simple:
If there is a function that wants a reference (instanceof TwigReferenceFunction), then change the "name" identifier to our own getContextReference(), which returns a TwigReference object.
This optimizes all calls that are not hide / show or printing something like all if statements.
Further it would allow us to have a critical section, where this "auto render" logic is off; note that hide / show / explicit render would still work.
Like:
Very similar to autoescape.
Optimization of getContextReference
In the ideal case getContextReference would only be called for variables that are "render arrays". As that information is not known at compile time, we save it as much as we can.
** Save resolved references as ReferenceObjects to $context['_references'][$key].
** Save information about references or no references to $this->is_reference or $this->is_no_reference.
Note: This is debatable as a render array could once be a value and once an array. I don't think besides it being NULL (need to check for that) / empty that this ever happens, but this needs debate.
This saves 1000 is_array calls, which are rather slow given that this is a critical path.
Edit: Just adding
before saves this case.
Lots of micro optimizations
Todos here
* Debate / Remove / Fix for NULL case the is_reference / is_no_reference
* Fix the failing test
* Add proper docs to all classes *sigh*
And thats it.
It is _really_ fast now.
Comment #40
fabianx commentedFixed non rooted class names.
And another one for the testbot ...
Comment #41
fabianx commentedComment #43
fabianx commentedOnly for testbot ...
Comment #44
fabianx commentedYahoo! That last patch 3/3 needs a chx review :)
@chx: Is that an acceptable fix?
Comment #45
podarokcan we convert this into config?
Comment #46
podarokpossibly we have to move into ...files/ folder
possibly we have to move this to config()
trailing whitespace :*
Comment #47
fabianx commented@podarok: Thanks for your feedback.
Please be aware that this issue is only for discussion of PATCH 2/2 (and sometimes 3/3) to keep this focused. 1/2 was RTBC already in itself.
To answer the questions: No, we cannot move any of these to config as these are necessary for the double engine support (will be removed anyway) or for the engine itself.
Modules can extend this anyway, but this should not happen as config.
Comment #48
fabianx commentedab -n100 http://127.0.0.1/node/(on a dedicated VM)TWIG
Node.tpl.php
So in this case twig was even faster, but normally it is now the same and the rest difference is measurement error.
The overhead is described in detail above. We can improve superfluous render calls in follow up issue.
Merging into main branch and closing this as duplicate.
Just putting last patch in, so difference is clear.
Comment #49
fabianx commentedContinuing in #1696786: Integrate Twig into core: Implementation issue.
Comment #49.0
fabianx commentedadd todos