Skip to content

Commit

Permalink
Improved performance for accept if, accept char, and accept char any
Browse files Browse the repository at this point in the history
  • Loading branch information
vallentin committed Jul 10, 2023
1 parent d2476e6 commit 425c714
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 12 deletions.
4 changes: 4 additions & 0 deletions text-scanner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ char-ranges = "0.1"
[dev-dependencies]
criterion = "0.5"

[[bench]]
name = "accept_impl"
harness = false

[[bench]]
name = "accept_vs_test"
harness = false
184 changes: 184 additions & 0 deletions text-scanner/benches/accept_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
use std::hint::black_box;

use criterion::{criterion_group, criterion_main, Criterion};
use text_scanner::{Scanner, ScannerItem, ScannerResult};

trait ScannerFuncs<'text> {
fn accept_if<F>(&mut self, f: F) -> ScannerResult<'text, char>
where
F: FnOnce(char) -> bool;

#[inline]
fn accept_char(&mut self, expected: char) -> ScannerResult<'text, char> {
self.accept_if(|c| c == expected)
}

#[inline]
fn accept_char_any(&mut self, expected: &[char]) -> ScannerResult<'text, char> {
self.accept_if(|c| expected.contains(&c))
}
}

impl<'text> ScannerFuncs<'text> for Scanner<'text> {
#[inline]
fn accept_if<F>(&mut self, f: F) -> ScannerResult<'text, char>
where
F: FnOnce(char) -> bool,
{
Scanner::accept_if(self, f)
}

#[inline]
fn accept_char(&mut self, expected: char) -> ScannerResult<'text, char> {
Scanner::accept_char(self, expected)
}

#[inline]
fn accept_char_any(&mut self, expected: &[char]) -> ScannerResult<'text, char> {
Scanner::accept_char_any(self, expected)
}
}

#[repr(transparent)]
struct ScannerPeek<'text>(Scanner<'text>);

impl<'text> ScannerFuncs<'text> for ScannerPeek<'text> {
#[inline]
fn accept_if<F>(&mut self, f: F) -> ScannerResult<'text, char>
where
F: FnOnce(char) -> bool,
{
let (r, c) = self.0.peek()?;
if f(c) {
self.0.set_cursor_pos(r.end);
Ok((r, c))
} else {
let cursor = self.0.cursor_pos();
Err((cursor..cursor, ""))
}
}
}

#[repr(transparent)]
struct ScannerPattern<'text>(Scanner<'text>);

impl<'text> ScannerPattern<'text> {
#[inline]
fn test<T, F>(&mut self, f: F) -> ScannerResult<'text, T>
where
F: FnOnce(&Scanner<'text>) -> Option<(usize, T)>,
{
match f(&self.0) {
Some((len_utf8, c)) => {
let start = self.0.cursor_pos();
let end = start + len_utf8;
self.0.set_cursor_pos(end);
Ok((start..end, c))
}
None => {
let cursor = self.0.cursor_pos();
Err((cursor..cursor, ""))
}
}
}
}

impl<'text> ScannerFuncs<'text> for ScannerPattern<'text> {
#[inline]
fn accept_if<F>(&mut self, f: F) -> ScannerResult<'text, char>
where
F: FnOnce(char) -> bool,
{
self.test(|scanner| {
let c = scanner.remaining_text().chars().next();
match c {
Some(c) if f(c) => Some((c.len_utf8(), c)),
_ => None,
}
})
}

#[inline]
fn accept_char(&mut self, expected: char) -> ScannerResult<'text, char> {
self.test(|scanner| {
scanner
.remaining_text()
.starts_with(expected)
.then(|| (expected.len_utf8(), expected))
})
}
}

#[inline]
fn test_accept_if<'text, S: ScannerFuncs<'text>>(scanner: &mut S) -> ScannerItem<char> {
scanner
.accept_if(black_box(|c: char| c.is_alphanumeric() || (c == '/')))
.unwrap()
}

#[inline]
fn test_accept_char<'text, S: ScannerFuncs<'text>>(
scanner: &mut S,
) -> (ScannerItem<char>, ScannerItem<char>) {
(
scanner.accept_char(black_box('/')).unwrap(),
scanner.accept_char(black_box('/')).unwrap(),
)
}

#[inline]
fn test_accept_char_any<'text, S: ScannerFuncs<'text>>(
scanner: &mut S,
) -> (ScannerItem<char>, ScannerItem<char>) {
(
scanner
.accept_char_any(&black_box(['f', 'o', 'o', 'b', 'a', 'r', '/']))
.unwrap(),
scanner
.accept_char_any(&black_box(['f', 'o', 'o', 'b', 'a', 'r', '/']))
.unwrap(),
)
}

