Skip to content

Commit e68092b

Browse files
committed
Update readme
1 parent dd02f93 commit e68092b

File tree

2 files changed

+115
-12
lines changed

2 files changed

+115
-12
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# json-escape-simd
22

3+
![Crates.io Version](https://img.shields.io/crates/v/json-escape-simd)
4+
![docs.rs](https://img.shields.io/docsrs/json-escape-simd)
5+
36
Optimized SIMD routines for escaping JSON strings. This repository contains the `json-escape-simd` crate, comparison fixtures, and Criterion benches against commonly used alternatives.
47

58
> [!IMPORTANT]

src/lib.rs

Lines changed: 112 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,113 @@
1+
//! Optimized SIMD routines for escaping JSON strings.
2+
//!
3+
//! ## <div class="warning">Important</div>
4+
//!
5+
//! On aarch64 NEON hosts the available register width is **128** bits, which is narrower than the lookup table this implementation prefers. As a result the SIMD path may not outperform the generic fallback, which is reflected in the benchmark numbers below.
6+
//!
7+
//! On some modern macOS devices with larger register numbers, the SIMD path may outperform the generic fallback, see the [M3 max benchmark](#apple-m3-max) below.
8+
//!
9+
//! ### Note
10+
//!
11+
//! The `force_aarch64_neon` feature flag can be used to force use of the neon implementation on aarch64. This is useful for the benchmark.
12+
//!
13+
//! ## Benchmarks
14+
//!
15+
//! Numbers below come from `cargo bench` runs on GitHub Actions hardware. Criterion reports are summarized to make it easier to spot relative performance. "vs fastest" shows how much slower each implementation is compared to the fastest entry in the table (1.00× means fastest).
16+
//!
17+
//! ### GitHub Actions x86_64 (`ubuntu-latest`)
18+
//!
19+
//! `AVX2` enabled.
20+
//!
21+
//! **RxJS payload (~10k iterations)**
22+
//!
23+
//! | Implementation | Median time | vs fastest |
24+
//! | --------------------- | ------------- | ---------- |
25+
//! | **`escape simd`** | **345.06 µs** | **1.00×** |
26+
//! | `escape v_jsonescape` | 576.25 µs | 1.67× |
27+
//! | `escape generic` | 657.94 µs | 1.91× |
28+
//! | `serde_json` | 766.72 µs | 2.22× |
29+
//! | `json-escape` | 782.65 µs | 2.27× |
30+
//!
31+
//! **Fixtures payload (~300 iterations)**
32+
//!
33+
//! | Implementation | Median time | vs fastest |
34+
//! | --------------------- | ------------ | ---------- |
35+
//! | **`escape simd`** | **12.84 ms** | **1.00×** |
36+
//! | `escape v_jsonescape` | 19.66 ms | 1.53× |
37+
//! | `escape generic` | 22.53 ms | 1.75× |
38+
//! | `serde_json` | 24.65 ms | 1.92× |
39+
//! | `json-escape` | 26.64 ms | 2.07× |
40+
//!
41+
//! ### GitHub Actions aarch64 (`ubuntu-24.04-arm`)
42+
//!
43+
//! Neon enabled.
44+
//!
45+
//! **RxJS payload (~10k iterations)**
46+
//!
47+
//! | Implementation | Median time | vs fastest |
48+
//! | --------------------- | ------------- | ---------- |
49+
//! | **`escape generic`** | **546.89 µs** | **1.00×** |
50+
//! | `escape simd` | 589.29 µs | 1.08× |
51+
//! | `serde_json` | 612.33 µs | 1.12× |
52+
//! | `json-escape` | 624.66 µs | 1.14× |
53+
//! | `escape v_jsonescape` | 789.14 µs | 1.44× |
54+
//!
55+
//! **Fixtures payload (~300 iterations)**
56+
//!
57+
//! | Implementation | Median time | vs fastest |
58+
//! | --------------------- | ------------ | ---------- |
59+
//! | **`escape generic`** | **17.81 ms** | **1.00×** |
60+
//! | `serde_json` | 19.77 ms | 1.11× |
61+
//! | `json-escape` | 20.84 ms | 1.17× |
62+
//! | `escape simd` | 21.04 ms | 1.18× |
63+
//! | `escape v_jsonescape` | 25.57 ms | 1.44× |
64+
//!
65+
//! ### GitHub Actions macOS (`macos-latest`)
66+
//!
67+
//! Apple M1 chip
68+
//!
69+
//! **RxJS payload (~10k iterations)**
70+
//!
71+
//! | Implementation | Median time | vs fastest |
72+
//! | --------------------- | ------------- | ---------- |
73+
//! | **`escape generic`** | **759.07 µs** | **1.00×** |
74+
//! | `escape simd` | 764.98 µs | 1.01× |
75+
//! | `serde_json` | 793.91 µs | 1.05× |
76+
//! | `json-escape` | 868.21 µs | 1.14× |
77+
//! | `escape v_jsonescape` | 926.00 µs | 1.22× |
78+
//!
79+
//! **Fixtures payload (~300 iterations)**
80+
//!
81+
//! | Implementation | Median time | vs fastest |
82+
//! | --------------------- | ------------ | ---------- |
83+
//! | **`serde_json`** | **26.41 ms** | **1.00×** |
84+
//! | `escape generic` | 26.43 ms | 1.00× |
85+
//! | `escape simd` | 26.42 ms | 1.00× |
86+
//! | `json-escape` | 28.94 ms | 1.10× |
87+
//! | `escape v_jsonescape` | 29.22 ms | 1.11× |
88+
//!
89+
//! ### Apple M3 Max
90+
//!
91+
//! **RxJS payload (~10k iterations)**
92+
//!
93+
//! | Implementation | Median time | vs fastest |
94+
//! | --------------------- | ------------- | ---------- |
95+
//! | **`escape simd`** | **307.20 µs** | **1.00×** |
96+
//! | `escape generic` | 490.00 µs | 1.60× |
97+
//! | `serde_json` | 570.35 µs | 1.86× |
98+
//! | `escape v_jsonescape` | 599.72 µs | 1.95× |
99+
//! | `json-escape` | 644.73 µs | 2.10× |
100+
//!
101+
//! **Fixtures payload (~300 iterations)**
102+
//!
103+
//! | Implementation | Median time | vs fastest |
104+
//! | --------------------- | ------------ | ---------- |
105+
//! | **`escape generic`** | **17.89 ms** | **1.00×** |
106+
//! | **`escape simd`** | **17.92 ms** | **1.00×** |
107+
//! | `serde_json` | 19.78 ms | 1.11× |
108+
//! | `escape v_jsonescape` | 21.09 ms | 1.18× |
109+
//! | `json-escape` | 22.43 ms | 1.25× |
110+
1111
#[cfg(target_arch = "x86_64")]
2112
mod x86;
3113

@@ -60,19 +170,8 @@ pub(crate) const HEX_BYTES: [(u8, u8); 256] = {
60170
bytes
61171
};
62172

63-
#[macro_export]
64-
// We only use our own error type; no need for From conversions provided by the
65-
// standard library's try! macro. This reduces lines of LLVM IR by 4%.
66-
macro_rules! tri {
67-
($e:expr $(,)?) => {
68-
match $e {
69-
core::result::Result::Ok(val) => val,
70-
core::result::Result::Err(err) => return core::result::Result::Err(err),
71-
}
72-
};
73-
}
74-
75173
#[inline]
174+
/// Cross platform generic implementation without any platform specific instructions
76175
pub fn escape_generic<S: AsRef<str>>(input: S) -> String {
77176
let s = input.as_ref();
78177
let bytes = s.as_bytes();
@@ -133,6 +232,7 @@ pub fn escape_generic<S: AsRef<str>>(input: S) -> String {
133232
}
134233

135234
/// Main entry point for JSON string escaping with SIMD acceleration
235+
/// If the platform is supported, the SIMD path will be used. Otherwise, the generic fallback will be used.
136236
pub fn escape<S: AsRef<str>>(input: S) -> String {
137237
#[cfg(target_arch = "x86_64")]
138238
{

0 commit comments

Comments
 (0)