Improve performance of string output (#3643)

Printing a string wrote each character with a separate call, one
fwrite() per character, or one jv_string_append_buf() for tojson and
tostring, which made output of string-heavy data slow.

Scan ahead for the next byte that needs escaping and write the run of
bytes before it with a single call, without changing the output.
Printing strings is now significantly faster, most of all for long
strings with few characters to escape, and the same applies to tojson
and tostring. With --ascii-output, text that is almost all non-ASCII is
about the same speed, since nearly every byte is escaped anyway.
This commit is contained in:
Michiel van Slobbe
2026-10-10 21:50:10 +09:00
committed by GitHub
parent a5323a369c
commit b904884b94
3 changed files with 35 additions and 1 deletions
+12 -1
View File
@@ -126,7 +126,18 @@ static void jvp_dump_string(jv str, int ascii_only, FILE* F, jv* S, int T) {
int c = 0;
char buf[32];
put_char('"', F, S, T);
while ((i = jvp_utf8_next((cstart = i), end, &c))) {
while (i < end) {
// Write each run of bytes that need no escaping with a single call.
for (cstart = i; cstart < end; cstart++) {
unsigned char b = *cstart;
if (b < 0x20 || b == '"' || b == '\\' || b == 0x7F || (ascii_only && b >= 0x80))
break;
}
if (cstart > i)
put_buf(i, cstart - i, F, S, T);
if (cstart == end)
break;
i = jvp_utf8_next(cstart, end, &c);
assert(c != -1);
int unicode_escape = 0;
if (0x20 <= c && c <= 0x7E) {
+9
View File
@@ -100,6 +100,15 @@ null
["foo", 1, ["a", 1, "b", 2, {"foo":"bar"}]]
["foo",1,["a",1,"b",2,{"foo":"bar"}]]
# Put escapes on both sides of runs of bytes that need no escaping
map(tojson)
["abcdefg\"", "abcdefgh\\", "abcdefghi\u0001j", "abcdefghijklmno\u007f", "abcdefgh\té", "abcdefghijklmnop\n"]
["\"abcdefg\\\"\"","\"abcdefgh\\\\\"","\"abcdefghi\\u0001j\"","\"abcdefghijklmno\\u007f\"","\"abcdefgh\\té\"","\"abcdefghijklmnop\\n\""]
[range(18) as $i | ("\"", "\\", "\u0001", "\u007f", "é", "😀") as $c | "abcdefghijklmnopq"[:$i] + $c + "abcdefghijklmnopq"[$i:] | (tojson | fromjson) == .] | all
null
true
#
# Dictionary construction syntax
#
+14
View File
@@ -21,4 +21,18 @@ for l in 1 2 3 4; do
fi
done
# --ascii-output escapes non-ASCII characters on both sides of runs of
# bytes that need no escaping
$VALGRIND $Q $JQ -cn '[range(18) as $i | ("é", "😀", "\u007f") as $c |
"abcdefghijklmnopq"[:$i] + $c + "abcdefghijklmnopq"[$i:]]' > $d/input
$VALGRIND $Q $JQ -ac . $d/input > $d/out
if LC_ALL=C grep -q '[^ -~]' $d/out; then
echo "--ascii-output printed a non-ASCII byte"
exit 1
fi
if ! $VALGRIND $Q $JQ -e --slurpfile a $d/input '. == $a[0]' $d/out > /dev/null; then
echo "--ascii-output changed a string"
exit 1
fi
exit 0