is_array is relatively expensive compared to things like empty. The attached patch tries to avoid those calls where possible by gating on empty() and adjusting some logic to favor a trivial assignment over a heavier check is_array check.
On a views heavy panel XHProf showed 15000 microseconds Wall time decrease and 50000 microsecond cputime decrease in views_object::unpack_options which is definitely worth while.
I built and tested this on day job site for 6.x-2.x but ports and testing would be appreciated since I don't have 3.x test sites setup. The patch is straightforward enough this is an easy thing to do.
Comment | File | Size | Author |
---|---|---|---|
#22 | views_unpack_options-is_array_0_0-test.ods | 13.43 KB | dalin |
#22 | views_unpack_options-is_array_0_0-test.diff | 935 bytes | dalin |
#10 | is_array.txt | 1.7 KB | chx |
#9 | is_array.patch | 796 bytes | omerida |
#7 | is_array.txt | 1003 bytes | chx |
Comments
Comment #1
omerida CreditAttribution: omerida commentedThanks for the patch, I tried a similar approach and saw the following in xhprof:
before patch
is_array 79,973 calls, 554,405 incl wall time
after patch
s_array 44,933 calls, 175,630 incl wall time
in your patch you do replace
with
I'm not sure you can do that, because empty will trigger a php notice if the variable you are testing is not set.
I modified your approach and did
The user comments at http://us.php.net/manual/en/function.is-array.php#98156 suggest an even faster approach to testing if something is an array. I'm going to give it a try and will report back.
Comment #2
merlinofchaos CreditAttribution: merlinofchaos commentedempty() will not trigger a notice if it is not set. empty() and isset() are the two notice-catching functions.
Comment #3
omerida CreditAttribution: omerida commentedcasting to an array as php.net is a lot more efficient, especially when you have a lot of views. For me, unpack_options was two times faster with this patch.
With attached patch, calls to is_array are 1/5 what they were before the patch.
Profiled with xhprof
BEFORE:
unpack_options #calls = 472 incl. cpu time = 633,458(16.7%)
is_array #calls = 79,973 incl. cpu time = 252,320 (6.2%)
AFTER:
unpack_options #calls = 473 incl. cpu time = 266,282 (7.8%)
is_array #calls = 15,983 incl. cpu time = 96,769 (1.4%)
Attached is a reworked patch.
Comment #4
merlinofchaos CreditAttribution: merlinofchaos commentedWow. That's shocking. I'm appalled that such a simple primitive is so slow and that using casting and !== is actually faster.
Comment #5
omerida CreditAttribution: omerida commentedsame here, but its been true for as long as I've done PHP, started in 2000. We had an in house cms at a previous job that used is_array and in_array extensively, switching to empty and using isset($var[$key]) gave us startling performance boosts. Do you want me to remove the unnecessary isset from that patch?
Comment #6
merlinofchaos CreditAttribution: merlinofchaos commentedI'm a bit concerned that hunk 2 changes the logic. Right now, an empty array still triggers the clause, and we'll need to carefully study the code to see if that will cause issues with actual empty arrays. Table style options can have empty arrays so that's a good one to test on.
Comment #7
chx CreditAttribution: chx commentedThis can't be true.
Comment #8
chx CreditAttribution: chx commentedI was seemingly able to reproduce your results for smaller arrays -- but then i realized that I had xdebug trace on. So I bet it's the benchmarking that adds so much to a function call that it's too slow.
Comment #9
omerida CreditAttribution: omerida commentedThis patch shouldn't change the logic that was introduced in the original patch's use of empty(). This patch just replaces is_array with the cast to an array tweak.
Performance is still better:
BEFORE:
unpack_options #calls = 472 incl. cpu time = 633,458(16.7%)
is_array #calls = 79,973 incl. cpu time = 252,320 (6.2%)
AFTER:
unpack_options #calls = 473 incl. cpu time = 247,482 (7.1%)
is_array #calls = 16,058 incl. cpu time = 60,231 (1.7%)
Comment #10
chx CreditAttribution: chx commentedJust for posterity, here's a much fleshed out microtime-based benchmark.
Comment #11
merlinofchaos CreditAttribution: merlinofchaos commentedchx's test suggests that the additional time spent is from profiling adding a bunch of overhead to the function call. It may be possible to do a reasonable test using timer_start and timer_stop in unpack_options and run it on a system without profiling.
Comment #12
chx CreditAttribution: chx commentedSo please re-benchmark without xhprof....
Comment #13
omerida CreditAttribution: omerida commentedThat's what I found too. I ran your benchmarks, and for large arrays, is_array is indeed faster. I found that when the array is 20 elements in length, the methods are equivalent. For arrays that are smaller, then the cast hack is faster.
There is an explanation of why over on stackoverflow (http://stackoverflow.com/questions/3470990/is-micro-optimization-worth-t...), the second answer deals with exactly this.
I guess views is generally using small arrays, hence the improvement in performance when profiling execution.
Comment #14
omerida CreditAttribution: omerida commentedI think timer_start and timer_stop don't handle recursive functions very well. I'll try to find another way to profile. Did you see the other replies, using non-profiling micro benchmarks, casting to an array is quicker for small arrays.
Comment #15
omerida CreditAttribution: omerida commentedBenchmarking without khprof enabled. Did ten runs for each option.
Comment #16
chx CreditAttribution: chx commentedOK so that small arrays? Well running my benchmark above shows a surefire win for 5 or less and a maybe win for below 10 (on several runs I see a slower on the four comparisons here and there). 10 and above is a lose. Now is the question, will these arrays always be one digit large?
Comment #17
catchsubscribing, it's definitely worth trying to figure this out, I come across D6 sites spending several hundred milliseconds in unpack_options() regularly.
I'd definitely be wary about profiling overhead, this is especially bad with xdebug, less so with xhprof, but it really matters if you are /only/ swapping a function call for something else.
Without profiling, what about using microtime just before code that call unpack options and then immediately after? That would give a more realistic data set. Either that or would need to var_export() a view then create a benchmark script using roughly the same data.
It would be great if Views got closer to being able to cache the result of unpack_options() between requests - so that the actual building of the view object doesn't have to run every request. Every time I've started to look at this that seemed like a very large task though, so potential quick wins like this are great.
Comment #18
omerida CreditAttribution: omerida commented#15 data used timer_start/stop around unpack options to measure.
Comment #19
catchomerida - and you had both the xhprof and xdebug extensions completely disabled for those?
Comment #20
mcurry CreditAttribution: mcurry commentedsubscribing
Comment #21
omerida CreditAttribution: omerida commentedyes, no profiling going on.
Comment #22
dalinI've got some bad news. I did some extensive benchmarking of this and there is no performance gain. The trick does seem to be xDebug itself. @omerida note that it is important to do the benchmarking without the xdebug PHP extension being loaded at all. Having profiling off is not enough.
Attached are my benchmarking results including a patch that shows how I did the benchmarking.
(array) $options !== $options
is actually about 15% slower thanis_array()
.These results actually open up a can of worms. We've done several micro-optimizations of core doing similar things over the past year or two that may have actually made things worse. We'll need to go back and re-assess all of those.
Comment #23
catchI'm going to mark this duplicate of #402944: Persistent caching for unpack_options() calls from building displays. While the other issue only adds caching, it does so in such a place that it's saving thousands of calls to unpack_options() on cache hits in the site I profiled.
Comment #24
sun.core CreditAttribution: sun.core commented