Skip to content

Commit a77bf8c

Browse files
committed
perf: Faster class name splitting
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
1 parent 9c06266 commit a77bf8c

1 file changed

Lines changed: 17 additions & 8 deletions

File tree

css-inline/src/html/attributes.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ pub(crate) struct Class {
1010
pub(crate) cache: Cache,
1111
}
1212

13-
static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C'];
13+
/// Bit-packed lookup for CSS selector whitespace: ' ', '\t', '\n', '\r', '\x0C'
14+
/// All whitespace chars are <= 32, so we pack them into a single u64.
15+
const WHITESPACE_MASK: u64 =
16+
(1 << b' ') | (1 << b'\t') | (1 << b'\n') | (1 << b'\r') | (1 << 0x0Cu8);
17+
18+
#[inline]
19+
fn is_selector_whitespace(b: u8) -> bool {
20+
b <= 32 && (WHITESPACE_MASK >> b) & 1 != 0
21+
}
1422

1523
/// This Bloom filter has 64 bits of storage and two hash functions which gives a decent false
1624
/// positive rate given that most of the time an element has a very few number of classes.
@@ -61,7 +69,10 @@ impl Class {
6169
pub(crate) fn new(value: StrTendril) -> Class {
6270
// Build a Bloom filter for all element's classes
6371
let mut cache = BloomFilter::new();
64-
let mut classes = value.split(SELECTOR_WHITESPACE).filter(|s| !s.is_empty());
72+
let bytes = value.as_bytes();
73+
let mut classes = bytes
74+
.split(|&b| is_selector_whitespace(b))
75+
.filter(|s| !s.is_empty());
6576
if let Some(class) = classes.next() {
6677
// If the first class we split is the same as the input value, then we have just
6778
// a single class and a Bloom filter is not needed.
@@ -71,12 +82,10 @@ impl Class {
7182
cache: Cache::Single,
7283
};
7384
}
74-
let hash = hash_class_name(class.as_bytes());
75-
cache.insert_hash(hash);
85+
cache.insert_hash(hash_class_name(class));
7686
}
7787
for class in classes {
78-
let hash = hash_class_name(class.as_bytes());
79-
cache.insert_hash(hash);
88+
cache.insert_hash(hash_class_name(class));
8089
}
8190
Class {
8291
value,
@@ -87,8 +96,8 @@ impl Class {
8796
/// Manually check whether the class attribute value contains the given class.
8897
#[inline]
8998
fn has_class_impl(&self, name: &[u8], case_sensitivity: CaseSensitivity) -> bool {
90-
for class in self.value.split(SELECTOR_WHITESPACE) {
91-
if case_sensitivity.eq(class.as_bytes(), name) {
99+
for class in self.value.as_bytes().split(|&b| is_selector_whitespace(b)) {
100+
if case_sensitivity.eq(class, name) {
92101
return true;
93102
}
94103
}

0 commit comments

Comments
 (0)