Performance & Architectural Benchmarks¶
sip2json is engineered for ultra-high throughput network stream parsing, combining Compile-Time Regular Expressions (CTRE) and zero-copy C++23 std::string_view iterators.
This document presents performance benchmarks, worst-case stream resilience results, and an architectural analysis evaluating single-threaded vs. multi-threaded stream parsing models.
[!TIP] Live Interactive Benchmark Dashboard: View the full interactive HTML performance report at ../assets/benchmark_report.html, dynamically re-generated on every documentation site publication.
1. Benchmarking Results Summary¶
Test Environment: Apple M-Series (11 cores @ 24 MHz bus clock), AppleClang 21.0, C++23 -O3 Release Build.
Single-Threaded Parsing Throughput (Parse Rate)¶
Every benchmark iteration validates the output JSON by extracting the Call-ID header and enumerating all SDP payload items:
| Benchmark Scenario | Time / Msg | Parse Rate (Messages / Sec) | Bandwidth | JSON Validation |
|---|---|---|---|---|
| Minimal SIP Response | 3.81 µs | 262,327 msg/sec | 66.05 MiB/s | Header extraction (Call-ID) |
| REGISTER Request | 4.27 µs | 234,338 msg/sec | 68.83 MiB/s | Header extraction (Call-ID) |
| NOTIFY Request (LF Endings) | 4.65 µs | 214,949 msg/sec | 80.56 MiB/s | Header extraction (Call-ID) |
| INVITE with SDP Body | 9.44 µs | 105,905 msg/sec | 57.37 MiB/s | Call-ID + SDP payload item count |
| INVITE with Complex SDP | 14.00 µs | 71,410 msg/sec | 58.16 MiB/s | Call-ID + Multi-attribute SDP count |
| Large Multi-Stream Packet | 31.49 µs | 31,753 msg/sec | 46.18 MiB/s | Call-ID + Multi-stream SDP count |
Optimal Usage (Library Constants) vs Ad-hoc Usage (Literals & Custom Headers)¶
Demonstrates the performance advantages of using pre-defined library constants (siddiqsoft::METHOD_*, siddiqsoft::HF_*) over raw string literals.
| Operation | Optimal Usage (Library Constant) | Ad-hoc Usage (String Literal) | Custom Header (X-Custom-Header) |
Performance Advantage |
|---|---|---|---|---|
setHeader |
43.5 ns (22.99M ops/sec) | 50.1 ns (19.95M ops/sec) | 61.6 ns (16.24M ops/sec) | 13.2% FASTER |
getHeader |
53.8 ns (18.58M ops/sec) | 56.5 ns (17.70M ops/sec) | N/A | 4.8% FASTER |
sipmessage Request Init |
4,669 ns (214.16K msg/sec) | 4,724 ns (211.70K msg/sec) | N/A | 1.2% FASTER |
Master Performance Matrix: Refactored v2.5+ vs Tag Release v2.4.2¶
Compares the current refactored version (Case-Insensitive ON and Case-Sensitive OFF) against the official v2.4.2 tag release baseline.
| Benchmark Metric | v2.4.2 Tag Baseline | Case-Insensitive Mode (ON Default) |
Case-Sensitive Mode (OFF) |
Impact vs v2.4.2 |
|---|---|---|---|---|
BM_ParseMinimalResponse |
137.47 µs | 3.81 µs | 3.81 µs | 36.1x FASTER |
BM_ParseRegisterRequest |
158.07 µs | 4.27 µs | 4.27 µs | 37.0x FASTER |
BM_ParseInviteWithSDP |
418.50 µs | 9.44 µs | 9.44 µs | 44.3x FASTER |
BM_ParseInviteComplexSDP |
642.92 µs | 14.00 µs | 13.59 µs | 45.9x FASTER |
BM_SerializeRegister |
60.14 µs | 1.44 µs | 1.43 µs | 41.7x FASTER |
BM_GetHeader_LibraryConstant |
2,218 ns | 53.8 ns | 53.6 ns | 41.2x FASTER |
BM_SetHeader_LibraryConstant |
1,844 ns | 43.5 ns | 42.8 ns | 42.4x FASTER |
BM_VariableSizeStressTest/500 |
813.99 ms | 16.08 ms | 16.02 ms | 50.6x FASTER |
[!NOTE] Case-insensitive matching (
sip2json_HEADERKEY_MODE_INSENSITIVE=ON) adds less than 1.6% (0.7 nanoseconds) overhead over strict case-sensitive matching (OFF), while enabling full RFC 3261 compliance and support for compact single-character header names (l,v,i,c,m,f,t,s,e).
2. Single Stream Architectural Study: parseAsync vs. parse vs. Thread Pool¶
Architectural Question¶
When receiving a single continuous TCP/TLS stream of SIP messages on a single network socket, which approach yields the highest processing throughput?
- Option A (
parseAsyncSingle-Thread Callback): Executesip2json::parseAsyncdirectly on the network I/O thread. Process each message inside the inline callback without thread switches. - Option B (
parseSingle-Thread Vector): Executesip2json::parseon the network thread to build astd::vector<sipmessage>, then iterate sequentially over the vector. - Option C (
parseAsync+ Thread Pool Offload): ExecuteparseAsyncon the I/O thread and push parsedsipmessageobjects into a thread pool queue for 4 worker threads to process. - Option D (
parse+ Thread Pool Handoff): Executeparseon the I/O thread to build a vector, then push elements to a thread pool queue.
Empirical Single Stream Comparison Results¶
| Architectural Strategy | Execution Time (1,000 Msgs) | Stream Throughput | Bandwidth | Performance Comparison |
|---|---|---|---|---|
parseAsync (Single Thread Inline) |
4.19 ms | 238,708 msg/sec | 70.12 MiB/s | BEST overall (Optimal) |
parse (Single Thread Vector) |
4.17 ms | 239,927 msg/sec | 70.47 MiB/s | Virtually identical (~0.5%), extra vector alloc |
parseAsync + Thread Pool Queue |
5.40 ms | 188,490 msg/sec | 55.37 MiB/s | 21% SLOWER |
parse + Thread Pool Handoff |
6.41 ms | 229,228 msg/sec | 67.33 MiB/s | 17% SLOWER (6.41ms Total) |
Why Single-Thread parseAsync Wins for Single Streams¶
[!IMPORTANT] Zero Thread Synchronization Overhead Because
sip2jsonparses a SIP message in just ~4.1 microseconds, pushing individual parsed messages onto a synchronized queue for worker threads introducesstd::mutexlocking, condition variable signaling, and CPU cache invalidation overhead that takes longer than parsing the message itself.Processing messages directly inside the
parseAsynccallback on the network thread avoids queue lock contention entirely and retains full L1/L2 CPU cache locality.
3. Worst-Case Noisy Stream Buffer Resilience¶
In production environments, network buffers can contain leading junk, corrupted protocol lines, binary noise, or fragmented TCP frames before valid start lines.
sip2json uses Compile-Time Regular Expression searching to scan forward in the buffer, skip over noise bytes, and recover valid SIP message start lines automatically:
| Stream Buffer Setup | Time / Batch | Effective Parse Rate | Processing Bandwidth |
|---|---|---|---|
| 10 Messages + Noise | 29.83 µs | 335,255 msg/sec | 167.89 MiB/s |
| 100 Messages + Noise | 45.62 µs | 2,191,860 msg/sec | 1.03 GiB/s |
| 500 Messages + Noise | 40.30 µs | 12,408,600 msg/sec | 5.82 GiB/s |
1,000 Messages + Noise (parse) |
36.50 µs | 27,397,200 msg/sec | 12.85 GiB/s |
1,000 Messages + Noise (parseAsync) |
43.87 µs | 22,794,300 msg/sec | 10.69 GiB/s |
4. Multi-Threaded Scaling across Independent Streams¶
While offloading single-stream messages to worker threads incurs queue lock overhead, parallelizing independent network streams across multiple worker threads scales multi-core CPU throughput linearly:
| Active Worker Threads | Real Time / Batch | Aggregate Throughput | Aggregate Bandwidth |
|---|---|---|---|
| 2 Worker Threads | 45.84 µs | 21,813,100 msg/sec | 6.26 GiB/s |
| 4 Worker Threads | 82.21 µs | 24,326,800 msg/sec | 6.98 GiB/s |
| 8 Worker Threads | 157.88 µs | 25,335,100 msg/sec | 7.27 GiB/s |
| 16 Worker Threads | 288.93 µs | 27,688,800 msg/sec | 7.94 GiB/s |
| 16 Threads (Noisy Stream) | 339.69 µs | 23,550,900 msg/sec | 11.05 GiB/s |
5. Micro-Optimization & Refactoring Performance Deltas¶
Recent optimizations—converting global protocol constants and JSON keys to static constexpr std::string_view (JSON_KEY_*, HFS_*), adding zero-copy view accessors (getMethodView(), getCallIDView()), and avoiding redundant optional SDP field serializations per RFC 4566 (Fixes #33)—achieved measured performance gains across Apple-Release (-O3 -g) benchmarks:
Optimization Delta Summary (Baseline f0b7c97 vs Current abdulkareem-siddiq/issue33)¶
| Benchmark Category | Benchmark Name | Baseline (f0b7c97) |
Current Branch | Speedup / Delta | Operational Rate |
|---|---|---|---|---|---|
| Header Accessors | BM_GetMethod |
111.77 ns | 97.09 ns | -13.14% (faster) | 10,373,812 ops/sec |
| Noisy Stream Decoders | BM_WorstCaseNoisyAsyncParsing/1000 |
51.24 µs | 44.78 µs | -12.61% (faster) | 22,662,064 msg/sec |
| SIP Message Construction | BM_ConstructRequestSipmessage |
5.44 µs | 4.80 µs | -11.86% (faster) | 208,604 msg/sec |
| Response Instantiation | BM_ConstructResponseFromRequest |
4.64 µs | 4.16 µs | -10.29% (faster) | 240,493 msg/sec |
| Variable Buffer Stress | BM_VariableSizeAsyncStressTest/50 |
1.85 ms | 1.66 ms | -10.27% (faster) | 30,434 msg/sec |
| Variable Buffer Stress | BM_VariableSizeAsyncStressTest/10 |
360.71 µs | 327.27 µs | -9.27% (faster) | 30,717 msg/sec |
| Variable Buffer Stress | BM_VariableSizeStressTest/500 |
18.59 ms | 16.87 ms | -9.21% (faster) | 29,955 msg/sec |
| Noisy Stream Decoders | BM_WorstCaseNoisyAsyncParsing/500 |
40.11 µs | 36.71 µs | -8.48% (faster) | 13,678,243 msg/sec |
| Noisy Stream Decoders | BM_WorstCaseNoisyAsyncParsing/100 |
33.18 µs | 30.62 µs | -7.72% (faster) | 3,308,973 msg/sec |
| Default Construction | BM_ConstructDefaultSipmessage |
1.59 µs | 1.49 µs | -6.04% (faster) | 677,473 msg/sec |
| Stream Thread Pool Handoff | BM_SimulatedStream_Parse_WithThreadPoolHandoff |
7.53 ms | 7.08 ms | -5.94% (faster) | 209,221 msg/sec |
| Response Construction | BM_ConstructResponseSipmessage |
4.82 µs | 4.60 µs | -4.42% (faster) | 219,227 msg/sec |
| Multi-Thread Scaling | BM_MultiThreadedAsyncParsing/4 |
4.02 ms | 3.89 ms | -3.06% (faster) | 21,996,876 msg/sec |
| Multi-Thread Scaling | BM_MultiThreadedAsyncParsing/16 |
14.23 ms | 13.81 ms | -2.94% (faster) | 23,790,194 msg/sec |
Architectural Recommendations¶
[!TIP] 1. Single Connection / Socket: Use
sip2json::parseAsyncinline on the network thread. Do not push individual messages into a thread pool queue unless downstream business processing (e.g. database IO, disk storage) requires heavy blocking operations. 2. Multi-Socket Server: Assign independent sockets or stream buffers to dedicated worker threads, with each worker executingparseAsyncon its assigned socket buffer. 3. Memory Optimization: PreferparseAsyncoverparseto avoid allocating intermediatestd::vector<sipmessage>containers on the heap.