Skip to content

Commit 600d07a

Browse files
committed
Optimizations
1 parent 2bba6bb commit 600d07a

File tree

6 files changed

+154
-83
lines changed

6 files changed

+154
-83
lines changed

bench/benches/bench.rs

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ use url::Url;
1010

1111
criterion_group!(
1212
benches,
13-
bench_parse,
14-
bench_parse_iref,
15-
bench_parse_iri_string,
16-
bench_parse_oxiri,
17-
bench_parse_url,
13+
bench_parse_uri,
14+
bench_parse_uri_iref,
15+
bench_parse_uri_iri_string,
16+
bench_parse_iri,
17+
bench_parse_iri_iref,
18+
bench_parse_iri_iri_string,
19+
bench_parse_iri_oxiri,
20+
bench_parse_iri_url,
1821
bench_build,
1922
bench_build_iri_string,
2023
bench_normalize,
@@ -24,36 +27,57 @@ criterion_group!(
2427
);
2528
criterion_main!(benches);
2629

27-
const PARSE_CASE: &str = "https://user@example.com/search?q=%E6%B5%8B%E8%AF%95#fragment";
30+
const PARSE_URI_CASE: &str = "https://user@example.com/search?q=%E6%B5%8B%E8%AF%95#fragment";
31+
const PARSE_IRI_CASE: &str = "https://用户@测试.com/search?q=我们测试解析IRI#fragment";
2832
const NORMALIZE_CASE: &str = "eXAMPLE://a/./b/../b/%63/%7bfoo%7d";
2933
const RESOLVE_CASE_BASE: &str = "http://example.com/foo/bar/baz/quz";
3034
const RESOLVE_CASE_REF: &str = "../../../qux/./quux/../corge";
3135

32-
fn bench_parse(c: &mut Criterion) {
33-
c.bench_function("parse", |b| b.iter(|| Iri::parse(black_box(PARSE_CASE))));
36+
fn bench_parse_uri(c: &mut Criterion) {
37+
c.bench_function("parse_uri", |b| {
38+
b.iter(|| Uri::parse(black_box(PARSE_URI_CASE)))
39+
});
40+
}
41+
42+
fn bench_parse_uri_iref(c: &mut Criterion) {
43+
c.bench_function("parse_uri_iref", |b| {
44+
b.iter(|| iref::Uri::new(black_box(PARSE_URI_CASE)))
45+
});
46+
}
47+
48+
fn bench_parse_uri_iri_string(c: &mut Criterion) {
49+
c.bench_function("parse_uri_iri_string", |b| {
50+
b.iter(|| UriStr::new(black_box(PARSE_URI_CASE)))
51+
});
52+
}
53+
54+
fn bench_parse_iri(c: &mut Criterion) {
55+
c.bench_function("parse_iri", |b| {
56+
b.iter(|| Iri::parse(black_box(PARSE_IRI_CASE)))
57+
});
3458
}
3559

36-
fn bench_parse_iref(c: &mut Criterion) {
37-
c.bench_function("parse_iref", |b| {
38-
b.iter(|| iref::Iri::new(black_box(PARSE_CASE)))
60+
fn bench_parse_iri_iref(c: &mut Criterion) {
61+
c.bench_function("parse_iri_iref", |b| {
62+
b.iter(|| iref::Iri::new(black_box(PARSE_IRI_CASE)))
3963
});
4064
}
4165

42-
fn bench_parse_iri_string(c: &mut Criterion) {
43-
c.bench_function("parse_iri_string", |b| {
44-
b.iter(|| IriStr::new(black_box(PARSE_CASE)))
66+
fn bench_parse_iri_iri_string(c: &mut Criterion) {
67+
c.bench_function("parse_iri_iri_string", |b| {
68+
b.iter(|| IriStr::new(black_box(PARSE_IRI_CASE)))
4569
});
4670
}
4771

48-
fn bench_parse_oxiri(c: &mut Criterion) {
49-
c.bench_function("parse_oxiri", |b| {
50-
b.iter(|| oxiri::Iri::parse(black_box(PARSE_CASE)))
72+
fn bench_parse_iri_oxiri(c: &mut Criterion) {
73+
c.bench_function("parse_iri_oxiri", |b| {
74+
b.iter(|| oxiri::Iri::parse(black_box(PARSE_IRI_CASE)))
5175
});
5276
}
5377

54-
fn bench_parse_url(c: &mut Criterion) {
55-
c.bench_function("parse_url", |b| {
56-
b.iter(|| Url::parse(black_box(PARSE_CASE)))
78+
fn bench_parse_iri_url(c: &mut Criterion) {
79+
c.bench_function("parse_iri_url", |b| {
80+
b.iter(|| Url::parse(black_box(PARSE_IRI_CASE)))
5781
});
5882
}
5983

bench/result.txt

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
Environment: Intel Core i5-11300H, Rust 1.91.0 stable
22

3-
parse time: [88.159 ns 88.572 ns 89.042 ns]
4-
parse_iref time: [141.80 ns 142.43 ns 143.17 ns]
5-
parse_iri_string time: [128.93 ns 129.82 ns 130.76 ns]
6-
parse_oxiri time: [118.65 ns 119.35 ns 120.21 ns]
7-
parse_url time: [311.78 ns 313.83 ns 316.09 ns]
8-
build time: [197.67 ns 198.54 ns 199.76 ns]
9-
build_iri_string time: [371.53 ns 373.18 ns 375.07 ns]
10-
normalize time: [95.095 ns 96.186 ns 97.330 ns]
11-
normalize_iri_string time: [502.08 ns 504.09 ns 506.43 ns]
12-
resolve time: [94.375 ns 94.987 ns 95.600 ns]
13-
resolve_iri_string time: [430.86 ns 432.78 ns 434.91 ns]
3+
parse_uri time: [51.437 ns 51.666 ns 51.958 ns]
4+
parse_uri_iref time: [107.81 ns 108.35 ns 108.90 ns]
5+
parse_uri_iri_string time: [138.03 ns 139.33 ns 140.78 ns]
6+
parse_iri time: [71.304 ns 71.657 ns 72.126 ns]
7+
parse_iri_iref time: [120.92 ns 121.48 ns 122.14 ns]
8+
parse_iri_iri_string time: [148.71 ns 150.10 ns 151.56 ns]
9+
parse_iri_oxiri time: [111.30 ns 112.06 ns 112.85 ns]
10+
parse_iri_url time: [601.13 ns 604.75 ns 608.75 ns]
11+
build time: [197.04 ns 198.23 ns 199.63 ns]
12+
build_iri_string time: [378.62 ns 380.27 ns 382.36 ns]
13+
normalize time: [100.78 ns 101.90 ns 103.05 ns]
14+
normalize_iri_string time: [511.76 ns 513.77 ns 516.05 ns]
15+
resolve time: [99.610 ns 100.19 ns 100.81 ns]
16+
resolve_iri_string time: [431.93 ns 433.37 ns 435.00 ns]

src/parse.rs

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
22
imp::{AuthMeta, Constraints, HostMeta, Meta},
3-
pct_enc::{table::*, Table},
3+
pct_enc::{self, table::*, Table},
44
utf8,
55
};
66
use core::{
@@ -165,33 +165,50 @@ impl<'a> Reader<'a> {
165165

166166
fn read_with(&mut self, table: Table, mut f: impl FnMut(usize, u32)) -> Result<()> {
167167
let mut i = self.pos;
168-
let allow_pct_encoded = table.allows_pct_encoded();
169-
let allow_non_ascii = table.allows_non_ascii();
170-
171-
while i < self.len() {
172-
let x = self.bytes[i];
173-
if allow_pct_encoded && x == b'%' {
174-
let [hi, lo, ..] = self.bytes[i + 1..] else {
175-
err!(i, InvalidPctEncodedOctet);
176-
};
177-
if !(hi.is_ascii_hexdigit() && lo.is_ascii_hexdigit()) {
178-
err!(i, InvalidPctEncodedOctet);
179-
}
180-
i += 3;
181-
} else if allow_non_ascii {
182-
let (x, len) = utf8::next_code_point(self.bytes, i);
183-
if !table.allows_code_point(x) {
184-
break;
168+
169+
macro_rules! do_loop {
170+
($allow_pct_encoded:expr, $allow_non_ascii:expr) => {
171+
while i < self.len() {
172+
let x = self.bytes[i];
173+
if $allow_pct_encoded && x == b'%' {
174+
let [hi, lo, ..] = self.bytes[i + 1..] else {
175+
err!(i, InvalidPctEncodedOctet);
176+
};
177+
if !pct_enc::is_valid_octet(hi, lo) {
178+
err!(i, InvalidPctEncodedOctet);
179+
}
180+
i += 3;
181+
} else if $allow_non_ascii {
182+
let (x, len) = utf8::next_code_point(self.bytes, i);
183+
if !table.allows_code_point(x) {
184+
break;
185+
}
186+
f(i, x);
187+
i += len;
188+
} else {
189+
if !table.allows_ascii(x) {
190+
break;
191+
}
192+
f(i, x as u32);
193+
i += 1;
194+
}
185195
}
186-
f(i, x);
187-
i += len;
196+
};
197+
}
198+
199+
// This expansion alone doesn't help much, but combined with
200+
// `#[inline(always)]` on `utf8::next_code_point`,
201+
// it improves performance significantly for non-ASCII case.
202+
if table.allows_pct_encoded() {
203+
if table.allows_non_ascii() {
204+
do_loop!(true, true);
188205
} else {
189-
if !table.allows_ascii(x) {
190-
break;
191-
}
192-
f(i, x as u32);
193-
i += 1;
206+
do_loop!(true, false);
194207
}
208+
} else if table.allows_non_ascii() {
209+
do_loop!(false, true);
210+
} else {
211+
do_loop!(false, false);
195212
}
196213

197214
// INVARIANT: `i` is non-decreasing.
@@ -274,7 +291,7 @@ impl<'a> Reader<'a> {
274291
}
275292

276293
let first = self.peek(0).unwrap();
277-
let mut x = match (first as char).to_digit(16) {
294+
let mut x = match pct_enc::decode_hexdigit(first) {
278295
Some(v) => v as u16,
279296
_ => {
280297
return colon.then(|| {
@@ -296,7 +313,7 @@ impl<'a> Reader<'a> {
296313
self.skip(i);
297314
return None;
298315
};
299-
match (b as char).to_digit(16) {
316+
match pct_enc::decode_hexdigit(b) {
300317
Some(v) => {
301318
x = (x << 4) | v as u16;
302319
i += 1;

src/pct_enc/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,14 @@ fn decode_octet(hi: u8, lo: u8) -> u8 {
487487
OCTET_TABLE_HI[hi as usize] | OCTET_TABLE_LO[lo as usize]
488488
}
489489

490+
pub(crate) fn decode_hexdigit(x: u8) -> Option<u8> {
491+
Some(OCTET_TABLE_LO[x as usize]).filter(|&v| v < 128)
492+
}
493+
494+
pub(crate) const fn is_valid_octet(hi: u8, lo: u8) -> bool {
495+
OCTET_TABLE_LO[hi as usize] | OCTET_TABLE_LO[lo as usize] < 128
496+
}
497+
490498
/// An iterator used to decode an [`EStr`] slice.
491499
///
492500
/// This struct is created by [`EStr::decode`]. Normally you'll use the methods below

src/pct_enc/table.rs

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//!
66
//! [RFC 5234]: https://datatracker.ietf.org/doc/html/rfc5234
77
8-
use crate::utf8;
8+
use crate::{pct_enc, utf8};
99

1010
const MASK_PCT_ENCODED: u64 = 1;
1111
const MASK_UCSCHAR: u64 = 2;
@@ -143,34 +143,52 @@ impl Table {
143143
/// Validates the given string with the table.
144144
pub(crate) const fn validate(self, s: &[u8]) -> bool {
145145
let mut i = 0;
146-
let allow_pct_encoded = self.allows_pct_encoded();
147-
let allow_non_ascii = self.allows_non_ascii();
148-
149-
while i < s.len() {
150-
let x = s[i];
151-
if allow_pct_encoded && x == b'%' {
152-
if i + 2 >= s.len() {
153-
return false;
154-
}
155-
let (hi, lo) = (s[i + 1], s[i + 2]);
156146

157-
if !(hi.is_ascii_hexdigit() && lo.is_ascii_hexdigit()) {
158-
return false;
159-
}
160-
i += 3;
161-
} else if allow_non_ascii {
162-
let (x, len) = utf8::next_code_point(s, i);
163-
if !self.allows_code_point(x) {
164-
return false;
147+
macro_rules! do_loop {
148+
($allow_pct_encoded:expr, $allow_non_ascii:expr) => {
149+
while i < s.len() {
150+
let x = s[i];
151+
if $allow_pct_encoded && x == b'%' {
152+
if i + 2 >= s.len() {
153+
return false;
154+
}
155+
let (hi, lo) = (s[i + 1], s[i + 2]);
156+
157+
if !pct_enc::is_valid_octet(hi, lo) {
158+
return false;
159+
}
160+
i += 3;
161+
} else if $allow_non_ascii {
162+
let (x, len) = utf8::next_code_point(s, i);
163+
if !self.allows_code_point(x) {
164+
return false;
165+
}
166+
i += len;
167+
} else {
168+
if !self.allows_ascii(x) {
169+
return false;
170+
}
171+
i += 1;
172+
}
165173
}
166-
i += len;
174+
};
175+
}
176+
177+
// This expansion alone doesn't help much, but combined with
178+
// `#[inline(always)]` on `utf8::next_code_point`,
179+
// it improves performance significantly for non-ASCII case.
180+
if self.allows_pct_encoded() {
181+
if self.allows_non_ascii() {
182+
do_loop!(true, true);
167183
} else {
168-
if !self.allows_ascii(x) {
169-
return false;
170-
}
171-
i += 1;
184+
do_loop!(true, false);
172185
}
186+
} else if self.allows_non_ascii() {
187+
do_loop!(false, true);
188+
} else {
189+
do_loop!(false, false);
173190
}
191+
174192
true
175193
}
176194
}

src/utf8.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const fn utf8_acc_cont_byte(ch: u32, byte: u8) -> u32 {
1010
(ch << 6) | (byte & CONT_MASK) as u32
1111
}
1212

13-
#[inline]
13+
// Make sure it's inlined into `Parser::read_with`.
14+
#[inline(always)]
1415
pub const fn next_code_point(bytes: &[u8], i: usize) -> (u32, usize) {
1516
let x = bytes[i];
1617
if x < 128 {

0 commit comments

Comments
 (0)