Skip to content

Commit 9ebd3ca

Browse files
committed
aarch64
1 parent bd6aaf7 commit 9ebd3ca

File tree

4 files changed

+73
-106
lines changed

4 files changed

+73
-106
lines changed

Cargo.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ version = "0.1.0"
44
edition = "2021"
55

66
[features]
7-
nightly = []
8-
default = ["nightly"]
7+
nightly = [] # For benchmark
8+
default = []
99

1010
[dependencies]
1111
anyhow = "1"
1212

1313
[dev-dependencies]
14-
serde_json = "1"
14+
serde_json = "1"
15+
16+
[profile.bench]
17+
lto = true

src/aarch64.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use std::arch::aarch64::{
2+
uint8x16_t, uint8x16x4_t, vld1q_u8, vld1q_u8_x4, vmaxvq_u8, vpmaxq_u8, vqtbl1q_u8, vqtbl4q_u8,
3+
};
4+
use std::sync::OnceLock;
5+
6+
use crate::{encode_str_inner, ESCAPE};
7+
8+
static ESCAPE_TABLE_LOW: OnceLock<uint8x16x4_t> = OnceLock::new();
9+
static ESCAPE_TABLE_HIGH: OnceLock<uint8x16_t> = OnceLock::new();
10+
const CHUNK_SIZE: usize = 16;
11+
12+
pub fn encode_str<S: AsRef<str>>(input: S) -> String {
13+
let input_str = input.as_ref();
14+
let mut output = Vec::with_capacity(input_str.len() + 2);
15+
let bytes = input_str.as_bytes();
16+
let writer = &mut output;
17+
writer.push(b'"');
18+
unsafe {
19+
let mut start = 0;
20+
while start + CHUNK_SIZE < bytes.len() {
21+
let next_chunk = start + CHUNK_SIZE;
22+
let table_low = ESCAPE_TABLE_LOW.get_or_init(|| vld1q_u8_x4(ESCAPE[0..64].as_ptr()));
23+
let table_high = ESCAPE_TABLE_HIGH.get_or_init(|| vld1q_u8(ESCAPE[64..80].as_ptr()));
24+
let chunk = vld1q_u8(bytes[start..next_chunk].as_ptr());
25+
let low_mask = vqtbl4q_u8(*table_low, chunk);
26+
let high_mask = vqtbl1q_u8(*table_high, chunk);
27+
if vmaxvq_u8(vpmaxq_u8(low_mask, high_mask)) == 0 {
28+
writer.extend_from_slice(&bytes[start..next_chunk]);
29+
start = next_chunk;
30+
continue;
31+
}
32+
encode_str_inner(&bytes[start..next_chunk], writer);
33+
start = next_chunk;
34+
}
35+
36+
if start < bytes.len() {
37+
encode_str_inner(&bytes[start..], writer);
38+
}
39+
}
40+
writer.push(b'"');
41+
// Safety: the bytes are valid UTF-8
42+
unsafe { String::from_utf8_unchecked(output) }
43+
}

src/lib.rs

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
#![cfg_attr(feature = "nightly", feature(portable_simd))]
21
#![cfg_attr(feature = "nightly", feature(test))]
32

4-
#[cfg(feature = "nightly")]
5-
pub use nightly::encode_str;
3+
#[cfg(target_arch = "aarch64")]
4+
pub use aarch64::encode_str;
65

7-
#[cfg(feature = "nightly")]
8-
mod nightly;
6+
#[cfg(target_arch = "aarch64")]
7+
mod aarch64;
98

109
const BB: u8 = b'b'; // \x08
1110
const TT: u8 = b't'; // \x09
@@ -92,18 +91,24 @@ macro_rules! tri {
9291
};
9392
}
9493

