Парсим css: performance tips & tricks
TRANSCRIPT
![Page 1: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/1.jpg)
Парсим CSS performance tips & tricks
Роман Дворнов Avito
Москва, сентябрь 2016
![Page 2: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/2.jpg)
Руководитель фронтенда в Avito
Основной интерес – SPA
Open source:basis.js, CSSO, component-inspector, csstree и другие
За любую движуху, кроме голодовки ;)
![Page 3: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/3.jpg)
Парсим CSS (зачем? почему? как дальше жить?)
3
tinyurl.com/csstree-intro
Начало истории (доклад)
![Page 4: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/4.jpg)
CSSTree
![Page 5: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/5.jpg)
CSSTree – самый быстрый и детальный парсер CSS
5
![Page 6: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/6.jpg)
Как я до этого докатился?
![Page 7: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/7.jpg)
Чуть меньше года назад я стал мейнтейнером CSSO
(минификатор CSS)
7
github.com/css/csso
![Page 9: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/9.jpg)
Проблемы• Не развивается с 2013
• Неудобный формат AST, местами странный
• Много ошибок
• Запутанная и сложная кодовая база
• Медленный, потребляет много памяти, GC9
![Page 10: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/10.jpg)
Парсер – последнее, что я собирался трогать…
10
![Page 11: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/11.jpg)
Альтернатива?
![Page 12: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/12.jpg)
Парсеров CSS на JavaScript достаточно много
12
![Page 13: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/13.jpg)
Частые проблемы• Заброшены и не развиваются
• Устарели (не поддерживают новое в CSS)
• Содержат ошибки
• Неудачная структура
• Медленные13
![Page 15: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/15.jpg)
Плюсы PostCSS• Развивается и поддерживается
• Хорошо справляется с синтаксисом CSS и даже будущим + tolerant mode
• Сохраняет информацию о форматировании
• Удобное API для работы с AST
• Быстрый15
![Page 16: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/16.jpg)
Основная проблема: селекторы и значения свойств остаются не разобранными
(хранятся в виде строки)
16
![Page 17: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/17.jpg)
Это вынуждает разработчиков
• Использовать костыли
• Писать свои парсеры
• Использовать дополнительные парсеры:postcss-selector-parser postcss-value-parser
17
![Page 18: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/18.jpg)
Переход на PostCSS означал написание собственных парсеров селекторов и свойств, что не сильно отличается от
написания парсера целиком
18
![Page 19: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/19.jpg)
Регулярный рефакторинг приводит к тому, что парсер может быть полностью переписан
(это норма 😳)
19
![Page 21: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/21.jpg)
Скорость
![Page 22: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/22.jpg)
CSSO – история ускорения (в том числе про парсер)
22
tinyurl.com/csso-speedup
В предыдущих сериях (доклад)
![Page 23: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/23.jpg)
После выступления разогнал парсер еще :)
23
* Вдохновленный общением с Вячеславом @mraleph Егоровым
![Page 24: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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
Не детальное AST
Детальное AST
PostCSS Full = + postcss-selector-parser
+ postcss-value-parser
![Page 25: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/25.jpg)
Epic fail как выяснилось позже, я вынес
не ту версию парсера
25
😱github.com/csstree/csstree/commit/57568c758195153e337f6154874c3bc42dd04450
![Page 26: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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
На FrontTalks был показан результат
до разгона
13 ms
![Page 27: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/27.jpg)
Парсеры: курс молодого бойца
![Page 28: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/28.jpg)
Основные шаги
• Токенизация
• Построение дерева (лексер)
28
![Page 29: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/29.jpg)
Токенизация
![Page 30: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/30.jpg)
30
• whitespaces – [ \n\r\t\f]+ • keyword – [a-zA-aZ…]+ • number – [0-9]+ • string – "string" или 'string' • comment – /* comment */ • punctuation – [;,.#\{\}\[\]\(\)…]
Разбиение текста на токены
![Page 31: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/31.jpg)
31
.foo { width: 10px;}
[ '.', 'foo', ' ', '{', '\n ', 'width', ':', ' ', '10', 'px', ';', '\n', '}']
![Page 32: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/32.jpg)
Нужна дополнительная информация о токене: тип и локация
32
На этапе токенизации мы знаем тип и позицию,
считать их после – дорого
![Page 33: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/33.jpg)
33
.foo { width: 10px;}
[ { type: 'FullStop', value: '.', offset: 0, line: 1, column: 1 }, …]
![Page 34: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/34.jpg)
Сборка
![Page 35: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/35.jpg)
35
function getSelector() { var selector = { type: 'Selector', sequence: [] };
// main loop
return selector;}
Сборка
![Page 36: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/37.jpg)
37
{ "type": "StyleSheet", "rules": [{ "type": "Atrule", "name": "import", "expression": { "type": "AtruleExpression", "sequence": [ ... ] }, "block": null }]}
Результат
![Page 38: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/38.jpg)
История ускорения #2
![Page 39: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/39.jpg)
39
[ { type: 'FullStop', value: '.', offset: 0, line: 1, column: 1 }, …]
Стоимость токена: 24 + 5 * 4 + массив = min 50 bytes per token
В нашем проекте ~1Mb CSS 254 062 токена
= min 12.7 Mb
![Page 40: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/40.jpg)
Прелюдия: меняем подход
![Page 41: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/41.jpg)
Посчитать все токены, а потом из них собирать AST – проще,
но ведет к лишним затратам памяти и медленней
41
![Page 42: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/42.jpg)
Scanner (ленивый токенайзер)
42
![Page 43: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/43.jpg)
43
scanner.token // текущий токен или nullscanner.next() // переход к следующему токенуscanner.lookup(N) // заглядывание вперед, возвращает // токен на N-ой позиции от текущей
Основное API
![Page 44: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/44.jpg)
44
• lookup(N) заполняет буфер токенов до позиции N, если еще не заполнен, возвращает N-1 токен из буфера
• next()делает shift из lookup буфера, если он не пустой, либо читает новый токен
![Page 45: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/45.jpg)
Создается столько же токенов, но нужно меньше памяти в один
момент времени
45
![Page 46: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/46.jpg)
Проблема: заставляем GC плакать работать
46
![Page 47: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/47.jpg)
Уменьшаем стоимость токенов: «многоходовочка»
![Page 48: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/48.jpg)
48
[ { type: 'FullStop', value: '.', offset: 0, line: 1, column: 1 }, …]
Строковые обозначения удобны при отладке, но они не выходят за рамки
сканера и можно заменить на числа
![Page 49: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/49.jpg)
49
[ { type: FULLSTOP, value: '.', offset: 0, line: 1, column: 1 }, …]
…// '.'.charCodeAt(0)var FULLSTOP = 46;…
![Page 50: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/50.jpg)
50
[ { type: 46, value: '.', offset: 0, line: 1, column: 1 }, …]
![Page 51: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/51.jpg)
51
[ { type: 46, value: '.', offset: 0, line: 1, column: 1 }, …]
Можно не хранить подстроку – это особенно
расточительно для одиночных символов;
к тому же многие многие конструкции собираются из нескольких токенов – эффективнее брать одну
подстроку вместо конкатенации нескольких
![Page 52: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/53.jpg)
53
[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]
Look, Ma! No strings just numbers!
![Page 54: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/54.jpg)
54
Да не просто Array, а TypedArray
Массив объектов
Массивы чисел
![Page 55: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/55.jpg)
Array vs. TypedArray• Не могут содержать дырок
• В теории быстрее (т.к. меньше проверок)
• Хранятся вне heap (если достаточно большие)
• Предзаполнены нулями
55
![Page 56: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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(кол-во токенов) 254 062 x 17 = 4.3Mb
![Page 57: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/57.jpg)
4.3Mb vs. 12.7Mb (min)
57
![Page 58: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/58.jpg)
Хьюстон, у нас проблемы: TypedArray фиксированной длины,
а мы не знаем сколько токенов будет
58
![Page 59: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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(кол-во символов) 983 085 x 17 = 16.7Mb
![Page 60: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/60.jpg)
16.7Mb vs. 12.7Mb (min)
60
![Page 61: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/61.jpg)
16.7Mb vs. 12.7Mb (min)
60
Не повод сдаваться, давайте немного
подумаем…
![Page 62: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/62.jpg)
61
start = [ 0, 5, 6, 7, 9, 11, …, 35 ]
end = [ 5, 6, 7, 9, 11, 12, …, 36 ]
![Page 63: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/63.jpg)
61
start = [ 0, 5, 6, 7, 9, 11, …, 35 ]
end = [ 5, 6, 7, 9, 11, 12, …, 36 ]
…
![Page 64: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/68.jpg)
65
line = lines[offset];
column = offset - lines.lastIndexOf(line - 1, offset);
lines & columns
![Page 69: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/69.jpg)
65
line = lines[offset];
column = offset - lines.lastIndexOf(line - 1, offset);
lines & columns
Ок для коротких строк, нужно кешировать для
длинных
![Page 70: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/71.jpg)
67
8.8Mb vs. 12.7Mb (min)
![Page 72: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/72.jpg)
Меньше операций со строками
![Page 73: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/73.jpg)
«Убийцы» производительности*• RegExp • Конкатенация строк • toLowerCase/toUpperCase • substr/substring • …
69
* Засоряют GC и он все портит
![Page 74: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/74.jpg)
«Убийцы» производительности*• RegExp • Конкатенация строк • toLowerCase/toUpperCase • substr/substring • …
70
Без этого никак, но от остального можно избавиться
* Засоряют GC и он все портит
![Page 75: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/75.jpg)
71
var start = scanner.tokenStart;
…
scanner.next();
…
scanner.next();
…
return source.substr(start, scanner.tokenEnd);
Нет конкатенации!
![Page 76: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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;}
Сравнение строк
![Page 77: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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;}
Сравнение строк
Быстрое отсечение по длине
![Page 78: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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;}
Сравнение строк
Сравниваем код за кодом
![Page 79: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/79.jpg)
Как сравнивать без учета регистра*?
75
* То есть без toLowerCase/toUpperCase
![Page 80: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/80.jpg)
Эвристика• Сравниваем с заранее известными строками (str)
• Заранее заданные строки всегда в нижнем регистре и содержат только латинские буквы
• Читал я как то в твиттере…
76
![Page 81: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/81.jpg)
Чтобы перевести из верхнего регистра в нижний, нужно выставить 6-й бит в 1
(работает только для латинских букв)
'A' = 01000001'a' = 01100001
'A'.charCodeAt(0) | 32 === 'a'.charCodeAt(0)
77
![Page 82: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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; } } …}
Сравнение строк без учета регистра
![Page 83: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/83.jpg)
Бенефиты• Часто срабатывает быстрое отсечение
• Нет получения подстрок (не давим на GC)
• Нет получения временных строк (результат toLowerCase/toUpperCase)
• Операция сравнения не производит мусор
79
![Page 84: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/84.jpg)
Результаты• RegExp • Конкатенация строк • toLowerCase/toUpperCase • substr/substring
80
![Page 85: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/85.jpg)
Отказываемся от массивов (от слова совсем)
![Page 86: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/86.jpg)
Что не так с массивами• Если растить массив, то происходит копирование памяти + нагрузка на GC
• Мы не можем заранее знать размер массива
82
![Page 87: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/87.jpg)
Решение?
83
![Page 88: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/88.jpg)
Двусвязные списки
84
![Page 89: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/89.jpg)
85
![Page 90: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/90.jpg)
85
AST node AST node AST node AST node
![Page 91: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/91.jpg)
Требует немного больше памяти чем массивы, но…
86
![Page 92: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/92.jpg)
Плюсы• Не вызывает копирование памяти
• Не засоряет GC при построении AST
• Мы получаем next/prev
• Дешевая вставка/удаление
• Лучше для мономорфности87
![Page 93: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/93.jpg)
Всё это и многое другое позволило уменьшить потребление памяти,
нагрузку на GC и ускорить вдвое
88
![Page 94: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/94.jpg)
Но это еще не конец 😋
89
![Page 95: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/95.jpg)
История ускорения #3 неделя после FrontTalks
![Page 96: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/96.jpg)
Общие моменты
• Упрощение структуры AST
• Меньше потребление памяти, переиспользование
• list.map().join() -> цикл + конкатенация
• и по мелочи…
91
![Page 97: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/97.jpg)
И снова про стоимость токенов
![Page 98: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/98.jpg)
93
[ { 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 99: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/99.jpg)
lines можно считать не всегда и лениво
94
![Page 100: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/100.jpg)
95
[ { 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 101: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/101.jpg)
Действительно ли для offsets нужно 32 бита?
Эвристика: вряд ли кто-то будет парсить CSS больше 16Mb
96
![Page 102: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/102.jpg)
97
offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
![Page 103: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/103.jpg)
98
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 104: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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]offsetAndType = [ 16777216, 788529157, … ]
+
=
![Page 105: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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, … ]start = offsetAndType[i] & 0xFFFFFF;type = offsetAndType[i] >> 24;
+
=
![Page 106: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/106.jpg)
101
[ { 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 107: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/107.jpg)
3.9-7.8 Mb vs. 12.7 Mb (min)
102
![Page 108: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/108.jpg)
103
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; }}
Нужно всего 2 чтения для 3 значений, т.к. конец становится началом
![Page 109: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/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; }}
Два чтения из массива – как то не круто…
![Page 110: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/110.jpg)
105
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 111: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/111.jpg)
105
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: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/112.jpg)
106
offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
Первое смещение всегда ноль
![Page 113: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/113.jpg)
107
offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]
type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]
Сдвигаем влево
![Page 114: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/114.jpg)
108
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 115: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/115.jpg)
109
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; }}
Теперь можно в одно чтение
![Page 116: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/116.jpg)
110
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% чтений (~250k)
👌
![Page 117: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/117.jpg)
Переиспользование
![Page 118: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/118.jpg)
Сканер каждый раз создавал новые массивы на каждый
разбор
112
![Page 119: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/119.jpg)
Сканер каждый раз создавал новые массивы на каждый
разбор
112
![Page 120: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/120.jpg)
Новая стратегия• По дефолту создается буфер в 16Kb
• Создается новый буфер, только если он мал для разбираемого CSS
• Значительный прирост скорости, особенно в сценариях разбора малых фрагментов CSS
113
![Page 121: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/121.jpg)
114
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
Текущий результат
![Page 122: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/122.jpg)
И это еще не конец… 😋
115
![Page 123: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/123.jpg)
Минутка «рекламы»
![Page 124: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/124.jpg)
CSSTree – не только про скорость
117
![Page 125: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/125.jpg)
Новая фича*: Разбор и матчинг синтаксиса
CSS значений
118
* Пока уникальная среди CSS парсеров
![Page 126: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/126.jpg)
Пример
119
![Page 127: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/127.jpg)
120
csstree.github.io/docs/syntax.html
Документация синтаксиса
![Page 128: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/128.jpg)
121
csstree.github.io/docs/validator.html
Валидатор синтаксиса CSS значений
![Page 129: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/129.jpg)
122
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); }});
Свой валидатор в 8 строк
![Page 130: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/130.jpg)
Кое что еще• csstree-validator – npm пакет + консольная команда
• stylelint-csstree-validator – плагин для stylelint
• gulp-csstree – плагин для gulp
• SublimeLinter-contrib-csstree – плагин для Sublime Text
• vscode-csstree – плагин для VS Code
• csstree-validator – плагин для Atom
More is coming…123
![Page 131: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/131.jpg)
Заключение
![Page 132: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/132.jpg)
Хотите чтобы ваш JavaScript работал так же быстро как Си, сделайте его похожим на Си
125
![Page 133: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/133.jpg)
Изучайте алгоритмы, структуры данных, как работают JS-движки и GC –
у вас будет больше вариантов для оптимизаций
126
– К.О.
![Page 134: Парсим CSS: performance tips & tricks](https://reader030.vdocuments.mx/reader030/viewer/2022020410/587b8b561a28ab9d448b70ff/html5/thumbnails/134.jpg)
Доклады по теме• CSSO – история ускорения
tinyurl.com/csso-speedup
• Парсим CSStinyurl.com/csstree-intro
127