css parsing: performance tips & tricks
TRANSCRIPT
![Page 1: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/1.jpg)
CSS Parsing performance tips & tricks
Roman Dvornov Avito
Moscow, September 2016
![Page 2: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/2.jpg)
Frontend lead in Avito
Specializes in SPA
Maintainer of:basis.js, CSSO, component-inspector, csstree and others
![Page 3: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/3.jpg)
CSS parsing (russian)
3
tinyurl.com/csstree-intro
This talk is the continuation of
![Page 4: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/4.jpg)
CSSTree
![Page 5: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/5.jpg)
CSSTree – fastest detailed CSS parser
5
![Page 6: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/6.jpg)
How this project was born
![Page 7: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/7.jpg)
About a year ago I started to maintain CSSO
(a CSS minifier)
7
github.com/css/csso
![Page 9: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/9.jpg)
What's wrong with Gonzales• Development stopped in 2013
• Unhandy and buggy AST format
• Parsing mistakes
• Excessively complex code base
• Slow, high memory consumption, pressure for GC
9
![Page 10: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/10.jpg)
But I didn’t want to spend my time developing the
parser…
10
![Page 11: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/11.jpg)
Alternatives?
![Page 12: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/12.jpg)
You can find a lot of CSS parsers
12
![Page 13: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/13.jpg)
Common problems• Not developing currently
• Outdated (don't support latest CSS features)
• Buggy
• Unhandy AST
• Slow13
![Page 15: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/15.jpg)
PostCSS pros• Сonstantly developing
• Parses CSS well, even non-standard syntax + tolerant mode
• Saves formatting info
• Handy API to work with AST
• Fast15
![Page 16: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/16.jpg)
General con: selectors and values are not parsed
(are represented as strings)
16
![Page 17: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/17.jpg)
That forces developers to• Use non-robust or non-effective approaches
• Invent their own parsers
• Use additional parsers: postcss-selector-parser postcss-value-parser
17
![Page 18: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/18.jpg)
Switching to PostCSS meant writing our own selector and value parsers,
what is pretty much the same as writing an entirely new parser
18
![Page 19: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/19.jpg)
However, as a result of a continuous refactoring within a few months
the CSSO parser was completely rewrote (which was not planned)
19
![Page 20: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/20.jpg)
And was extracted to a separate project
github.com/csstree/csstree
20
![Page 21: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/21.jpg)
Performance
![Page 22: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/22.jpg)
CSSO – performance boost story (russian)
22
tinyurl.com/csso-speedup
My previous talk about parser performance
![Page 23: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/23.jpg)
After my talk on HolyJS conference the parser's
performance was improved one more time :)
23
* Thanks Vyacheslav @mraleph Egorov for inspiration
![Page 24: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/24.jpg)
24
CSSTree: 24 msMensch: 31 msCSSOM: 36 msPostCSS: 38 msRework: 81 msPostCSS Full: 100 msGonzales: 175 msStylecow: 176 msGonzales PE: 214 msParserLib: 414 ms
bootstrap.css v3.3.7 (146Kb)
github.com/postcss/benchmark
Non-detailed AST
Detailed AST
PostCSS Full = + postcss-selector-parser
+ postcss-value-parser
![Page 25: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/25.jpg)
Epic fail as I realised later I extracted
the wrong version of the parser
25
😱github.com/csstree/csstree/commit/57568c758195153e337f6154874c3bc42dd04450
![Page 26: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/26.jpg)
26
CSSTree: 24 msMensch: 31 msCSSOM: 36 msPostCSS: 38 msRework: 81 msPostCSS Full: 100 msGonzales: 175 msStylecow: 176 msGonzales PE: 214 msParserLib: 414 ms
bootstrap.css v3.3.7 (146Kb)
github.com/postcss/benchmark
Time after parser update
13 ms
![Page 27: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/27.jpg)
Parsers: basic training
![Page 28: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/28.jpg)
Main steps
• Tokenization
• Tree assembling
28
![Page 29: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/29.jpg)
Tokenization
![Page 30: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/30.jpg)
30
• whitespaces – [ \n\r\t\f]+ • keyword – [a-zA-aZ…]+ • number – [0-9]+ • string – "string" or 'string' • comment – /* comment */ • punctuation – [;,.#\{\}\[\]\(\)…]
Split text into tokens
![Page 31: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/31.jpg)
31
.foo { width: 10px;}
[ '.', 'foo', ' ', '{', '\n ', 'width', ':', ' ', '10', 'px', ';', '\n', '}']
![Page 32: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/32.jpg)
We need more info about every token: type and location
32
It is more efficient to compute type and location
on tokenization step
![Page 33: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/33.jpg)
33
.foo { width: 10px;}
[ { type: 'FullStop', value: '.', offset: 0, line: 1, column: 1 }, …]
![Page 34: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/34.jpg)
Tree assembling
![Page 35: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/35.jpg)
35
function getSelector() { var selector = { type: 'Selector', sequence: [] };
// main loop
return selector;}
Creating a node
![Page 36: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/36.jpg)
36
for (;currentToken < tokenCount; currentToken++) { switch (tokens[currentToken]) { case TokenType.Hash: // # selector.sequence.push(getId()); break; case TokenType.FullStop: // . selector.sequence.push(getClass()); break; … }
Main loop
![Page 37: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/37.jpg)
37
{ "type": "StyleSheet", "rules": [{ "type": "Atrule", "name": "import", "expression": { "type": "AtruleExpression", "sequence": [ ... ] }, "block": null }]}
Result
![Page 38: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/38.jpg)
Parser performance boost Part 2: new horizons
![Page 39: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/39.jpg)
39
[ { type: 'FullStop', value: '.', offset: 0, line: 1, column: 1 }, …]
Token's cost: 24 + 5 * 4 + array =
min 50 bytes per token
Our project ~1Mb CSS 254 062 tokens
= min 12.7 Mb
![Page 40: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/40.jpg)
Out of the box: changing approach
![Page 41: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/41.jpg)
Compute all tokens at once and then assembly a tree is much more easy, but needs more memory, therefore is
slower
41
![Page 42: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/42.jpg)
Scanner (lazy tokenizer)
42
![Page 43: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/43.jpg)
43
scanner.token // current token or nullscanner.next() // going to next tokenscanner.lookup(N) // look ahead, returns // Nth token from current token
Key API
![Page 44: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/44.jpg)
44
• lookup(N)fills tokens buffer up to N tokens (if they are not computed yet), returns N-1 token from buffer
• next()shift token from buffer, if any, or compute next token
![Page 45: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/45.jpg)
Computing the same number of tokens, but not simultaneously
and requires less memory
45
![Page 46: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/46.jpg)
Problem: the approach puts pressure on GC
46
![Page 47: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/47.jpg)
Reducing token's cost step by step
![Page 48: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/48.jpg)
48
[ { type: 'FullStop', value: '.', offset: 0, line: 1, column: 1 }, …]
Type as string is easy to understand, but it's for
internal use only and we can replace it by numbers
![Page 49: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/49.jpg)
49
[ { type: FULLSTOP, value: '.', offset: 0, line: 1, column: 1 }, …]
…// '.'.charCodeAt(0)var FULLSTOP = 46;…
![Page 50: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/50.jpg)
50
[ { type: 46, value: '.', offset: 0, line: 1, column: 1 }, …]
![Page 51: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/51.jpg)
51
[ { type: 46, value: '.', offset: 0, line: 1, column: 1 }, …]
We can avoid substring storage in the token – it's very
expensive for punctuation (moreover those substrings
are never used); Many constructions are assembled by several
substrings. One long substring is better than
a concat of several small ones
![Page 52: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/52.jpg)
52
[ { type: 46, value: '.', offset: 0, line: 1, column: 1 }, …]
[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]
![Page 53: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/53.jpg)
53
[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]
Look, Ma! No strings just numbers!
![Page 54: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/54.jpg)
54
Moreover not an Array, but TypedArray
Array of objects
Arraysof numbers
![Page 55: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/55.jpg)
Array vs. TypedArray• Can't have holes
• Faster in theory (less checking)
• Can be stored outside the heap (when big enough)
• Prefilled with zeros
55
![Page 56: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/56.jpg)
56
[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]
Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array
1 4 4 4 4
17 per token(tokens count) 254 062 x 17 = 4.3Mb
![Page 57: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/57.jpg)
4.3Mb vs. 12.7Mb (min)
57
![Page 58: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/58.jpg)
Houston we have a problem: TypedArray has a fixed length,
but we don't know how many tokens will be found
58
![Page 59: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/59.jpg)
59
[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]
Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array
1 4 4 4 4
17 per token(symbols count) 983 085 x 17 = 16.7Mb
![Page 60: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/60.jpg)
16.7Mb vs. 12.7Mb (min)
60
![Page 61: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/61.jpg)
16.7Mb vs. 12.7Mb (min)
60
Don't give up, let's look on arrays
more attentively
![Page 62: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/62.jpg)
61
start = [ 0, 5, 6, 7, 9, 11, …, 35 ]
end = [ 5, 6, 7, 9, 11, 12, …, 36 ]
![Page 63: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/63.jpg)
61
start = [ 0, 5, 6, 7, 9, 11, …, 35 ]
end = [ 5, 6, 7, 9, 11, 12, …, 36 ]
…
![Page 64: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/64.jpg)
62
start = [ 0, 5, 6, 7, 9, 11, …, 35 ]
end = [ 5, 6, 7, 9, 11, 12, …, 36 ]
offset = [ 0, 5, 6, 7, 9, 11, …, 35, 36 ] start = offset[i] end = offset[i + 1]
+
=
![Page 65: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/65.jpg)
63
[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]
Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array
1 4 4 4 4
13 per token983 085 x 13 = 12.7Mb
![Page 66: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/66.jpg)
64
a { top: 0;}
lines = [ 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3]
columns = [ 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1]
lines & columns
![Page 67: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/67.jpg)
64
a { top: 0;}
lines = [ 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3]
columns = [ 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1]
lines & columns
![Page 68: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/68.jpg)
65
line = lines[offset];
column = offset - lines.lastIndexOf(line - 1, offset);
lines & columns
![Page 69: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/69.jpg)
65
line = lines[offset];
column = offset - lines.lastIndexOf(line - 1, offset);
lines & columns
It's acceptable only for short lines, that's why we cache the last line
start offset
![Page 70: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/70.jpg)
66
[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]
Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array
1 4 4 4 4
9 per token983 085 x 9 = 8.8Mb
![Page 71: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/71.jpg)
67
8.8Mb vs. 12.7Mb (min)
![Page 72: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/72.jpg)
Reduce operations with strings
![Page 73: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/73.jpg)
Performance «killers»*• RegExp • String concatenation • toLowerCase/toUpperCase • substr/substring • …
69
* Polluted GC pulls performance down
![Page 74: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/74.jpg)
Performance «killers»*• RegExp • String concatenation • toLowerCase/toUpperCase • substr/substring • …
70
* Polluted GC pulls performance down
We can’t avoid using these things, but we
can get rid of the rest
![Page 75: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/75.jpg)
71
var start = scanner.tokenStart;
…
scanner.next();
…
scanner.next();
…
return source.substr(start, scanner.tokenEnd);
Avoid string concatenations
![Page 76: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/76.jpg)
72
function cmpStr(source, start, end, str) { if (end - start !== str.length) { return false; }
for (var i = start; i < end; i++) { var sourceCode = source.charCodeAt(i); var strCode = str.charCodeAt(i - start);
if (sourceCode !== strCode) { return false; } }
return true;}
String comparison
No substring!
![Page 77: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/77.jpg)
73
function cmpStr(source, start, end, str) { if (end - start !== str.length) { return false; }
for (var i = start; i < end; i++) { var sourceCode = source.charCodeAt(i); var strCode = str.charCodeAt(i - start);
if (sourceCode !== strCode) { return false; } }
return true;}
String comparison
Length fast-check
![Page 78: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/78.jpg)
74
function cmpStr(source, start, end, str) { if (end - start !== str.length) { return false; }
for (var i = start; i < end; i++) { var sourceCode = source.charCodeAt(i); var strCode = str.charCodeAt(i - start);
if (sourceCode !== strCode) { return false; } }
return true;}
String comparison
Compare strings by char codes
![Page 79: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/79.jpg)
Case insensitive comparison of strings*?
75
* Means avoid toLowerCase/toUpperCase
![Page 80: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/80.jpg)
Heuristics• Comparison with the reference strings only (str)
• Reference strings may be in lower case and contain latin letters only (no unicode)
• I read once on Twitter…
76
![Page 81: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/81.jpg)
Setting of the 6th bit to 1 changes upper case latin letter to lower case
(works for latin ASCII letters only)
'A' = 01000001'a' = 01100001
'A'.charCodeAt(0) | 32 === 'a'.charCodeAt(0)
77
![Page 82: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/82.jpg)
78
function cmpStr(source, start, end, str) { … for (var i = start; i < end; i++) { … // source[i].toLowerCase() if (sourceCode >= 65 && sourceCode <= 90) { // 'A' .. 'Z' sourceCode = sourceCode | 32; }
if (sourceCode !== strCode) { return false; } } …}
Case insensitive string comparison
![Page 83: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/83.jpg)
Benefits• Frequent comparison stops on length check
• No substring (no pressure on GC)
• No temporary strings (e.g. result of toLowerCase/toUpperCase)
• String comparison don't pollute CG
79
![Page 84: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/84.jpg)
Results• RegExp • string concatenation • toLowerCase/toUpperCase • substr/substring
80
![Page 85: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/85.jpg)
No arrays in AST
![Page 86: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/86.jpg)
What's wrong with arrays?• As we are growing arrays their memory
fragments are to be relocated frequently (unnecessary memory moving)
• Pressure on GC
• We don't know the size of resulting arrays
82
![Page 87: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/87.jpg)
Solution?
83
![Page 88: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/88.jpg)
Bi-directional list
84
![Page 89: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/89.jpg)
85
![Page 90: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/90.jpg)
85
AST node AST node AST node AST node
![Page 91: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/91.jpg)
Needs a little bit more memory than arrays, but…
86
![Page 92: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/92.jpg)
Pros• No memory relocation
• No GC pollution during AST assembly
• next/prev references for free
• Cheap insertion and deletion
• Better for monomorphic walkers87
![Page 93: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/93.jpg)
Those approaches and others allowed to reduce memory consumption,
pressure on GC and made the parser twice faster than before
88
![Page 94: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/94.jpg)
89
CSSTree: 24 msMensch: 31 msCSSOM: 36 msPostCSS: 38 msRework: 81 msPostCSS Full: 100 msGonzales: 175 msStylecow: 176 msGonzales PE: 214 msParserLib: 414 ms
bootstrap.css v3.3.7 (146Kb)
github.com/postcss/benchmark
It's about this changes
13 ms
![Page 95: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/95.jpg)
But the story goes on 😋
90
![Page 96: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/96.jpg)
Parser performance boost story Part 3: а week after FrontTalks
![Page 97: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/97.jpg)
In general
• Simplify AST structure
• Less memory consumption
• Arrays reusing
• list.map().join() -> loop + string concatenation
• and others…92
![Page 98: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/98.jpg)
Once more time about token costs
![Page 99: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/99.jpg)
94
[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]
Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array
1 types 4 offsets 4 4 lines 4
9 per token983 085 x 9 = 8.8Mb
![Page 100: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/100.jpg)
lines can be computed on demand
95
![Page 101: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/101.jpg)
96
[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]
Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array
1 types 4 offsets 4 4 lines 4
5 per token983 085 x 5 = 4.9Mb
![Page 102: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/102.jpg)
Do we really needs all 32 bits for the offset?
Heuristics: no one parses more than 16Mb of CSS
97
![Page 103: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/103.jpg)
98
offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
![Page 104: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/104.jpg)
99
offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
offsetAndType[i] = type[i] << 24 | offset[i]
+
=
![Page 105: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/105.jpg)
100
offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
offsetAndType[i] = type[i] << 24 | offset[i]offsetAndType = [ 16777216, 788529157, … ]
+
=
![Page 106: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/106.jpg)
101
offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
offsetAndType[i] = type[i] << 24 | offset[i]offsetAndType = [ 16777216, 788529157, … ]start = offsetAndType[i] & 0xFFFFFF;type = offsetAndType[i] >> 24;
+
=
![Page 107: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/107.jpg)
102
[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]
Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array
1 types 4 offsets 4 4 lines 4
4 per token983 085 x 4 = 3.9Mb
![Page 108: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/108.jpg)
3.9-7.8 Mb vs. 12.7 Mb (min)
103
![Page 109: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/109.jpg)
104
class Scanner { ... next() { var next = this.currentToken + 1;
this.currentToken = next; this.tokenStart = this.tokenEnd; this.tokenEnd = this.offsetAndType[next + 1] & 0xFFFFFF; this.tokenType = this.offsetAndType[next] >> 24; }}
Needs 2 reads for 3 values (tokenEnd becomes tokenStart)
![Page 110: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/110.jpg)
105
class Scanner { ... next() { var next = this.currentToken + 1;
this.currentToken = next; this.tokenStart = this.tokenEnd; this.tokenEnd = this.offsetAndType[next + 1] & 0xFFFFFF; this.tokenType = this.offsetAndType[next] >> 24; }}
But 2 reads look redundant, let's fix it…
![Page 111: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/111.jpg)
106
offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
offsetAndType[i] = type[i] << 24 | offset[i]start = endend = offsetAndType[i + 1] & 0xFFFFFF;type = offsetAndType[i] >> 24;
![Page 112: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/112.jpg)
106
offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
offsetAndType[i] = type[i] << 24 | offset[i]start = endend = offsetAndType[i + 1] & 0xFFFFFF;type = offsetAndType[i] >> 24;
…
![Page 113: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/113.jpg)
107
offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
The first offset is always zero
![Page 114: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/114.jpg)
108
offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
Shift offsets to the left
![Page 115: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/115.jpg)
109
offset = [ 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
offsetAndType[i] = type[i] << 24 | offset[i + 1]offsetAndType[i] = type[i] << 24 | offset[i]start = endend = offsetAndType[i] & 0xFFFFFF;type = offsetAndType[i] >> 24;
…
![Page 116: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/116.jpg)
110
class Scanner { ... next() { var next = this.currentToken + 1;
this.currentToken = next; this.tokenStart = this.tokenEnd; this.tokenEnd = this.offsetAndType[next] & 0xFFFFFF; this.tokenType = this.offsetAndType[next] >> 24; }}
Now we need just one read
![Page 117: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/117.jpg)
111
class Scanner { ... next() { var next = this.currentToken + 1;
this.currentToken = next; this.tokenStart = this.tokenEnd; next = this.offsetAndType[next]; this.tokenEnd = next & 0xFFFFFF; this.tokenType = next >> 24; }}
-50% reads (~250k)
👌
![Page 118: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/118.jpg)
Re-use
![Page 119: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/119.jpg)
The scanner creates arrays every time when it parses
a new string
113
![Page 120: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/120.jpg)
The scanner creates arrays every time when it parses
a new string
113
![Page 121: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/121.jpg)
New strategy• Preallocate 16Kb buffer by default
• Create new buffer only if current is smaller than needed for parsing
• Significantly improves performance especially in cases when parsing a number of small CSS fragments
114
![Page 122: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/122.jpg)
115
CSSTree: 24 msMensch: 31 msCSSOM: 36 msPostCSS: 38 msRework: 81 msPostCSS Full: 100 msGonzales: 175 msStylecow: 176 msGonzales PE: 214 msParserLib: 414 ms
bootstrap.css v3.3.7 (146Kb)
github.com/postcss/benchmark
13 ms 7 ms
Current results
![Page 123: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/123.jpg)
And still not the end… 😋
116
![Page 124: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/124.jpg)
One more thing
![Page 125: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/125.jpg)
CSSTree – is not just about performance
118
![Page 126: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/126.jpg)
New feature*: Parsing and matching of
CSS values syntax
119
* Currently unique across CSS parsers
![Page 127: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/127.jpg)
Example
120
![Page 128: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/128.jpg)
121
csstree.github.io/docs/syntax.html
CSS syntax reference
![Page 129: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/129.jpg)
122
csstree.github.io/docs/validator.html
CSS values validator
![Page 130: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/130.jpg)
123
var csstree = require('css-tree');var syntax = csstree.syntax.defaultSyntax;var ast = csstree.parse('… your css …');
csstree.walkDeclarations(ast, function(node) { if (!syntax.match(node.property.name, node.value)) { console.log(syntax.lastMatchError); }});
Your own validator in 8 lines of code
![Page 131: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/131.jpg)
Some tools and plugins• csstree-validator – npm package + cli command
• stylelint-csstree-validator – plugin for stylelint
• gulp-csstree – plugin for gulp
• SublimeLinter-contrib-csstree – plugin for Sublime Text
• vscode-csstree – plugin for VS Code
• csstree-validator – plugin for Atom
More is coming…124
![Page 132: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/132.jpg)
Conclusion
![Page 133: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/133.jpg)
If you want your JavaScript works as fast as C, make it look like C
126
![Page 134: CSS parsing: performance tips & tricks](https://reader035.vdocuments.site/reader035/viewer/2022062503/5870055c1a28ab427f8b5e59/html5/thumbnails/134.jpg)
Previous talks• CSSO – performance boost story (russian)
tinyurl.com/csso-speedup
• CSS parsing (russian)tinyurl.com/csstree-intro
127