95-
#[cfg(not(feature = "nightly"))]
96-
pub fn encode_str<S: AsRef<str>>(input: S, writer: &mut String) {
97-
let writer = unsafe { writer.as_mut_vec() };
94+
#[cfg_attr(target_arch = "aarch64", allow(unused))]
95+
#[inline]
96+
fn encode_str_fallback<S: AsRef<str>>(input: S) -> String {
97+
let mut output = String::with_capacity(input.as_ref().len() + 2);
98+
let writer = unsafe { output.as_mut_vec() };
9899
writer.push(b'"');
99-
encode_str_inner(input, writer);
100+
encode_str_inner(input.as_ref().as_bytes(), writer);
100101
writer.push(b'"');
102+
output
103+
}
104+
105+
#[cfg(not(target_arch = "aarch64"))]
106+
pub fn encode_str<S: AsRef<str>>(input: S) -> String {
107+
encode_str_fallback(input)
101108
}
102109

103110
#[inline]
104-
pub(crate) fn encode_str_inner<S: AsRef<str>>(input: S, writer: &mut Vec<u8>) {
105-
let input = input.as_ref();
106-
let bytes = input.as_bytes();
111+
pub(crate) fn encode_str_inner(bytes: &[u8], writer: &mut Vec<u8>) {
107112
let mut start = 0;
108113
for (i, &byte) in bytes.iter().enumerate() {
109114
let escape = ESCAPE[byte as usize];
@@ -112,7 +117,7 @@ pub(crate) fn encode_str_inner<S: AsRef<str>>(input: S, writer: &mut Vec<u8>) {
112117
}
113118

114119
if start < i {
115-
writer.extend_from_slice(&input.as_bytes()[start..i]);
120+
writer.extend_from_slice(&bytes[start..i]);
116121
}
117122

118123
let char_escape = CharEscape::from_escape_table(escape, byte);
@@ -124,7 +129,7 @@ pub(crate) fn encode_str_inner<S: AsRef<str>>(input: S, writer: &mut Vec<u8>) {
124129
if start == bytes.len() {
125130
return;
126131
}
127-
writer.extend_from_slice(&input.as_bytes()[start..]);
132+
writer.extend_from_slice(&bytes[start..]);
128133
}
129134

130135
/// Writes a character escape code to the specified writer.
@@ -161,9 +166,7 @@ fn write_char_escape(writer: &mut Vec<u8>, char_escape: CharEscape) {
161166
#[test]
162167
fn test_escape_ascii_json_string() {
163168
let fixture = r#"abcdefghijklmnopqrstuvwxyz .*? hello world escape json string"#;
164-
let mut buf = String::new();
165-
encode_str(fixture, &mut buf);
166-
assert_eq!(buf, serde_json::to_string(fixture).unwrap());
169+
assert_eq!(encode_str(fixture), serde_json::to_string(fixture).unwrap());
167170
}
168171

169172
#[test]
@@ -183,10 +186,9 @@ fn test_escape_json_string() {
183186
fixture.push_str("normal string");
184187
fixture.push('😊');
185188
fixture.push_str("中文 English 🚀 \n❓ 𝄞");
186-
let mut buf = String::new();
187-
encode_str(fixture.as_str(), &mut buf);
189+
encode_str(fixture.as_str());
188190
assert_eq!(
189-
buf,
191+
encode_str(fixture.as_str()),
190192
serde_json::to_string(fixture.as_str()).unwrap(),
191193
"fixture: {:?}",
192194
fixture
@@ -197,30 +199,21 @@ fn test_escape_json_string() {
197199
mod bench {
198200
extern crate test;
199201

200-
use std::hint::black_box;
201-
202202
use test::Bencher;
203203

204204
const FIXTURE: &str = include_str!("../cal.com.tsx");
205205

206206
#[bench]
207207
fn bench_simd_encode(b: &mut Bencher) {
208208
b.iter(|| {
209-
let mut buf = String::new();
210-
black_box(super::encode_str(FIXTURE, &mut buf));
211-
buf
209+
super::encode_str(FIXTURE);
212210
});
213211
}
214212

215213
#[bench]
216214
fn bench_encode(b: &mut Bencher) {
217215
b.iter(|| {
218-
let mut buf = String::new();
219-
let writer = unsafe { buf.as_mut_vec() };
220-
writer.push(b'"');
221-
black_box(super::encode_str_inner(FIXTURE, writer));
222-
writer.push(b'"');
223-
buf
216+
super::encode_str_fallback(FIXTURE);
224217
});
225218
}
226219
}

src/nightly.rs

Lines changed: 0 additions & 72 deletions
This file was deleted.

0 commit comments

Comments
 (0)