perf: JsString 缓存 UTF-16 length,字符串 length 访问改 O(1)

This commit is contained in:
爱特每羊队
2026-08-10 16:13:24 +08:00
parent f284f74cb1
commit cd96fa6fcb
2 changed files with 27 additions and 3 deletions
+25 -1
View File
@@ -7,6 +7,7 @@
//! 闭包共享 cell (`Cell`)。
use crate::value::JsValue;
use std::sync::atomic::{AtomicU32, Ordering};
/// 堆分配的 JS 字符串值。
///
@@ -14,12 +15,24 @@ use crate::value::JsValue;
#[derive(Debug)]
pub struct JsString {
pub data: String,
/// JS 语义的字符串长度(UTF-16 code unit 数)懒缓存。
///
/// 构造是热路径(拼接/格式化),长度查询少见,故不预付 O(n) 扫描;首次
/// `utf16_len()` 访问时计算一次。以 sentinel + `AtomicU32` 实现:perm 字符串
/// 可被多线程 VM 共享读,计算幂等,relaxed 序即可。
utf16_len: AtomicU32,
}
/// `utf16_len` 未计算的哨兵值(有效长度不可能为 u32::MAX)。
const UTF16_LEN_UNSET: u32 = u32::MAX;
impl JsString {
/// 用 UTF-8 数据构造字符串。
pub fn new(data: String) -> Self {
Self { data }
Self {
data,
utf16_len: AtomicU32::new(UTF16_LEN_UNSET),
}
}
/// 字符串的字节长度(非字符数)。
@@ -27,6 +40,17 @@ impl JsString {
self.data.len()
}
/// JS 字符串长度(UTF-16 code unit 数)。首次访问计算并缓存,此后 O(1)。
pub fn utf16_len(&self) -> u32 {
let cached = self.utf16_len.load(Ordering::Relaxed);
if cached != UTF16_LEN_UNSET {
return cached;
}
let len = self.data.encode_utf16().count() as u32;
self.utf16_len.store(len, Ordering::Relaxed);
len
}
/// 是否为空字符串。
pub fn is_empty(&self) -> bool {
self.data.is_empty()
+2 -2
View File
@@ -47,8 +47,8 @@ impl Vm {
if val.is_string() {
let length_si = self.kernel_core.perm_interner().intern("length").0;
if prop_name_si == length_si {
// SAFETY: val 是字符串值。
let len = unsafe { (*val.as_string_ptr()).data.encode_utf16().count() };
// SAFETY: val 是字符串值。utf16_len 构造时缓存,O(1) 读取。
let len = unsafe { (*val.as_string_ptr()).utf16_len() };
return Ok(Some(JsValue::int(len as i32)));
}
let proto_ptr = self.session.builtin_world().string_proto.as_ptr() as *mut JsObject;