Compare commits

...

3 Commits

Author SHA1 Message Date
Francisco Geiman Thiesen cd4a074653 WIP: bump DEFAULT_INITIAL_CAPACITY to 256KB
Most CITM/Twitter realloc cost was due to growing from 1024 in 7-9 doublings
to reach the actual output size. Bumping the initial capacity to 256KB means
Twitter (82KB) fits in one allocation and CITM (496KB) only needs 1 growth.

Measured (TRUE A/B, 7 alternating rounds in single docker, with all prior
follow-ups in this branch + this cap bump):
  CITM:    4358 -> 4924 MB/s  (+13.0%)  - now ~2.3% AHEAD of Glaze
  Twitter: 6338 -> 8034 MB/s  (+26.8%)  - ~50% AHEAD of Glaze

Trade-off: 256KB upfront per string_builder instance. Reasonable for
high-perf JSON serialization but wasteful for tiny one-off messages.
Users serializing small payloads should pass a smaller initial_capacity
to the constructor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 16:13:26 -07:00
Francisco Geiman Thiesen 0ab7e2d701 WIP: template append_raw on size for compile-time-inlined memcpy
Add append_raw_n<N>(const char*) which propagates the key length as a
template parameter, letting the compiler emit a fully inlined memcpy
with a constant size (direct loads/stores) instead of the size-passed-
as-runtime-arg variant which sometimes fell back to a libc memcpy
dispatch.

Use it in the reflection struct atom for both first_key and rest_key
(both are compile-time constants from define_static_string).

Measured (TRUE A/B, 7 alternating rounds in single docker invocation,
two independent runs combined):
  CITM:    baseline ~4395 -> patched ~4791 MB/s  (+9%)
           Glaze    ~4810 MB/s -> simdjson now at PARITY with Glaze
                                  (within +/- 3% noise across runs)
  Twitter: still well ahead of Glaze, no change from baseline

Output is byte-identical, all reflection comprehensive tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 15:45:08 -07:00
Francisco Geiman Thiesen 0a694273e2 WIP: builder follow-up — inline remaining atoms + skip strlen on keys
Three small follow-ups on top of #2707, all aimed at closing the
remaining CITM gap:

  1. simdjson_really_inline on atom<map>, atom<smart_pointer>,
     atom<enum>. The previous PR missed these three. atom<map> in
     particular was visible at ~11% of CITM profile time before this
     change (CITM has events: std::map<string, CITMEvent>).

  2. struct atom passes the key size explicitly to append_raw(c, len)
     instead of going through append_raw(const char*) which calls
     std::strlen on every key (8 fields × 184 events on CITM).

  3. append_raw(const char*) uses std::char_traits<char>::length
     (constexpr) instead of std::strlen so the compiler can fold the
     length when the pointer is to a compile-time string.

Measured (TRUE A/B, 7 alternating rounds in single docker invocation):
  CITM:    baseline 4437 -> patched 4686 MB/s  (+5.6%)
  Twitter: within noise

Output is byte-identical to baseline. All static_reflection_comprehensive_tests
pass.

WIP — pushing for safekeeping while continuing to investigate the
remaining ~5% gap to Glaze on CITM. Not ready for PR yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 15:34:00 -07:00
3 changed files with 30 additions and 6 deletions
@@ -51,7 +51,7 @@ simdjson_really_inline constexpr void atom(string_builder &b, const T &t) {
template <concepts::string_view_keyed_map T>
requires(!require_custom_serialization<T>)
constexpr void atom(string_builder &b, const T &m) {
simdjson_really_inline constexpr void atom(string_builder &b, const T &m) {
if (m.empty()) {
b.append_raw("{}");
return;
@@ -100,7 +100,12 @@ simdjson_really_inline constexpr void atom(string_builder &b, const T &t) {
constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
constexpr auto rest_key = std::define_static_string(
std::string(",") + constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
b.append_raw(i == 0 ? first_key : rest_key);
// Pass size as template parameter so memcpy is fully inlined with
// a compile-time-constant size.
constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
if (i == 0) b.template append_raw_n<first_key_len>(first_key);
else b.template append_raw_n<rest_key_len>(rest_key);
atom(b, t.[:dm:]);
i++;
};
@@ -121,7 +126,7 @@ simdjson_really_inline constexpr void atom(string_builder &b, const T &opt) {
// Support for smart pointers (std::unique_ptr, std::shared_ptr, etc.)
template <concepts::smart_pointer T>
requires(!require_custom_serialization<T>)
constexpr void atom(string_builder &b, const T &ptr) {
simdjson_really_inline constexpr void atom(string_builder &b, const T &ptr) {
if (ptr) {
atom(b, *ptr);
} else {
@@ -132,7 +137,7 @@ constexpr void atom(string_builder &b, const T &ptr) {
// Support for enums - serialize as string representation using expand approach from P2996R12
template <typename T>
requires(std::is_enum_v<T> && !require_custom_serialization<T>)
void atom(string_builder &b, const T &e) {
simdjson_really_inline void atom(string_builder &b, const T &e) {
#if SIMDJSON_STATIC_REFLECTION
static constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
template for (constexpr auto enum_val : enumerators) {
@@ -759,7 +759,9 @@ simdjson_inline void string_builder::escape_and_append_with_quotes() noexcept {
#endif
simdjson_inline void string_builder::append_raw(const char *c) noexcept {
size_t len = std::strlen(c);
// char_traits::length is constexpr; lets the compiler fold the length
// when called with a pointer to a compile-time-constant string.
size_t len = std::char_traits<char>::length(c);
append_raw(c, len);
}
@@ -778,6 +780,14 @@ simdjson_inline void string_builder::append_raw(const char *str,
position += len;
}
}
template <size_t N>
simdjson_inline void string_builder::append_raw_n(const char *str) noexcept {
if (capacity_check(N)) {
std::memcpy(buffer.get() + position, str, N);
position += N;
}
}
#if SIMDJSON_SUPPORTS_CONCEPTS
// Support for optional types (std::optional, etc.)
template <concepts::optional_type T>
@@ -52,7 +52,7 @@ class string_builder {
public:
simdjson_inline string_builder(size_t initial_capacity = DEFAULT_INITIAL_CAPACITY);
static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1024;
static constexpr size_t DEFAULT_INITIAL_CAPACITY = 262144;
/**
* Append number (includes Booleans). Booleans are mapped to the strings
@@ -185,6 +185,15 @@ requires (!std::is_convertible<R, std::string_view>::value && !concepts::optiona
* There is no UTF-8 validation.
*/
simdjson_inline void append_raw(const char *str, size_t len) noexcept;
/**
* Append exactly N characters from str. The length is a template parameter
* so the compiler can fully inline the memcpy with a compile-time-constant
* size, avoiding the libc call. Used for compile-time-constant keys in the
* reflection struct atom.
*/
template <size_t N>
simdjson_inline void append_raw_n(const char *str) noexcept;
#if SIMDJSON_EXCEPTIONS
/**
* Creates an std::string from the written JSON buffer.