mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
perf(parser): use a stack buffer for float separators (#28435)
Avoid temporary heap strings for floats such as `1_000.5` with a 64-byte stack buffer, keeping the existing fallback for longer literals. Local arm64 benchmarks: 26–57% less parsing time on short separated-float fixtures; ordinary corpus +0.35% without tokens / −0.25% with tokens. Integer-only token controls are ~4% slower. Parser/Node checks pass, conformance is unchanged, and 58,500 differential comparisons match. AI assistance: Codex.
This commit is contained in:
1 parent
6e35e3c268
commit
5238839466
2 files changed
+48
-4
No files matched your search
@@ -44,11 +44,35 @@ pub fn parse_int(s: &str, kind: Kind, has_sep: bool) -> Result<f64, &'static str
|
||||
}
|
||||
|
||||
pub fn parse_float(s: &str, has_sep: bool) -> Result<f64, &'static str> {
|
||||
let s = if has_sep { s.cow_replace('_', "") } else { Cow::Borrowed(s) };
|
||||
if has_sep {
|
||||
return parse_float_with_underscores(s);
|
||||
}
|
||||
debug_assert!(!s.contains('_'));
|
||||
s.parse::<f64>().map_err(|_| "invalid float")
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn parse_float_with_underscores(s: &str) -> Result<f64, &'static str> {
|
||||
// Common numeric literals fit in a small scratch buffer. Keep the heap fallback
|
||||
// for arbitrarily long literals without allocating for every separated literal.
|
||||
const CAPACITY: usize = 64;
|
||||
if s.len() > CAPACITY {
|
||||
return s.cow_replace('_', "").parse::<f64>().map_err(|_| "invalid float");
|
||||
}
|
||||
|
||||
let mut buffer = [0; CAPACITY];
|
||||
let mut len = 0;
|
||||
for byte in s.bytes() {
|
||||
if byte != b'_' {
|
||||
buffer[len] = byte;
|
||||
len += 1;
|
||||
}
|
||||
}
|
||||
// Removing an ASCII character preserves UTF-8.
|
||||
let s = std::str::from_utf8(&buffer[..len]).unwrap();
|
||||
s.parse::<f64>().map_err(|_| "invalid float")
|
||||
}
|
||||
|
||||
// ==================================== DECIMAL ====================================
|
||||
|
||||
/// b'0' is 0x30 and b'9' is 0x39.
|
||||
@@ -611,6 +635,26 @@ mod test {
|
||||
assert_all_floats_eq(cases, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal_separators_rounding_and_buffer_boundary() {
|
||||
for (literal, bits) in [
|
||||
("1.0000000000000001110223024625156540423631668090820312_5", 0x3ff0_0000_0000_0000),
|
||||
("1.0000000000000001110223024625156540423631668090820312_6", 0x3ff0_0000_0000_0001),
|
||||
("1.797_693_134_862_315_7e308", 0x7fef_ffff_ffff_ffff),
|
||||
("1.797_693_134_862_315_9e308", 0x7ff0_0000_0000_0000),
|
||||
("4.940_656_458_412_465_4e-324", 1),
|
||||
("2.470_328_229_206_232_7e-324", 0),
|
||||
("2.470_328_229_206_232_8e-324", 1),
|
||||
] {
|
||||
assert_eq!(parse_float(literal, true).unwrap().to_bits(), bits, "{literal}");
|
||||
}
|
||||
for len in [63, 64, 65, 128] {
|
||||
let literal = format!("1.5_{}", "0".repeat(len - 4));
|
||||
assert_eq!(parse_float(&literal, true), Ok(1.5), "{literal}");
|
||||
}
|
||||
assert_eq!(parse_float("1_µ.5", true), Err("invalid float"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_int_no_sep() {
|
||||
let decimal: Vec<(&str, i64)> = vec![
|
||||
|
||||
@@ -66,9 +66,9 @@ binder.ts:
|
||||
|
||||
kitchen-sink.tsx:
|
||||
file size: 732898 # 732.90 kB
|
||||
sys allocs: 183
|
||||
sys reallocs: 160
|
||||
sys deallocs: 183
|
||||
sys allocs: 182
|
||||
sys reallocs: 159
|
||||
sys deallocs: 182
|
||||
sys alloc bytes: 34053 # 34.05 kB
|
||||
sys peak growth: 3892 # 3.89 kB
|
||||
arena allocs: 135741
|
||||
|
||||
Reference in new issue
Block a user