Problem/Motivation
isset()
is fast but doesn't take in account NULL values, we can use it ahead of an array_key_exists()
check to glean some of it's speed but PHP's short circuit ||
http://www.zomeoff.com/php-fast-way-to-determine-a-key-elements-existanc...
Proposed resolution
Before
if (array_key_exists($key, $array))
After
if (isset($array[$key]) || array_key_exists($key, $array))
Performance profiling
Scenario: 50 nodes generated on the homepage.
With warm cache but caches turned off with example.settings.local.php used.
After drush cr
with the same.
Exceptions
If the array variable can potentially be an object without array access, array_key_exists()
will ignore that but isset()
will throw an Fatal Error in trying to use it as an array.
Example
// drush scr test.php
use Symfony\Component\HttpFoundation\ParameterBag;
$name = 'test';
$raw_parameters = new ParameterBag([$name => $name]);
if (isset($raw_parameters[$name]) && array_key_exists($name, $raw_parameters)) {
print 'test';
}
Comments
Comment #2
joelpittetComment #3
joelpittetComment #5
dawehnerIMHO fixing parts of core which are called often, this might make sense. Fixing it all over the place is a bit unfortunate because it reduces readability.
Comment #6
joelpittetMay have found bugs if the logic is correct(and I didn't make typos).
But yeah, I agree @dawehner. The ones that are called lots will have the largest impact. Knowing that is another problem though.
Comment #7
dawehnerIt is hard to judge, maybe we should start with a limit of places where we thin we might have something costly. Still, I doubt you can really easily measure a time difference.
Comment #8
joelpittetWe can profile this to see what kind of overall time difference. Need to fix the fails (and find out why the fails) first, then we can profile to see. Following that I think we can start profiling.
This one could be a not of work for little gain, but I'm hoping to either learn something new that I didn't know before about that PHP optimization pattern or make some performance gains:)
Comment #9
joelpittet( ! ) Fatal error: Cannot use object of type Symfony\Component\HttpFoundation\ParameterBag as array in core/lib/Drupal/Core/Controller/ControllerResolver.php on line 139
That's an interesting one:)
ParameterBag
is implementingIteratorAggregate, Countable
. Soarray_key_exists()
ignores that caveat.Added to issue summary!
Comment #10
joelpittetMost of the errors were on that, didn't check them all locally but a couple I did checked passed. So let's try this without that ControllerResolver change. (we could put a
is_array()
check in that which may be idea actually becauseis_
type checks are quite fast too)Comment #12
joelpittetLol diffed against 8.0.x. Well if someone wants to upgrade, that's the patch.
Comment #13
joelpittetComment #15
joelpittetRe testing due to #2749955: Random fails in UpdatePathTestBase tests
Comment #17
joelpittetSweet one more test to pass and that was a logic error I introduced to the diff. Much simpler to do
!(isset() || array_key_exists)
in parens for the negationComment #18
joelpittetScenario: 50 nodes generated on the homepage.
With warm cache but caches turned off with example.settings.local.php used.
After
drush cr
with the same.This is totally minor, but maybe we can do the top ones that made a difference?
Suggesting these ones if any:
Comment #19
dawehnerSo you show the time spent in
array_key_exists()
but yeah,isset()
is essentially for free.It is not only that there is less time spend, the amount of noise for profiling people is reduced, so there is another positive aspect on that side of things.
Comment #23
joseph.olstadpatch needs a reroll.
Comment #24
joseph.olstadpatch to follow shortly
Comment #25
joseph.olstadComment #27
joseph.olstadComment #29
joseph.olstadThis one should pass
Comment #30
joseph.olstadThis one should also pass, space indent style correction in
core/modules/migrate/src/Plugin/migrate/process/SubProcess.php
Comment #33
joseph.olstadI'll review and reroll shortly, I noticed a few more copy / paste issues.
Comment #34
joseph.olstadunfortunately I don't have a test system ready with phpUnit installed (prerequisite for D8 testing suite), so relying on d.o for tests.
Comment #35
joseph.olstadComment #36
joelpittetYou can use this issue if you want.
#2465399: IGNORE: Patch testing issue
Comment #37
joseph.olstadComment #38
joseph.olstad@joelpittet ? the IGNORE patch testing issue, is that for ignoring PHP Unit so that I can run tests locally without PHP Unit?
there's little description in that issue. ? please let me know
Comment #39
joelpittetIt's for when a change may affect many tests and may fail a lot before it's ready, to avoid spamming the original issue with experiments.
It's a normal issue with a name that others have used for this purpose.
Comment #40
joseph.olstadGreat work joel_pittet , now if we can get this committed we won't have to keep rerolling it.
Comment #41
catchisset() is nearly free, but it's not entirely free. So if the isset() checks fail more often than not, it's not necessarily an improvement.
Comment #42
willzyx CreditAttribution: willzyx commentedI agree with catch, we should use the
isset() || array_key_exists()
approach where it make sense. Probably the best candidates in core for this are:Drupal\Core\Utility\ThemeRegistry::has
Drupal\Core\KeyValueStore\MemoryStorage::has
Drupal\Core\KeyValueStore\MemoryStorage::get
Drupal\Component\Utility\NestedArray::getValue
Drupal\Component\Utility\NestedArray::unsetValue
For views already exists an RTBC issue #2903843: views get_option() micro optimization
Anyway I think the the performance gain whit this patch for d8 is minimal, specially if you run php 7+.. Could make more sense for d7 as proposed here #2863786: D7 ThemeRegistry array_key_exists() micro-optimization
Comment #43
joseph.olstadok, sounds good, so reroll a smaller patch doing this just for:
Comment #44
dawehnerI would vote for skipping those as well, as you would not use a memory storage in the critical path.
Comment #45
dawehnerComment #46
joseph.olstadThe requested/suggested changes have been made according to #2770065-43: array_key_exists() micro-optimization and additional suggestion at #2770065-44: array_key_exists() micro-optimization . So yes I agree with #44. see new patch
For background information and justification of this performance patch please read: #2903843-13: views get_option() micro optimization
This gives us the simplest patch with the biggest positive impact on performance (as noted the biggest gains will be on php 5.6.x however still gains in php 7.x as well). Side note: php 5.6 will get security updates until Dec 31 2018 whereas php 7.0 will only get security updates until Dec 1 2018
http://php.net/supported-versions.php
Comment #47
joseph.olstadComment #48
joseph.olstadAll green, RTBC anyone?
Comment #49
joelpittetRTBC +1 from me but I made a patch in this, so not worthy;)
Comment #50
joseph.olstadI made exactly the changes to the patch as recommended by the core maintainers.
For more information on this performance improvement please see php.net array_key_exists AND also this detailed comparison between php versions
I crunched the numbers myself and came with a conservative estimate of performance improvement for the code in question and resulted in:
in php 5.6.12 expect a 134 % improvement, meaning more than twice as fast!
in php 7.0.11 expect a 56% improvement, meaning over 50% faster!
See this website for detailed Performance statistics between the various PHP versions.
So, the numbers don't lie, based on the stats alone there is a very strong performance argument in favor of this performance patch regardless of the chosen php version.
Comment #51
joseph.olstadAs per policy (respect thy authori-tie). The D7 performance fix is pending this D8 fix. I've marked the D7 issue as postponed until this issue is fixed.
#2863786: D7 ThemeRegistry array_key_exists() micro-optimization
Comment #52
dsutter CreditAttribution: dsutter as a volunteer commentedI looked at the patch and agree that it would be a performance improvement and does not affect the logic. However, if it turned out that the value being tested is NULL most of the time, then there may not be much of a performance improvement. I'd go with it.
Comment #53
larowlanAlthough you were only making changes core maintainers asked, you can't RTBC your own patch.
Can someone else give it one final look over?
Comment #54
dawehnerNote: while microopts might sound interesting the total time spend there could still be 1ms. I think looking at an entire page request and see how often these methods are called is abetter way to judge the effectiveness of an optimization.
Nevertheless, these 3 places are probably in the path of a typical rendered page.
Comment #55
joseph.olstadAccording to the xhprof run that joel_pittet logged the few lines of code that this patch touches was executed over 500 times.
so yes, a few milliseconds improvement on a vanilla test environment, however on bigger complex installations of Drupal the improvement will be greater.
it's fine and dandy to see a vanilla site perform fast, however on a serious Drupal site with with hundreds of blocks, millions of nodes, hundreds of taxonomies, a few hundred contrib modules, several content types, several entity types with many fields, a theme with a lot of different twigs/templates and throw in special sauce, then this type of micro optimization becomes more important.
Comment #56
dawehnerTo play the devil's advocate here, don't these optimisations scale linearly with all the other costs?
Comment #57
joseph.olstadThat would seem like a plausible assumption. However given the the almost limitless ways Drupal can be used I'd disagree because some modules might excerpt a heavier load on these core functions than others making this a non-linear optimisation.
Comment #58
dawehnerIt'd be happy if this would be true :)
Comment #59
larowlanAdding review credits
Comment #60
larowlanCommitted as aaefc3f and pushed to 8.5.x.
Comment #62
joelpittetThank you 🎉🎉
Comment #64
jweowu CreditAttribution: jweowu at Catalyst IT commentedCan these cases please be reverted, for Drupal 9+ at least?
It was obviously a performance bug in PHP, and unsurprisingly it's been fixed. I'm using PHP 7.4.20 and this awkward combo of isset and array_key_exists is slower than array_key_exists on its own (according to the benchmark used in http://thinkofdev.com/php-fast-way-to-determine-a-key-elements-existance... which seems to be where this hack originated), so any such code should be reverted to the simpler and now-faster method.
I haven't tested in PHP 7.3, but https://www.drupal.org/docs/system-requirements/php-requirements#php_req... says that 7.3 is not recommended for D9, so I feel it's valid to target a minimum of PHP 7.4 for performance micro-optimisations (and change absurd-looking code back to sensible-looking code in the process).
Comment #65
joseph.olstad@jweowu good to know that this has been fixed in php 7.4.20
with that said, please create a new issue and a patch and link the issue to this one.
we can continue this development in a new issue.
Comment #66
jweowu CreditAttribution: jweowu at Catalyst IT commented