Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ PHP 8.6 UPGRADE NOTES

- JSON:
. Improve performance of encoding arrays and objects.
. Improved performance of indentation generation in json_encode()
when using PHP_JSON_PRETTY_PRINT.

- Standard:
. Improved performance of array_fill_keys().
Expand Down
18 changes: 13 additions & 5 deletions ext/json/json_encoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,19 @@ static inline void php_json_pretty_print_char(smart_str *buf, int options, char

static inline void php_json_pretty_print_indent(smart_str *buf, int options, const php_json_encoder *encoder) /* {{{ */
{
if (options & PHP_JSON_PRETTY_PRINT) {
for (int i = 0; i < encoder->depth; ++i) {
smart_str_appendl(buf, " ", 4);
}
}
if (options & PHP_JSON_PRETTY_PRINT) {
Comment thread
LamentXU123 marked this conversation as resolved.
Outdated
int depth = encoder->depth;
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);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can the original loop can be faster? The inlined functions are almost the same:

size_t new_len = smart_str_alloc(dest, len, persistent);
memcpy(ZSTR_VAL(dest->s) + ZSTR_LEN(dest->s), str, len);
ZSTR_LEN(dest->s) = new_len;

size_t new_len = smart_str_alloc(dest, len, persistent);
char *ret = ZSTR_VAL(dest->s) + ZSTR_LEN(dest->s);
ZSTR_LEN(dest->s) = new_len;
return ret;

It the return variable the bottleneck? Or maybe use the original approach with longer spaces literal " ..." with like 64 spaces?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

@LamentXU123 LamentXU123 Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

@LamentXU123 LamentXU123 Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

<?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);
}
?>

depth 2:
image
depth 1:
image

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.

}
}
/* }}} */

Expand Down
Loading