Optimize JSON pretty print indentation performance#21474
Optimize JSON pretty print indentation performance#21474LamentXU123 wants to merge 8 commits intophp:masterfrom
Conversation
Could you give some before/after numbers? |
Sure do, but maybe tomorrow. I think this is a pretty obvious optimization so I don't do this initially |
|
A short benchmark would be appreciated. |
|
So you should probably raise threshold in that condition ( |
Well when depth > 2 the performance of the optimized version start to be better than the original one, and the benefits become bigger when depth increase. I think the threshold can be increased to 8, because since than it provides a 1.10x optimization which is useful enough |
ext/json/json_encoder.c
Outdated
| if (depth <= 8) { | ||
| int i; | ||
| for (i = 0; i < depth; i++) { | ||
| smart_str_appendl(buf, " ", 4); | ||
| } | ||
| } else { | ||
| size_t remaining = (size_t) depth * 4; | ||
| char *dst = smart_str_extend(buf, remaining); | ||
| memset(dst, ' ', remaining); | ||
| } |
There was a problem hiding this comment.
There was a problem hiding this comment.
How can the original loop can be faster?
I am wondering this too, probably because memset is causing extra cost... But I think thats ok the loss can be ignored when depth goes bigger.
I will run benchmarks to test the original approach later (but I highly doubt if it could be better than memset)
There was a problem hiding this comment.
Just a guess, memset with an arbitrary length may be overspecialized for large sizes. In that case, I don't see a big point of this PR. 50 levels of nesting seem pretty artificial. Nevertheless, I'm not code owner so I'll keep that decision up to those who are.
There was a problem hiding this comment.
50 levels of nesting seem pretty artificial.
The original approach (by defining space constant) should work in simple json structures I guess. I will send in the test results later in this thread
There was a problem hiding this comment.
Just a guess,
memsetwith an arbitrary length may be overspecialized for large sizes. In that case, I don't see a big point of this PR. 50 levels of nesting seem pretty artificial. Nevertheless, I'm not code owner so I'll keep that decision up to those who are.
<?php
$data = [];
$ptr = &$data;
for ($i = 0; $i < 2; $i++) {
$ptr["level_$i"] = ["msg" => "opt_test", "id" => $i];
$ptr = &$ptr["level_$i"];
}
for ($i = 0; $i < 500000; $i++) {
json_encode($data, JSON_PRETTY_PRINT);
}
?>
So by using the original optimization approach the optimized version is 1.29x slower in depth 2 and 1.12x slower in depth 1.
So yes, this pr could only optimize nested data.
It the return variable the bottleneck? Or maybe use the original approach with longer spaces literal " ..." with like 64 spaces?
This happens to be even slower, weird. Probably because of compiler's optimization, because smart_str_appendl(buf, " ", 4) allows the compiler to optimize the constant size 4 into a single 32-bit integer store, bypassing the overhead of a variable-length memcpy and branch evaluations present in the optimized block.






This PR optimizes the
php_json_pretty_print_indentfunction in the JSON extension to improve performance when encoding structures withJSON_PRETTY_PRINT.When I was reading this, I found that now, the indentation logic used a for loop to append spaces in increments of 4 characters per depth level. For a JSON structure with a depth of N, this resulted in N consecutive calls to
smart_str_appendl. This approach introduces unnecessary overhead due to repeated function calls.So I think I would introduce this space-time optimization to add a static constant string of pre-allocated spaces. For almost all typical JSON depths, this reduces the number of
smart_str_appendlcalls from O(N) to exactly 1. This significantly reduces function call overhead and improves CPU performance duringjson_encodewithJSON_PRETTY_PRINT, especially for deeply nested data.