Copy the short runs between escapes as a block of 16 bytes

The characters between two escapes (a quote, a backslash, a control
character) are copied to the write buffer by memcpy with a length known only
at run time. For a run of up to 16 characters that has 16 or more bytes in
the string behind its start and in the buffer behind its end, a copy of a
fixed block of 16 bytes does the same without the call; the surplus bytes in
the buffer are overwritten by what follows.

Apple M1, Apple clang, retired instructions per dump(), on top of the
previous changes:
  JSON in JSON 8.32M -> 7.52M (-10 %)   paths 8.17M -> 7.49M (-8 %)
  log lines 7.49M -> 7.14M (-5 %)       cjk, emoji text (no escapes): +-1 %
Linux x86-64 (callgrind), gcc 13 / clang 18:
  JSON in JSON -11 % / -10 %, paths -7 % / -6 %, log lines -5 % / -4 %.
The branch is marked unlikely: text without escapes (CJK, accented Latin)
has few short runs, and without the hint it paid up to 5 % for the check.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-22 09:28:28 +02:00
1 parent 47462527b2
commit ebcd4ae4ab
2 files changed
+46 -2

No files matched your search

+23 -1
View File
@@ -1038,7 +1038,7 @@ class serializer
put_buffer(string_buffer, bytes); put_buffer(string_buffer, bytes);
bytes = 0; bytes = 0;
} }
put_string(s, i, i + run); put_run(s, i, run);
bytes_after_last_accept = 0; bytes_after_last_accept = 0;
undumped_chars = 0; undumped_chars = 0;
i += run; i += run;
@@ -1474,6 +1474,28 @@ class serializer
write_buffer_pos += length; write_buffer_pos += length;
} }
/*!
@brief append the characters of @a str in [@a start, @a start + @a length)
A run of up to 16 characters with 16 or more in the string from @a start on
and in the write buffer from its end on is copied as a whole block of 16,
which spares the call of memcpy with a length known only at run time; the
surplus characters in the buffer are overwritten by what follows.
*/
template<typename StringType>
void put_run(const StringType& str, const std::size_t start, const std::size_t length)
{
// a short run is the exception in text without escapes (CJK, accented
// Latin), for which this keeps the long path the fast one
if (JSON_HEDLEY_UNLIKELY(length <= 16 && start + 16 <= str.size() && write_buffer_pos + 16 <= write_buffer.size()))
{
std::memcpy(write_buffer.data() + write_buffer_pos, str.data() + start, 16);
write_buffer_pos += length;
return;
}
put_string(str, start, start + length);
}
/*! /*!
@brief append the characters of @a str in [@a start, @a end) @brief append the characters of @a str in [@a start, @a end)
+23 -1
View File
@@ -31182,7 +31182,7 @@ class serializer
put_buffer(string_buffer, bytes); put_buffer(string_buffer, bytes);
bytes = 0; bytes = 0;
} }
put_string(s, i, i + run); put_run(s, i, run);
bytes_after_last_accept = 0; bytes_after_last_accept = 0;
undumped_chars = 0; undumped_chars = 0;
i += run; i += run;
@@ -31618,6 +31618,28 @@ class serializer
write_buffer_pos += length; write_buffer_pos += length;
} }
/*!
@brief append the characters of @a str in [@a start, @a start + @a length)
A run of up to 16 characters with 16 or more in the string from @a start on
and in the write buffer from its end on is copied as a whole block of 16,
which spares the call of memcpy with a length known only at run time; the
surplus characters in the buffer are overwritten by what follows.
*/
template<typename StringType>
void put_run(const StringType& str, const std::size_t start, const std::size_t length)
{
// a short run is the exception in text without escapes (CJK, accented
// Latin), for which this keeps the long path the fast one
if (JSON_HEDLEY_UNLIKELY(length <= 16 && start + 16 <= str.size() && write_buffer_pos + 16 <= write_buffer.size()))
{
std::memcpy(write_buffer.data() + write_buffer_pos, str.data() + start, 16);
write_buffer_pos += length;
return;
}
put_string(str, start, start + length);
}
/*! /*!
@brief append the characters of @a str in [@a start, @a end) @brief append the characters of @a str in [@a start, @a end)