Problem/Motivation
In an API-First context, typed data is used A LOT so any micro optimization goes a long way. During profiling in JSON:API i found strlen to be executed too many times. It turned out that the method was used to check whether we have a prefix or not, so we can have that changed to an empty() call instead.
Proposed resolution
I've changed this:
return (strlen($prefix) ? $prefix . '.' : '') . $this->name;
to this
return strlen($prefix) ? "{$prefix}.{$this->name}" : $this->name;
to leverage fast string concatenation - https://blog.blackfire.io/php-7-performance-improvements-encapsed-string...
Remaining tasks
Review, RTBC, commit.
User interface changes
None.
API changes
None.
Data model changes
None.
Release notes snippet
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #38 | 3017710-38.patch | 740 bytes | joseph.olstad |
| #21 | 3017710-20.patch | 741 bytes | ndobromirov |
| #16 | Workspace 1_246.png | 129.23 KB | ndobromirov |
| #16 | Workspace 1_244.png | 90.86 KB | ndobromirov |
| Workspace 1_242.png | 87.72 KB | ndobromirov |
Comments
Comment #2
ndobromirov commentedComment #3
ndobromirov commentedComment #4
ndobromirov commentedComment #5
ndobromirov commentedComment #6
wim leersHoly crap! Micro optimizations that actually matter.
Comment #7
ndobromirov commentedComment #8
wim leersI meant to do this.
@ndobromirov Can you specify which version of PHP 7 you profiled against?
Comment #9
tstoecklerIf we want to go for performance I would think that
!$prefixwould be even faster, but not sure.Also, more importantly, this is a behavior change if
$prefixis'0'. Since I don't see any discussion of that, I'm not sure whether or not this can actually happen here, so moving back to needs review.Comment #10
ndobromirov commentedPHP version is 7.2, but the fast string concatenation landed in 7.0.
#9 So instead of checking for empty, we can check for isset on the zero-th character. This will work for the
"0"case.My train of thought is to avoid strlen that is linear based on the prefix's length. And move that to a PHP native opcode operation that will work in a constant time. Putting a new patch shortly.
!$prefixStill does not handle the zero scenario.Another thing, is the prefix
'0'a valid case? There is no test for it, even though it's a proper edge case...Comment #11
ndobromirov commentedHere is the new version. No benchmarks. I am expecting similar performance.
Comment #12
wim leers@tstoeckler FYI: I felt comfortable RTBC'ing this since the entire test suite passes.
Comment #13
ndobromirov commentedIf that's not ok, we can always compare to empty string with
$prefix === '', as it might be more readable.Comment #14
borisson_Are we sure that the increase in speed is from the change of strlen? Or is it because of the change in string concatenation?
Because if it is the latter, that seems a whole lot less controversial.
In any case, if #9 is a valid prefix, we should probably add a test for that as well, so we can be sure that this new patch doesn't break that behavior?
Comment #15
ndobromirov commentedEven if it's not, it's 16k+ less function calls in my current use case, so if possible I will prefer not having to call
strlen.It seems there is already tests to cover that. No idea why it did not trigger:
Anyone can take over writing the tests.
Comment #16
ndobromirov commentedHere is a benchmark with the
strlen()and just the concatenation fixes - small wins.strlenis still executed ~18k times more than its needed.And

