Skip to content

Commit b01e4ac

Browse files
committed
perf: reduce allocation on x86
1 parent f830bb2 commit b01e4ac

File tree

3 files changed

+23
-32
lines changed

3 files changed

+23
-32
lines changed

src/generic.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
#[inline]
2-
// Slightly modified version of
3-
// <https://github.com/serde-rs/json/blob/d12e943590208da738c092db92c34b39796a2538/src/ser.rs#L2079>
4-
// Borrowed from:
5-
// <https://github.com/oxc-project/oxc-sourcemap/blob/e533e6ca4d08c538d8d4df74eacd29437851591f/src/encode.rs#L331>
62
pub fn escape_generic<S: AsRef<str>>(s: S) -> String {
73
let s = s.as_ref();
84
let bytes = s.as_bytes();
9-
105
// Estimate capacity - most strings don't need much escaping
116
// Add some padding for potential escapes
127
let estimated_capacity = bytes.len() + bytes.len() / 2 + 2;
13-
let mut result = Vec::with_capacity(estimated_capacity);
8+
let result = Vec::with_capacity(estimated_capacity);
9+
escape_inner(bytes, result)
10+
}
1411

12+
#[inline]
13+
// Slightly modified version of
14+
// <https://github.com/serde-rs/json/blob/d12e943590208da738c092db92c34b39796a2538/src/ser.rs#L2079>
15+
// Borrowed from:
16+
// <https://github.com/oxc-project/oxc-sourcemap/blob/e533e6ca4d08c538d8d4df74eacd29437851591f/src/encode.rs#L331>
17+
pub(crate) fn escape_inner(bytes: &[u8], mut result: Vec<u8>) -> String {
1518
result.push(b'"');
1619

1720
let mut start = 0;

src/lib.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,20 @@ pub use generic::escape_generic;
121121
pub fn escape<S: AsRef<str>>(input: S) -> String {
122122
#[cfg(target_arch = "x86_64")]
123123
{
124+
use generic::escape_inner;
125+
126+
let result = Vec::with_capacity(input.as_ref().len() + input.as_ref().len() / 2 + 2);
127+
let s = input.as_ref();
128+
let bytes = s.as_bytes();
124129
// Runtime CPU feature detection for x86_64
125130
if is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512bw") {
126-
unsafe { return x86::escape_avx512(input) }
131+
unsafe { return x86::escape_avx512(bytes, result) }
127132
} else if is_x86_feature_detected!("avx2") {
128-
unsafe { return x86::escape_avx2(input) }
133+
unsafe { return x86::escape_avx2(bytes, result) }
129134
} else if is_x86_feature_detected!("sse2") {
130-
unsafe { return x86::escape_sse2(input) }
135+
unsafe { return x86::escape_sse2(bytes, result) }
131136
} else {
132-
return escape_generic(input);
137+
return escape_inner(bytes, result);
133138
}
134139
}
135140

src/x86.rs

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,9 @@ fn sub(a: *const u8, b: *const u8) -> usize {
3131

3232
#[target_feature(enable = "avx512f", enable = "avx512bw")]
3333
#[inline]
34-
pub unsafe fn escape_avx512<S: AsRef<str>>(input: S) -> String {
35-
let s = input.as_ref();
36-
let bytes = s.as_bytes();
34+
pub unsafe fn escape_avx512(bytes: &[u8], mut result: Vec<u8>) -> String {
3735
let len = bytes.len();
3836

39-
// Pre-allocate with estimated capacity
40-
let estimated_capacity = len + len / 2 + 2;
41-
let mut result = Vec::with_capacity(estimated_capacity);
42-
4337
result.push(b'"');
4438

4539
let start_ptr = bytes.as_ptr();
@@ -237,7 +231,7 @@ pub unsafe fn escape_avx512<S: AsRef<str>>(input: S) -> String {
237231
}
238232
} else {
239233
// Fall back to AVX2 for small strings
240-
return escape_avx2(input);
234+
return escape_avx2(bytes, result);
241235
}
242236

243237
// Copy any remaining bytes
@@ -251,15 +245,9 @@ pub unsafe fn escape_avx512<S: AsRef<str>>(input: S) -> String {
251245

252246
#[target_feature(enable = "avx2")]
253247
#[inline]
254-
pub unsafe fn escape_avx2<S: AsRef<str>>(input: S) -> String {
255-
let s = input.as_ref();
256-
let bytes = s.as_bytes();
248+
pub unsafe fn escape_avx2(bytes: &[u8], mut result: Vec<u8>) -> String {
257249
let len = bytes.len();
258250

259-
// Pre-allocate with estimated capacity
260-
let estimated_capacity = len + len / 2 + 2;
261-
let mut result = Vec::with_capacity(estimated_capacity);
262-
263251
result.push(b'"');
264252

265253
let start_ptr = bytes.as_ptr();
@@ -477,7 +465,7 @@ pub unsafe fn escape_avx2<S: AsRef<str>>(input: S) -> String {
477465
}
478466
} else {
479467
// Fall back to SSE2 for small strings
480-
return escape_sse2(input);
468+
return escape_sse2(bytes, result);
481469
}
482470

483471
// Copy any remaining bytes
@@ -491,14 +479,9 @@ pub unsafe fn escape_avx2<S: AsRef<str>>(input: S) -> String {
491479

492480
#[target_feature(enable = "sse2")]
493481
#[inline]
494-
pub unsafe fn escape_sse2<S: AsRef<str>>(input: S) -> String {
495-
let s = input.as_ref();
496-
let bytes = s.as_bytes();
482+
pub unsafe fn escape_sse2(bytes: &[u8], mut result: Vec<u8>) -> String {
497483
let len = bytes.len();
498484

499-
let estimated_capacity = len + len / 2 + 2;
500-
let mut result = Vec::with_capacity(estimated_capacity);
501-
502485
result.push(b'"');
503486

504487
let start_ptr = bytes.as_ptr();

0 commit comments

Comments
 (0)