fn bench_accept_impl(c: &mut Criterion) {
let scanner = Scanner::new(black_box("// Hello World"));

let mut group = c.benchmark_group("accept_if");
group.bench_function("peek", |b| {
b.iter(|| test_accept_if(&mut ScannerPeek(scanner.clone())));
});
group.bench_function("pattern", |b| {
b.iter(|| test_accept_if(&mut ScannerPattern(scanner.clone())));
});
group.bench_function("current", |b| {
b.iter(|| test_accept_if(&mut scanner.clone()));
});
group.finish();

let mut group = c.benchmark_group("accept_char");
group.bench_function("peek", |b| {
b.iter(|| test_accept_char(&mut ScannerPeek(scanner.clone())));
});
group.bench_function("pattern", |b| {
b.iter(|| test_accept_char(&mut ScannerPattern(scanner.clone())));
});
group.bench_function("current", |b| {
b.iter(|| test_accept_char(&mut scanner.clone()));
});
group.finish();

let mut group = c.benchmark_group("accept_char_any");
group.bench_function("peek", |b| {
b.iter(|| test_accept_char_any(&mut ScannerPeek(scanner.clone())));
});
group.bench_function("pattern", |b| {
b.iter(|| test_accept_char_any(&mut ScannerPattern(scanner.clone())));
});
group.bench_function("current", |b| {
b.iter(|| test_accept_char_any(&mut scanner.clone()));
});
group.finish();
}

criterion_group!(benches, bench_accept_impl);
criterion_main!(benches);
12 changes: 7 additions & 5 deletions text-scanner/benches/accept_vs_test.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
use std::hint::black_box;

use criterion::{criterion_group, criterion_main, Criterion};
use text_scanner::{Scanner, ScannerItem};

fn bench_accept_vs_test(c: &mut Criterion) {
let scanner = Scanner::new("// Hello World");
let scanner = Scanner::new(black_box("// Hello World"));

let mut group = c.benchmark_group("str");
group.bench_function("accept_str", |b| {
b.iter(|| -> ScannerItem<&'_ str> {
let mut scanner = scanner.clone();
scanner.accept_str("//").unwrap()
scanner.accept_str(black_box("//")).unwrap()
});
});
group.bench_function("test_str", |b| {
b.iter(|| -> ScannerItem<&'_ str> {
let mut scanner = scanner.clone();
scanner.test_str("//").unwrap()
scanner.test_str(black_box("//")).unwrap()
});
});
group.finish();
Expand All @@ -24,15 +26,15 @@ fn bench_accept_vs_test(c: &mut Criterion) {
b.iter(|| -> ScannerItem<&'_ str> {
let mut scanner = scanner.clone();
scanner
.accept_str_any(&["// foo", "// bar", "// baz", "//"])
.accept_str_any(&black_box(["// foo", "// bar", "// baz", "//"]))
.unwrap()
});
});
group.bench_function("test_str_any", |b| {
b.iter(|| -> ScannerItem<&'_ str> {
let mut scanner = scanner.clone();
scanner
.test_str_any(&["// foo", "// bar", "// baz", "//"])
.test_str_any(&black_box(["// foo", "// bar", "// baz", "//"]))
.unwrap()
});
});
Expand Down
25 changes: 18 additions & 7 deletions text-scanner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,14 @@ impl<'text> Scanner<'text> {
} else {
Err((self.cursor..self.cursor, ""))
}

// self.test(|scanner| {
// let c = scanner.remaining_text().chars().next();
// match c {
// Some(c) if f(c) => Some((c.len_utf8(), c)),
// _ => None,
// }
// })
}

#[allow(dead_code)]
Expand Down Expand Up @@ -791,6 +799,13 @@ impl<'text> Scanner<'text> {
#[inline]
pub fn accept_char(&mut self, expected: char) -> ScannerResult<'text, char> {
self.accept_if(|c| c == expected)

// self.test(|scanner| {
// scanner
// .remaining_text()
// .starts_with(expected)
// .then(|| (expected.len_utf8(), expected))
// })
}

/// Advances the scanner cursor and returns the next
Expand Down Expand Up @@ -827,15 +842,11 @@ impl<'text> Scanner<'text> {
///
/// [cursor]: Self::cursor_pos
/// [empty]: https://doc.rust-lang.org/std/primitive.slice.html#method.is_empty
#[inline]
pub fn accept_char_any(&mut self, expected: &[char]) -> ScannerResult<'text, char> {
debug_assert!(!expected.is_empty(), "`expected` is empty");
let (r, c) = self.peek()?;
if expected.contains(&c) {
self.cursor = r.end;
Ok((r, c))
} else {
Err((self.cursor..self.cursor, ""))
}

self.accept_if(|c| expected.contains(&c))
}

/// Advances the scanner cursor and returns `Ok` with the `&'text str`
Expand Down

0 comments on commit 425c714

Please sign in to comment.