perf(css/parser): Improve performance of lexer (#4921)

This commit is contained in:
Alexander Akait 2022-06-11 17:47:08 +03:00 committed by GitHub
parent e25b6ed63c
commit 327969d0d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 19298 additions and 450 deletions

2
Cargo.lock generated
View File

@ -3225,6 +3225,7 @@ name = "swc_css_parser"
version = "0.101.4"
dependencies = [
"bitflags",
"criterion",
"lexical",
"serde",
"serde_json",
@ -3232,6 +3233,7 @@ dependencies = [
"swc_common",
"swc_css_ast",
"swc_css_visit",
"swc_node_base",
"testing",
]

View File

@ -23,7 +23,21 @@ swc_common = { version = "0.18.0", path = "../swc_common" }
swc_css_ast = { version = "0.93.0", path = "../swc_css_ast" }
[dev-dependencies]
criterion = "0.3"
serde = "1.0.127"
serde_json = "1.0.66"
swc_css_visit = { version = "0.92.0", path = "../swc_css_visit" }
swc_node_base = { version = "0.5.0", path = "../swc_node_base" }
testing = { version = "0.20.0", path = "../testing" }
[[bench]]
harness = false
name = "compare"
[[bench]]
harness = false
name = "lexer"
[[bench]]
harness = false
name = "parser"

View File

@ -0,0 +1,102 @@
extern crate swc_node_base;
use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion};
use swc_common::{input::StringInput, FileName, Span, SyntaxContext, DUMMY_SP};
use swc_css_ast::Stylesheet;
use swc_css_parser::{lexer::Lexer, parser::Parser};
use swc_css_visit::{Fold, FoldWith, VisitMut, VisitMutWith};
static SOURCE: &str = include_str!("files/bootstrap_5_1_3.css");
fn run<F>(b: &mut Bencher, mut op: F)
where
F: FnMut(Stylesheet) -> Stylesheet,
{
let _ = ::testing::run_test(false, |cm, _| {
let fm = cm.new_source_file(FileName::Anon, SOURCE.into());
let lexer = Lexer::new(StringInput::from(&*fm), Default::default());
let mut parser = Parser::new(lexer, Default::default());
let stylesheet: Stylesheet = parser.parse_all().unwrap();
b.iter(|| {
let stylesheet = stylesheet.clone();
let stylesheet = op(stylesheet);
black_box(stylesheet)
});
Ok(())
});
}
fn bench_cases(c: &mut Criterion) {
c.bench_function("css/visitor/compare/clone", |b| run(b, |m: Stylesheet| m));
c.bench_function("css/visitor/compare/visit_mut_span", |b| {
struct RespanVisitMut;
impl VisitMut for RespanVisitMut {
fn visit_mut_span(&mut self, span: &mut Span) {
*span = DUMMY_SP;
}
}
run(b, |mut m| {
m.visit_mut_with(&mut RespanVisitMut);
m
});
});
c.bench_function("css/visitor/compare/visit_mut_span_panic", |b| {
struct RespanVisitMut;
impl VisitMut for RespanVisitMut {
fn visit_mut_span(&mut self, span: &mut Span) {
if span.ctxt != SyntaxContext::empty() {
panic!()
}
*span = DUMMY_SP;
}
}
run(b, |mut m| {
m.visit_mut_with(&mut RespanVisitMut);
m
});
});
c.bench_function("css/visitor/compare/fold_span", |b| {
struct RespanFold;
impl Fold for RespanFold {
fn fold_span(&mut self, _: Span) -> Span {
DUMMY_SP
}
}
run(b, |m| m.fold_with(&mut RespanFold));
});
c.bench_function("css/visitor/compare/fold_span_panic", |b| {
struct RespanFold;
impl Fold for RespanFold {
fn fold_span(&mut self, s: Span) -> Span {
if s.ctxt != SyntaxContext::empty() {
panic!()
}
DUMMY_SP
}
}
run(b, |m| m.fold_with(&mut RespanFold));
});
}
criterion_group!(benches, bench_cases);
criterion_main!(benches);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
extern crate swc_node_base;
use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion};
use swc_common::{input::StringInput, FileName};
use swc_css_parser::lexer::Lexer;
fn bench_stylesheet(b: &mut Bencher, src: &'static str) {
let _ = ::testing::run_test(false, |cm, _| {
let fm = cm.new_source_file(FileName::Anon, src.into());
b.iter(|| {
let lexer = Lexer::new(StringInput::from(&*fm), Default::default());
for t in lexer {
black_box(t);
}
});
Ok(())
});
}
fn bench_files(c: &mut Criterion) {
c.bench_function("css/lexer/bootstrap_5_1_3", |b| {
bench_stylesheet(b, include_str!("./files/bootstrap_5_1_3.css"))
});
c.bench_function("css/lexer/foundation_6_7_4", |b| {
bench_stylesheet(b, include_str!("./files/foundation_6_7_4.css"))
});
c.bench_function("css/lexer/tailwind_3_1_1", |b| {
bench_stylesheet(b, include_str!("./files/tailwind_3_1_1.css"))
});
}
criterion_group!(benches, bench_files);
criterion_main!(benches);

