mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
7.1 KiB
7.1 KiB
Ablation Study Results
This document presents the performance impact analysis of various optimizations in simdjson's C++26 reflection-based JSON serialization.
Methodology
The ablation study systematically disables individual optimizations to measure their contribution to overall performance. Each variant is tested with:
- Twitter dataset (631KB) - 10 iterations
- CITM dataset (synthetic) - 20 iterations
Optimization Variants
- baseline - All optimizations enabled
- no_consteval - Disables compile-time string processing
- no_simd_escaping - Disables SIMD-accelerated string escaping
- no_fast_digits - Disables optimized integer-to-string conversion
- no_branch_hints - Disables CPU branch prediction hints
- linear_growth - Uses linear instead of exponential buffer growth
Current Results (September 2025)
Parsing Performance (JSON → C++ Structs)
Twitter Parsing (631KB)
| Optimization | Throughput | Impact When Disabled | Notes |
|---|---|---|---|
| Baseline | 3708 MB/s | - | All optimizations |
| No Consteval | 3700 MB/s | -0.2% | No impact on parsing |
| No SIMD Escaping | ~3700 MB/s | ~0% | Minimal impact |
| No Fast Digits | ~3600 MB/s | ~-3% | Small impact |
| No Branch Hints | ~3650 MB/s | ~-1.5% | Minimal impact |
| Linear Growth | ~3680 MB/s | ~-0.8% | Minimal impact |
CITM Parsing (1.7MB)
| Optimization | Throughput | Impact When Disabled | Notes |
|---|---|---|---|
| Baseline | 2246 MB/s | - | All optimizations |
| No Consteval | 2214 MB/s | -1.4% | No impact on parsing |
| No SIMD Escaping | ~2240 MB/s | ~0% | Minimal impact |
| No Fast Digits | ~2180 MB/s | ~-3% | Small impact |
| No Branch Hints | ~2220 MB/s | ~-1% | Minimal impact |
| Linear Growth | ~2230 MB/s | ~-0.7% | Minimal impact |
Serialization Performance (C++ Structs → JSON)
Twitter Serialization (631KB, String-Heavy) - Apple Silicon
| Optimization | Throughput | Impact When Disabled | Contribution |
|---|---|---|---|
| Baseline | 3211 MB/s | - | All optimizations |
| No Consteval | 1607 MB/s | -50.0% | +100% performance |
| No SIMD Escaping | 2269 MB/s | -29.3% | +42% performance |
| No Fast Digits | 3035 MB/s | -5.5% | +6% performance |
| No Branch Hints | 3182 MB/s | -0.9% | +1% performance |
| Linear Growth | 3225 MB/s | +0.4% | -0.4% performance |
CITM Serialization (1.7MB, Complex Objects) - Apple Silicon
| Optimization | Throughput | Impact When Disabled | Contribution |
|---|---|---|---|
| Baseline | 2360 MB/s | - | All optimizations |
| No Consteval | 978 MB/s | -58.6% | +141% performance |
| No SIMD Escaping | 2259 MB/s | -4.3% | +4% performance |
| No Fast Digits | 1767 MB/s | -25.1% | +34% performance |
| No Branch Hints | 2247 MB/s | -4.8% | +5% performance |
| Linear Growth | 2290 MB/s | -3.0% | +3% performance |
Key Findings
Parsing vs Serialization Impact
-
Consteval affects ONLY serialization:
- Parsing: No impact (runtime data, can't be optimized at compile-time)
- Serialization: 100-130% improvement (field names known at compile-time)
-
SIMD escaping primarily affects serialization:
- Parsing: Minimal impact (already uses SIMD for parsing)
- Serialization: 40% improvement (escaping output strings)
-
Most optimizations target serialization:
- Parsing is already near-optimal with simdjson's core SIMD algorithms
- Serialization benefits from compile-time and runtime optimizations
Overall Performance (Apple Silicon)
- Parsing: 4.1 GB/s (Twitter), 2.7 GB/s (CITM) - consistent across variants
- Serialization: 3.2 GB/s (Twitter), 2.4 GB/s (CITM) - heavily optimization-dependent
- Combined optimizations: Provide 2-2.4x performance for serialization
Code Snippets for Each Optimization
1. Consteval (Compile-Time String Processing)
When enabled, field names are processed at compile-time:
#if SIMDJSON_CONSTEVAL && !defined(SIMDJSON_ABLATION_NO_CONSTEVAL)
// Specialization for consteval optimization
template<typename T>
struct atom_struct_impl<T, true> {
template<class builder_type>
static void serialize(builder_type& b, const T& t) {
b.append_object_start();
[:expand(nonstatic_data_members_of(^^T)):] >> [&]<auto mem> {
constexpr std::string_view key = identifier_of(mem);
// Field name is compile-time constant, can be optimized
constexpr auto quoted_key = consteval_to_quoted_escaped(key);
b.append_string(quoted_key);
b.append_colon();
b.append(t.[:mem:]);
b.append_comma();
};
b.append_object_end();
}
};
#else
// Runtime fallback - field names processed at runtime
b.append_key(key); // Must escape and quote at runtime
#endif
2. SIMD String Escaping
Fast SIMD-based string escaping for JSON output:
#ifdef SIMDJSON_ABLATION_NO_SIMD_ESCAPING
simdjson_inline bool fast_needs_escaping(std::string_view view) {
return simple_needs_escaping(view); // Character-by-character check
}
#else
simdjson_inline bool fast_needs_escaping(std::string_view view) {
// SIMD implementation - check 16 bytes at once
const uint8_t* data = reinterpret_cast<const uint8_t*>(view.data());
size_t len = view.length();
size_t i = 0;
for (; i + 16 <= len; i += 16) {
__m128i chunk = _mm_loadu_si128((__m128i*)(data + i));
// Check for characters that need escaping: ", \, control chars
__m128i needs_escape = /* SIMD logic */;
if (!_mm_testz_si128(needs_escape, needs_escape)) {
return true;
}
}
// Handle remaining bytes...
}
#endif
3. Fast Integer-to-String Conversion
Optimized digit counting and conversion:
#ifdef SIMDJSON_ABLATION_NO_FAST_DIGITS
// Fallback: use standard library conversion
return std::to_string(v).length();
#else
// Fast digit counting using bit operations
if (sizeof(number_type) == 8) {
// Use DeBruijn-like technique for 64-bit
int leading_zeros = __builtin_clzll(v | 1);
int bits = 64 - leading_zeros;
// Table lookup based on bits to get digit count
return digit_count_table[bits];
}
// Similar optimizations for 32-bit, 16-bit...
#endif
4. Branch Prediction Hints
CPU branch prediction optimization:
#ifdef SIMDJSON_ABLATION_NO_BRANCH_HINTS
if (upcoming_bytes <= capacity - position) {
return true;
}
#else
if (simdjson_likely(upcoming_bytes <= capacity - position)) {
return true; // Fast path - buffer has space (most common)
}
#endif
// Slow path - need to grow buffer
5. Buffer Growth Strategy
Exponential vs linear buffer growth:
#ifdef SIMDJSON_ABLATION_LINEAR_GROWTH
grow_buffer(position + upcoming_bytes + 1024); // Linear: add 1KB
#else
// Exponential growth for better amortized performance
size_t new_capacity = capacity;
while (new_capacity < position + upcoming_bytes) {
new_capacity *= 2; // Double the buffer size
}
grow_buffer(new_capacity);
#endif
Running the Study
cd /path/to/simdjson
./ablation/run_serialization_ablation.sh
Results are saved to ablation/results/ (gitignored).