Skip to content

Commit f9993c2

Browse files
committed
Improvements to the speed of latin1_to_string.
On the benchmark it improves the encoding from around 9 microsends to 6 microseconds. This PR also includes the benchmark and setup as criterion benchmark. Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
1 parent 99731e7 commit f9993c2

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed

mozjs/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ libc.workspace = true
2121
log = "0.4"
2222
mozjs_sys = { path = "../mozjs-sys" }
2323

24+
[dev-dependencies]
25+
criterion = "0.6"
26+
2427
[build-dependencies]
2528
cc.workspace = true
2629
bindgen.workspace = true
30+
31+
32+
[[bench]]
33+
name = "latin1_string_conversion"
34+
harness = false
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use criterion::{criterion_group, criterion_main, Criterion};
2+
use std::ffi::c_void;
3+
use std::{iter, ptr};
4+
5+
use mozjs::conversions::jsstr_to_string;
6+
use mozjs::glue::{CreateJSExternalStringCallbacks, JSExternalStringCallbacksTraps};
7+
use mozjs::jsapi::{
8+
JSAutoRealm, JS_NewExternalStringLatin1, JS_NewGlobalObject, OnNewGlobalHookOption,
9+
};
10+
use mozjs::rooted;
11+
use mozjs::rust::{JSEngine, RealmOptions, Runtime, SIMPLE_GLOBAL_CLASS};
12+
13+
fn external_string(c: &mut Criterion) {
14+
unsafe {
15+
let engine = JSEngine::init().unwrap();
16+
let runtime = Runtime::new(engine.handle());
17+
let context = runtime.cx();
18+
let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook;
19+
let c_option = RealmOptions::default();
20+
rooted!(in(context) let global = JS_NewGlobalObject(
21+
context,
22+
&SIMPLE_GLOBAL_CLASS,
23+
ptr::null_mut(),
24+
h_option,
25+
&*c_option,
26+
));
27+
let _ac = JSAutoRealm::new(context, global.get());
28+
29+
let latin1_base =
30+
iter::repeat_n("test latin-1 test", 1_000_000).fold(String::new(), |mut acc, x| {
31+
acc.push_str(x);
32+
acc
33+
});
34+
35+
let latin1_boxed = latin1_base.as_bytes().to_vec().into_boxed_slice();
36+
let latin1_chars = Box::into_raw(latin1_boxed).cast::<u8>();
37+
let callbacks = CreateJSExternalStringCallbacks(
38+
&EXTERNAL_STRING_CALLBACKS_TRAPS,
39+
latin1_base.len() as *mut c_void,
40+
);
41+
rooted!(in(context) let latin1_jsstr = JS_NewExternalStringLatin1(
42+
context,
43+
latin1_chars,
44+
latin1_base.len(),
45+
callbacks
46+
));
47+
c.bench_function("external_string_latin1", |b| {
48+
b.iter(|| {
49+
jsstr_to_string(context, latin1_jsstr.get());
50+
})
51+
});
52+
}
53+
}
54+
55+
static EXTERNAL_STRING_CALLBACKS_TRAPS: JSExternalStringCallbacksTraps =
56+
JSExternalStringCallbacksTraps {
57+
latin1Finalize: Some(latin1::finalize),
58+
latin1SizeOfBuffer: Some(latin1::size_of),
59+
utf16Finalize: Some(utf16::finalize),
60+
utf16SizeOfBuffer: Some(utf16::size_of),
61+
};
62+
63+
mod latin1 {
64+
use std::ffi::c_void;
65+
use std::slice;
66+
67+
use mozjs::jsapi::mozilla::MallocSizeOf;
68+
69+
pub unsafe extern "C" fn finalize(data: *const c_void, chars: *mut u8) {
70+
let slice = slice::from_raw_parts_mut(chars, data as usize);
71+
let _ = Box::from_raw(slice);
72+
}
73+
74+
pub unsafe extern "C" fn size_of(data: *const c_void, _: *const u8, _: MallocSizeOf) -> usize {
75+
data as usize
76+
}
77+
}
78+
79+
mod utf16 {
80+
use std::ffi::c_void;
81+
use std::slice;
82+
83+
use mozjs::jsapi::mozilla::MallocSizeOf;
84+
85+
pub unsafe extern "C" fn finalize(data: *const c_void, chars: *mut u16) {
86+
let slice = slice::from_raw_parts_mut(chars, data as usize);
87+
let _ = Box::from_raw(slice);
88+
}
89+
90+
pub unsafe extern "C" fn size_of(data: *const c_void, _: *const u16, _: MallocSizeOf) -> usize {
91+
data as usize
92+
}
93+
}
94+
95+
criterion_group!(benches, external_string);
96+
criterion_main!(benches);

mozjs/src/conversions.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,12 @@ impl FromJSValConvertible for f64 {
525525
}
526526
}
527527

528+
/// A simple wrapper for ascii compatible chars. This and the mem::transmute call
529+
/// in latin1_to_string should be replaced when the `ascii_char` feature is stable.
530+
/// See: <https://github.com/rust-lang/rust/issues/110998>.
531+
#[repr(transparent)]
532+
struct AsciiChar(u8);
533+
528534
/// Converts a `JSString`, encoded in "Latin1" (i.e. U+0000-U+00FF encoded as 0x00-0xFF) into a
529535
/// `String`.
530536
pub unsafe fn latin1_to_string(cx: *mut JSContext, s: *mut JSString) -> String {
@@ -536,7 +542,14 @@ pub unsafe fn latin1_to_string(cx: *mut JSContext, s: *mut JSString) -> String {
536542

537543
let chars = slice::from_raw_parts(chars, length as usize);
538544
let mut s = String::with_capacity(length as usize);
539-
s.extend(chars.iter().map(|&c| c as char));
545+
546+
let v = s.as_mut_vec();
547+
for c in chars {
548+
// This is safe as we are ensured that we only have "Latin1" strings which have the upper
549+
// code word 00.
550+
v.push(*c);
551+
}
552+
540553
s
541554
}
542555

0 commit comments

Comments
 (0)