View File

@ -0,0 +1,39 @@
extern crate swc_node_base;
use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion};
use swc_common::{input::StringInput, FileName};
use swc_css_parser::{lexer::Lexer, parser::Parser};
fn bench_stylesheet(b: &mut Bencher, src: &'static str) {
let _ = ::testing::run_test(false, |cm, _| {
let fm = cm.new_source_file(FileName::Anon, src.into());
b.iter(|| {
let _ = black_box({
let lexer = Lexer::new(StringInput::from(&*fm), Default::default());
let mut parser = Parser::new(lexer, Default::default());
parser.parse_all()
});
});
Ok(())
});
}
fn bench_files(c: &mut Criterion) {
c.bench_function("css/parser/bootstrap_5_1_3", |b| {
bench_stylesheet(b, include_str!("./files/bootstrap_5_1_3.css"))
});
c.bench_function("css/parser/foundation_6_7_4", |b| {
bench_stylesheet(b, include_str!("./files/foundation_6_7_4.css"))
});
c.bench_function("css/parser/tailwind_3_1_1", |b| {
bench_stylesheet(b, include_str!("./files/tailwind_3_1_1.css"))
});
}
criterion_group!(benches, bench_files);
criterion_main!(benches);

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ use swc_css_ast::{Token, TokenAndSpan, Tokens};
use super::PResult;
use crate::error::{Error, ErrorKind};
pub trait ParserInput {
pub trait ParserInput: Iterator<Item = TokenAndSpan> {
type State: Debug;
fn next(&mut self) -> PResult<TokenAndSpan>;
@ -73,7 +73,7 @@ where
self.cur()?;
if self.peeked.is_none() {
self.peeked = Some(self.input.next()?);
self.peeked = Some(ParserInput::next(&mut self.input)?);
}
Ok(self.peeked.as_ref().map(|v| &v.token))
@ -109,7 +109,7 @@ where
}
if self.cur.is_none() {
let res = self.input.next();
let res = ParserInput::next(&mut self.input);
if let Err(err) = &res {
if let ErrorKind::Eof = err.kind() {
@ -159,6 +159,7 @@ pub struct TokensState {
idx: usize,
}
#[derive(Debug)]
pub struct TokensInput<'a> {
tokens: &'a Tokens,
idx: usize,
@ -184,7 +185,7 @@ impl<'a> TokensInput<'a> {
}
}
impl ParserInput for TokensInput<'_> {
impl<'a> ParserInput for TokensInput<'a> {
type State = TokensState;
fn next(&mut self) -> PResult<TokenAndSpan> {
@ -206,3 +207,20 @@ impl ParserInput for TokensInput<'_> {
self.idx = state.idx;
}
}
impl<'a> Iterator for TokensInput<'a> {
type Item = TokenAndSpan;
fn next(&mut self) -> Option<Self::Item> {
let token_and_span = ParserInput::next(self);
match token_and_span {
Ok(token_and_span) => {
return Some(token_and_span);
}
Err(..) => {
return None;
}
}
}
}

View File

@ -27,7 +27,7 @@ fn tokens_input(input: PathBuf) {
let mut tokens = vec![];
while let Ok(t) = lexer.next() {
while let Ok(t) = ParserInput::next(&mut lexer) {
tokens.push(t);
}
Tokens {
@ -85,7 +85,8 @@ fn test_pass(input: PathBuf, config: ParserConfig) {
};
loop {
let res = lexer.next();
let res = ParserInput::next(&mut lexer);
match res {
Ok(t) => {
tokens.tokens.push(t);
@ -196,7 +197,8 @@ fn recovery(input: PathBuf) {
};
loop {
let res = lexer.next();
let res = ParserInput::next(&mut lexer);
match res {
Ok(t) => {
tokens.tokens.push(t);