strlenviewComment #17
berdirJust following for now and one comment. Keep in mind that profiling adds overhead and that overhead gets bigger with a lot of calls to fast functions. I'd imagine the improvement might be a lot less if you do a performance test without xdebug. for example just call this method many times in a loop and then check the time it took with a microtime() call before + after.
xhprof (or whatever this is exactly) might have less overhead, but I know that for example blackfire can increase the exeuction times by 200-300%.
Comment #18
ndobromirov commentedThere is no xdebug - just the profiler - xhpof. The overhead is roughly doubling the execution time. The percentage improvement will stay the same with and without xhprof enabled and that is around 35% for this function with the proposed changes.
Comment #19
berdir> The overhead is roughly doubling the execution time.
Yes, but my point is that the overhead is bigger for fast functions, so yes, it will still be 35% faster than before, but this specific function might be reported as 3-4 or more times slower due to the overhead and not just 50%. Plus, empty()/isset() and array access is not a regular function and I guess is not tracked at all by xhprof. So you can't just compare how much time you have less on strlen(), but instead compare the execution time of the function.
So instead, I would try to compare it without xhprof and just do a PHP script that calls strlen() a million times or so and compare the passed time.
I did find https://stackoverflow.com/questions/6955913/isset-vs-strlen-a-fast-clear..., which does report that isset() is 6x faster *but* that was in 2011. There is a comment that says that the performance difference is only 10% on PHP 7.1 with 10k iterations (interestingly, that reported the difference got bigger with more iterations).
That is contradicted by this comment on strlen: http://php.net/manual/de/function.strlen.php#120632.
Just like the comments on the stackexchange question say, the new code is harder to understand, so my point is that we should only do this if we are *sure* that there really is a measurable performance difference.
PS: xdebug was a typo in my previous comment, should have said xhprof in both cases.
Comment #20
wim leers@Berdir++
Alternatively, disable caching and do 1K requests with and without the patch (and report variance) with
ab. IOW: do benchmarking of a repeatable use case. If the performance difference is noticeable, it should show up there too.Comment #21
ndobromirov commentedYup... They are performance wise the same. I did not know that but PHP stored a counter for the length, so strlen is constant - O(1) as well with that in mind I will pass a patch only with the concatenation tweaks.
Based on a local script after 5 mil iterations it turned out that strlen is actually faster than isset()[0] with a margin of 0.3E-6 over the whole execution time (not average) :D.
Comment #22
borisson_Ok, that means we don't need tests, we're not changing anything functional anymore. We're only doing a different kind of concatenation now.
We should probably update the Summary of this issue to indicate that we're not longer looking at strlen/empty to make this faster. So that someone reading the issue description doesn't get confused with the patch :)
Comment #23
wim leersSo what is the real-world performance gain that's we're observing then?
Comment #27
joseph.olstadPatch applies cleanly on latest head of 9.1.x - tests triggered
Patch applies cleanly on latest head of 9.0.x - tests triggered
Patch applies cleanly on latest head of 8.9.x
Patch applies cleanly on latest head of 8.8.x
This is a win, I'll take any performance gain I can get with no harm to functionality, super!
Comment #28
catchThe discussion here indicates this is no longer being done for performance (since strlen() is no longer slower than isset()), and it doesn't remove the function call.
The new version of the code is more readable - is that the only improvement here?
Comment #32
alexpottRe #28 well according to https://blog.blackfire.io/php-7-performance-improvements-encapsed-string... the patch here will be able leverage fast string concatenation while HEAD does not. Also in case of no prefix we are no longer doing concatenation. SO I guess we can do this. It certainly is not going to hurt.
Comment #33
joseph.olstadTriggerred patch against 9.5.x, 9.4.x and 9.3.x ALL GREEN
Comment #34
alexpottOnce getPropertyPath() has a return typehint of string we can change the
strlen()to!== ''which will be a saving if this is called thousands of times. There is a value in removing this from profiles as it might make other things easier to spot. I doubt the gains a big at all here. But they will be bigger than 0.Comment #35
alexpottFixing issue summary.
Comment #36
alexpottSo I profiled this with the following script:
The new function seems to come out on tops more often than not but it is a bit random.
I then profiled a version of the function like
return $prefix !== '' ? "{$prefix}.{$this->name}" : $this->name;and that was always quicker.I did the profiling on PHP 8.1 so maybe there are further improvements to string concatenation in PHP 7 or PHP 8 that make this change not important. I think we should retask this to do the no string len at all.
Comment #37
joseph.olstadnew patch as per comment #36
Comment #38
joseph.olstadOops on patch 37, new patch.
Comment #41
joseph.olstadTriggered a couple
Comment #42
berdir\MoveBlockFormTest::testMoveBlock might be a random file, I wouldn't be so sure about the other one. Layout Builder definitely does some funky stuff with entities and fields, wouldn't be urprised if that's a real issue.
@joseph.olstad: You might want to have a look at git apply -3, that will create an inline merge conflict like a git merge and not .rej and .orig files.
Comment #43
joseph.olstadtests for #38 are green for 9.3.x and 9.4.x
patch is verbatim as designed by @alexpott from #36
@berdir, yes thanks for that suggestion I will try
git apply -3next time and see what it does.Comment #46
smustgrave commentedTriggering for 10.1 but was this waiting on anything else? Reading the comments and seemed to leave off on the test failures. Can wait to see what 10.1 does.
Comment #47
smustgrave commentedNo D10 failures.
Comment #48
alexpottCommitted 91e19e5 and pushed to 10.1.x. Thanks!
Comment #50
phenaproximaOne small thing that should maybe be done in this issue as a small follow-up patch: there really should be a comment there explaining why we're using the constructs we're using, and that it was specifically done for performance reasons. Otherwise we risk regressing it in the future, since we're not (AFAIK) doing regular profiling of core.
Comment #51
quietone commentedAdding tag for a followup per #50
Comment #53
quietone commentedFollowup made, #3343913: Add comments explaining performance improvement in TypedData