holyjs - jug ru grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfcsso —...

146
CSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург, 2016

Upload: others

Post on 23-May-2020

9 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

CSSO — история ускорения

Роман Дворнов Avito

HolyJS

Санкт-Петербург, 2016

Page 2: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Работаю в Avito

Делаю SPA

Автор basis.js

Мейнтенер CSSO

За любую движуху, кроме голодовки ;)

Page 3: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Врем

я сж

атия

CSS

(600

Kb)

500 ms

1 000 ms

1 500 ms

2 000 ms

2 500 ms

3 000 ms

3 500 ms

4 000 ms

4 500 ms

5 000 ms

5 500 ms

6 000 ms

Версия CSSO

1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 2.0

1 050 msclean-css

Изменение скорости CSSO

csso500 ms

cssnano23 250 ms

Page 4: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Почему это важно?

Page 5: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

5

clean-css 3.4.16 cssnano 3.6.2 csso 2.1.1

ноябрь 2015602 245 байт

430 2402 039 ms

439 21041 355 ms

435 629714 ms

апрель 2016822 021 байт

587 9062 546 ms

604 16776 665 ms

595 834793 ms

июнь 2016883 498 байт

632 0072 588 ms

648 57994 867 ms

639 495803 ms

Время минификации CSSОсновной CSS файл ActiAgent.ru

Page 6: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Тренд изменения времени от размера CSSТр

енд из

мене

ний ра

змер

а и вр

емен

и

100 %

120 %

140 %

160 %

180 %

200 %

220 %

240 %

Размер CSS

602 245 822 021 883 498

размер CSS clean-css cssnano csso

229%

146%

127%112%

Page 7: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

7

Новые продвинутые оптимизации(usage data, rename, etc)

245 823

Время

Размер (байт) 639 495

803 ms 2 513 ms

Лучше сжатие, но требует 2-3х больше времени

Page 8: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

8

clean-css 3.4.16 cssnano 3.6.2 csso 2.1.1

15,5 sec 9 min 29 sec 4,8 sec

Таких файлов в проекте несколько (6 больших)время минификации всех файлов

Page 9: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

8

clean-css 3.4.16 cssnano 3.6.2 csso 2.1.1

15,5 sec 9 min 29 sec 4,8 sec

Таких файлов в проекте несколько (6 больших)время минификации всех файлов

clean-css 3.4.16 cssnano 3.6.2 csso 2.1.1

не поддерживается

(≈46,5 sec)не поддерживается

(≈28 min 27 sec) ~15 sec

+ продвинутые оптимизации

Page 10: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

9

CSS

сжатый CSS

Процесс минификации

Page 11: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

9

CSS

сжатый CSS

Парсер

Процесс минификации

Page 12: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

9

CSS

сжатый CSS

ASTПарсер

Процесс минификации

Page 13: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

9

CSS

сжатый CSS

ASTПарсер

Компрессор

Процесс минификации

Page 14: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

9

CSS

сжатый CSS

AST

AST

Парсер

Компрессор

Процесс минификации

Page 15: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

9

CSS

сжатый CSS

AST

AST

Парсер

Компрессор

Транслятор

Процесс минификации

Page 16: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

9

CSS

сжатый CSS

AST

AST

Парсер

Компрессор

Транслятор

Процесс минификации

Минификатор

Page 17: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Парсер

Page 18: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Основные шаги

• Токенизация

• Построение дерева (лексер)

11

Page 19: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Токенизация

Page 20: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

13

• whitespaces – [ \n\r\t\f]+ • keyword – [a-zA-aZ…]+ • number – [0-9]+ • string – "string" или 'string' • comment – /* comment */ • punctuation – [;,.#\{\}\[\]\(\)…]

Разбиение текста на токены

Page 21: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

14

.foo { width: 10px;}

[ '.', 'foo', ' ', '{', '\n ', 'width', ':', ' ', '10', 'px', ';', '\n', '}']

Page 22: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Низкоуровневые штуки

Page 23: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

16

for (var i = 0; i < str.length; i++) { var ch = str.charAt(i); var chNext = str.charAt(i + 1); if (ch === '/' && chNext === '*') { result.push(readComment()); } else if (ch >= '0' && ch <= '9') { result.push(readNumber()); } ...}

Ключевой цикл токенайзера

Page 24: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

17

for (var i = 0; i < str.length; i++) { var ch = str.charAt(i); var chNext = str.charAt(i + 1); if (ch === '/' && chNext === '*') { result.push(readComment()); } else if (ch >= '0' && ch <= '9') { result.push(readNumber()); } ...}

Ключевой цикл токенайзера

Page 25: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

18

for (var i = 0; i < str.length; i++) { var ch = str.charAt(i); var chNext = str.charAt(i + 1); if (ch === '/' && chNext === '*') { result.push(readComment()); } else if (ch >= '0' && ch <= '9') { result.push(readNumber()); } ...}

Ключевой цикл токенайзера

Page 26: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

19

for (var i = 0; i < str.length; i++) { var ch = str.charAt(i); var chNext = str.charAt(i + 1); if (ch === '/' && chNext === '*') { result.push(readComment()); } else if (ch >= '0' && ch <= '9') { result.push(readNumber()); } ...}

Ключевой цикл токенайзера

Page 27: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

20

for (var i = 0; i < str.length; i++) { var ch = str.charAt(i); var chNext = str.charAt(i + 1); if (ch === '/' && chNext === '*') { result.push(readComment()); } else if (ch >= '0' && ch <= '9') { result.push(readNumber()); } ...}

Ключевой цикл токенайзера

Page 28: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Обычный код – простор для потери быстродействия

21

Page 29: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Строки vs. числа

Page 30: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

23

for (var i = 0; i < str.length; i++) { var ch = str.charAt(i); var chNext = str.charAt(i + 1); if (ch === '/' && chNext === '*') { result.push(readComment()); } else if (ch >= '0' && ch <= '9') { result.push(readNumber()); } ...}

Сравнение символов

Page 31: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

24

• Строка – последовательность чисел

• Извлечение символа = получение новой строки

• Сравнение символов ≈ сравнение строк

Нужно помнить

Page 32: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

25

!!!

Page 33: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

26

// выносим коды символов в константыvar STAR = 42;var SLASH = 47;var ZERO = 48;var NINE = 57;...

Делаем быстрее

Page 34: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

27

for (var i = 0; i < str.length; i++) { var code = str.charCodeAt(i); var codeNext = str.charCodeAt(i + 1); if (code === SLASH && codeNext === STAR) { result.push(readComment()); } else if (code >= ZERO && code <= NINE) { result.push(readNumber()); } ...}

charCodeAt() + числовые константы

Page 35: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

28

✔Нет больше

StringCharFromCode

✔Всегда сравнение

чисел

Page 36: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

28

✔Нет больше

StringCharFromCode

✔Всегда сравнение

чисел

???Загрузка значения перед сравнением

Page 37: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Используй const, Люк!

29

var STAR = 42;var SLASH = 47;var ZERO = 48;var NINE = 57;...

const STAR = 42;const SLASH = 47;const ZERO = 48;const NINE = 57;...

Page 38: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

30

WTF?

Page 39: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

– Вячеслав Егоров

“... [const] это все-таки неизменяемая привязка переменной к значению ...

С другой стороны виртуальная машина может и должна бы использовать это самое свойство неизменяемости ...

V8 не использует, к сожалению.”

31

habrahabr.ru/company/jugru/blog/301040/#comment_9622474

Page 40: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

32

for (var i = 0; i < str.length; i++) { var code = str.charCodeAt(i); var codeNext = str.charCodeAt(i + 1); if (code === 47 && codeNext === 42) { result.push(readComment()); } else if (code >= 48 && code <= 57) { result.push(readNumber()); } ...}

Но мы люди упоротые упорные

Page 41: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

32

for (var i = 0; i < str.length; i++) { var code = str.charCodeAt(i); var codeNext = str.charCodeAt(i + 1); if (code === 47 && codeNext === 42) { result.push(readComment()); } else if (code >= 48 && code <= 57) { result.push(readNumber()); } ...}

Но мы люди упоротые упорные

Положительный эффект не замечен…

Page 42: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

33

356ms – chatAt() 307ms – charCodeAt()

Результаты

на ~14% быстрее

Page 43: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Осторожней со строками

Page 44: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

35

for (var i = 0; i < str.length; i++) { var code = str.charCodeAt(i); var codeNext = str.charCodeAt(i + 1); if (code === SLASH && codeNext === STAR) { result.push(readComment()); } else if (code >= ZERO && code <= NINE) { result.push(readNumber()); } ...}

Избегаем лишних чтений из строки

Page 45: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

36

for (var i = 0; i < str.length; i++) { var code = str.charCodeAt(i); if (code === SLASH) { var codeNext = str.charCodeAt(i + 1); if (codeNext === STAR) { result.push(readComment()); continue; } } ...}

Избегаем лишних чтений из строки

307ms → 292 ms (-5%)

Page 46: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

37

for (var i = 0; i < str.length; i++) { var code = str.charCodeAt(i); if (code === SLASH) { var codeNext = str.charCodeAt(i + 1); if (codeNext === STAR) { result.push(readComment()); continue; } } ...}

Опасная операция!

Page 47: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

38

Попытка чтения за пределами строки или массива

приводит к деоптимизации

Page 48: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

39

var codeNext = 0;if (i + 1 < str.length) { codeNext = str.charCodeAt(i + 1);}

Избегаем выхода за пределы строки

292ms → 69 ms (в 4 раза быстрее!)

Page 49: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Меньше сравнений

Page 50: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

41

if (code >= ZERO && code <= NINE) { // число} else if (code === SINGLE_QUOTE || code === DOUBLE_QUOTE) { // строка} else if (code === SPACE || code === N || code === R || code === F) { // whitespace} …

Много сравнений

Page 51: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

42

var PUNCTUATION = { 9: 'Tab', // '\t' 10: 'Newline', // '\n' ... 126: 'Tilde' // '~'};...} else if (code in PUNCTUATION) { // символ пунктуации} ...

Проверка в словаре – очень дорого

Page 52: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

43

var CATEGORY_LENGTH = Math.max.apply(null, Object.keys(PUNCTUATION)) + 1;var CATEGORY = new Uint32Array(CATEGORY_LENGTH);

Object.keys(PUNCTUATION).forEach(function(key) { CATEGORY[key] = PUNCTUATOR;});

for (var i = ZERO; i <= NINE; i++) { CATEGORY[i] = DIGIT;}

CATEGORY[SINGLE_QUOTE] = STRING;CATEGORY[DOUBLE_QUOTE] = STRING;CATEGORY[SPACE] = WHITESPACE;...

Создаем карту категорий

Page 53: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

44

switch (code < CATEGORY_LENGTH ? CATEGORY[code] : 0) { case DIGIT: // число case STRING: // строка case WHITESPACE: // whitespace case PUNCTUATOR: // символ пунктуации ...}

Уменьшаем число сравнений

69ms → 14 ms (еще в 5 раз быстрее!)

Page 54: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Предварительные итоги

Page 55: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Результат: 356ms → 14 ms Цифры получены на упрощенном примере, на практике всё сложнее

Примеры тестов: gist.github.com/lahmatiy/e124293c2f2b98e847d0f2bb54c2738e

46

Page 56: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

47

• charAt() → charCodeAt()

• Избегайте лишних операций со строками

• Контролируйте выход за пределы

• Уменьшайте количество сравнений

Подводим итоги

Page 57: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Токен – не только строка

Page 58: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Нужна дополнительная информация о токене: тип и локация

49

Page 59: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

50

.foo { width: 10px;}

[ { type: 'FullStop', value: '.', offset: 0, line: 1, column: 1 }, …]

Page 60: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Как хранить?

51

Object ArrayПонятнее Компактнее

{ type: 'FullStop', value: '.', offset: 1, line: 2, column: 3}

['FullStop', '.', 1, 2, 3]

Page 61: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Размер токена

52

Object Array64 байт 88 байт

Page 62: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Размер токена

52

Object Array64 байт 88 байт

Наш CSS – 885Kb 256 163 токена

16,4 Мб 22,5 Мбразница 6,1 Мб

Page 63: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Что быстрее?

53

Object Array

Время создания (литералов) сопоставимо, меняется в зависимости от задачи

Обработка объектов (чтение) гораздо быстрее

Нет простого ответа

gist.github.com/lahmatiy/90fa91d5ea89465d08c7db1e591e91c2

Page 64: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Меняем подход

Page 65: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Последовательные токенайзер и лексер – проще,

но ведет к лишним затратам памяти и медленней

55

Page 66: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Scanner (ленивый токенайзер)

56

Page 67: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

57

scanner.token // текущий токен или nullscanner.next() // переход к следующему токенуscanner.lookup(N) // заглядывание вперед, возвращает // токен на N-ой позиции от текущей

Основное API

Page 68: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

58

• lookup(N) заполняет буфер токенов до позиции N, если еще не заполнен, возвращает N-1 токен из буфера

• next()делает shift из lookup буфера, если он не пустой, либо читает новый токен

Page 69: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Создается столько же токенов, но нужно меньше памяти в один

момент времени

59

Page 70: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

60

• Переиспользование объектов токенов

• Пул токенов фиксированного размера

Неудачные эксперименты

Выигрыш практически не ощущается, но код гораздо сложнее и в разных случаях нужен пул

разного размера, иногда очень большой

Page 71: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Сборка дерева

Page 72: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

62

• Избавление от look ahead

• Array → Object

• Array → List

• CST → AST

• …

Как ускорялся

Page 73: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Look ahead

Page 74: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

64

function getSelector() { var node = ['Selector']; while (i < tokens.length) { if (checkId()) { node.push(getId()); } else if (checkClass()) { node.push(getClass()); } else ... } return node;}

Было

Page 75: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

65

function getSelector() { var node = ['Selector']; while (i < tokens.length) { if (checkId()) { node.push(getId()); } else if (checkClass()) { node.push(getClass()); } else ... } return node;}

Было

Checker-функции – проверяют, подходящая ли последовательность токенов чтобы собрать AST узел

Page 76: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

66

function getSelector() { var node = ['Selector']; while (i < tokens.length) { if (checkId()) { node.push(getId()); } else if (checkClass()) { node.push(getClass()); } else ... } return node;}

БылоBuild-функции – собирают AST узел, у них есть гарантия, что впереди подходящая последовательность токенов

Page 77: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

67

• Многократный проход по списку токенов

• Нужны все токены сразу

• Большое дублирование кода (проверок)

• Сложно определить местоположение, в случае синтаксической ошибки в CSS

Проблемы

Page 78: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

68

StyleSheet Ruleset Selector SimpleSelector Id | Class | Attribute … … Block … …

Насколько все плохоcheckStyleSheet() проверяла всю последовательность токенов, прежде чем начиналась сборка AST

Page 79: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

– Cascading Style Sheets Level 2 Specification

“The lexical scanner for the CSS core syntax in section 4.1.1 can be implemented as a scanner without back-up.”

69

www.w3.org/TR/CSS22/grammar.html#q4

Page 80: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

70

while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.Hash: // # node.push(getId()); break; case TokenType.FullStop: // . node.push(getClass()); break; … }

СталоБыстрая проверка типа токена. В большинстве случае этого достаточно, чтобы выбрать подходящую функцию сборки

Page 81: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

71

function getPseudo() { var next = scanner.lookup(1);

if (next.type === TokenType.Colon) { return getPseudoElement(); }

return getPseudoClass();}

СталоЗаглядывать вперед нужно редко, в этих случаях используется scanner.lookup(N)

Page 82: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

72

if (scanner.token.type !== TokenType.Something) { throwError('Something wrong here');}

Стало

Если встречается что-то "не то" – тут же выбрасывается сообщение об ошибке

Page 83: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

73

• Значительно быстрее

• Используется ленивый токенайзер

• Код проще, мало дублирования

• Точное местоположение для ошибок в CSS

Сплошные плюсы

Page 84: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Array → Object

Page 85: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

75

[ "stylesheet", [ "atrules", [ "atkeyword", [ "ident", "import" ] ], [ "s", " " ], [ "string", "'path/to/file.css'" ] ]]

Было – массив массивов

Page 86: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

76

{ "type": "StyleSheet", "rules": [{ "type": "Atrule", "name": "import", "expression": { "type": "AtruleExpression", "sequence": [ ... ] }, "block": null }]}

Стало

Page 87: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

77

• Требуется меньше памяти

• Быстрее работа с узлами

• Проще писать/читать код (node[1] vs. node.value)

Результат

Page 88: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

78

Помни – меньше мутаций!function createNode() { var node = { type: 'Type' };

if (expression1) { node.foo = 123; } if (expression2) { node.bar = true; }

return node;}

При добавлении нового свойства создается новый hidden class (здесь до 4). Узел одного типа, но объекты разных классов – принимающие функции станут полиморфными.

Page 89: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

79

Добавляется новое свойство → меняется hidden class

Page 90: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

80

Смена hidden class

Page 91: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

81

Фиксимfunction createNode() { var node = { type: 'Type', foo: null, bar: false };

if (expression1) { node.foo = 123; } if (expression2) { node.bar = true; }

return node;}

При создании объекта стоит указывать все возможные свойства – будет один hidden class

Page 92: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

82

Все стало проще

И функции работающие с объектами только этого типа будут мономорфными

Page 93: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

CST → AST

Page 94: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

84

• AST (Abstract Syntax Tree)логическая структура

• CST (Concrete Syntax Tree)логическая структура + информация о форматировании

AST vs. CST

Page 95: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

85

• AST – не нужно форматирование linters, minifiers, beautifiers, …

• CST – форматирование важноlinters, autoprefixer, postcss и друзья

AST vs. CST

Page 96: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Раньше парсер возвращал CST, то есть сохранял все

пробелы, комментарии и разделители

86

Page 97: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

87

.foo > .bar,

.baz { border: 1px solid red; // I'm useless comment}

Отмеченное желтым – не нужно, но сохранялось в дереве, а компрессор

это все удалял

Page 98: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Так же теперь парсер не собирает заведомо неверные конструкции,

что упрощает и ускоряет дальнейшую работу с деревом

(т.к. не требуются лишние проверки)

88

Page 99: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

89

{ "type": "Dimension", "value": { "type": "Number", "value": "123" }, "unit": { "type": "Identifier", "name": "px" }}

Упрощен формат некоторых узлов

{ "type": "Dimension", "value": "123", "unit": "px"}

Page 100: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

90

• Меньше узлов в дереве

• Не нужен обход, чтобы удалить ненужное

• Многое проще не добавлять в дерево, чем потом искать и удалять

Результат

Page 101: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

91

• Наш CSS – 885Kb

• было узлов – 281 750

• стало узлов – 139 858

• Экономия памяти ~7Mb

• В два раза быстрее обход

В цифрах

Page 102: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Подводим итоги

Page 103: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

93

• В ~5 раз быстрее

• В ~3 раза меньше потребление памяти

• Проще формат

• Дешевле обход и обработка AST

Улучшения парсера

Page 104: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Компрессор

Page 105: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

95

• Однопроходная реструктуризация

• Уменьшение числа обходов

• Уменьшение глубины обхода

• Минимум вычислений, мутаций узлов, копирования

• Учет специфики CSS

• …

Как ускорялся

Page 106: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Однопроходная реструктуризация

Page 107: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

97

var prevAst = ast;var prev = translate(ast);while (true) { var resAst = restruct(copy(prevAst)); var res = translate(resAst);

if (res.length > prev.length) { resAst = prevAst; break; }

prevAst = resAst; prev = res;}

Трансформируем пока есть положительный эффект

Page 108: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

98

var prevAst = ast;var prev = translate(ast);while (true) { var resAst = restruct(copy(prevAst)); var res = translate(resAst);

if (res.length > prev.length) { resAst = prevAst; break; }

prevAst = resAst; prev = res;}

На каждой итерации • копирование дерева

Page 109: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

99

var prevAst = ast;var prev = translate(ast);while (true) { var resAst = restruct(copy(prevAst)); var res = translate(resAst);

if (res.length > prev.length) { resAst = prevAst; break; }

prevAst = resAst; prev = res;}

На каждой итерации • копирование дерева • трансляция в строку

Page 110: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

100

var prevAst = ast;var prev = translate(ast);while (true) { var resAst = restruct(copy(prevAst)); var res = translate(resAst);

if (res.length > prev.length) { resAst = prevAst; break; }

prevAst = resAst; prev = res;}

На каждой итерации • копирование дерева • трансляция в строку •бай-бай GC

Page 111: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

101

var prevAst = ast;var prev = translate(ast);while (true) { var resAst = restruct(copy(prevAst)); var res = translate(resAst);

if (res.length > prev.length) { resAst = prevAst; break; }

prevAst = resAst; prev = res;}

На каждой итерации • копирование дерева • трансляция в строку •бай-бай GC

😱

Page 112: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Стало

102

restruct(ast);

• Не ошибка – один проход

• Нет копирования AST, трансляции всего дерева в строку

• Пришлось поменять алгоритмы

• Меньше нагрузка на GC, быстрее в 3-4 раза

Page 113: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Глубина обхода

Page 114: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

104

Наш CSS – 885Kb

139 858 узлов

Холостой обход дерева

31 ms

Page 115: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Многим трансформациям необходимо обходить лишь

Ruleset и AtRule

105

Page 116: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

106

• walkAll() – обход всех узлов

• walkRules() – обход только правил и at-rules

• walkRulesRight() – обход только правил и at-rules в обратном порядке

Walker под задачу

Page 117: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

107

Наш CSS – 139 858 узлов

walkAll()

31 ms

Холостой обход дерева

walkRules(), walkRulesRight()

2 msобходится 6 154 узла (4%)

Page 118: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Меньше обходов

Page 119: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

109

walkAll(ast, function(node) { if (node.type === 'Number') { … }});walkAll(ast, function(node) { if (node.type === 'String') { … }});…

БылоДля каждого типа узла отдельный обход Как мы помним, для нашего дерева операция стоит 31 ms

Page 120: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

110

walkAll(ast, function(node) { if (node.type === 'Number') { … }});walkAll(ast, function(node) { if (node.type === 'String') { … }});…

БылоФункции получают все узлы дерева, а они разных hidden class. Так функции будут полиморфными, хотя работают (обычно) только с одним типом узлов

Page 121: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

111

var handlers = { Number: packNumber, String: packString, …};

walkAll(ast, function(node, item, list) { if (handlers.hasOwnProperty(node.type)) { handlers[node.type].call(this, node, item, list); }});

СталоОдин проход

Page 122: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

112

var handlers = { Number: packNumber, String: packString, …};

walkAll(ast, function(node, item, list) { if (handlers.hasOwnProperty(node.type)) { handlers[node.type].call(this, node, item, list); }});

СталоЕдинственная полиморфная функция, но простая

Page 123: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

113

var handlers = { Number: packNumber, String: packString, …};

walkAll(ast, function(node, item, list) { if (handlers.hasOwnProperty(node.type)) { handlers[node.type].call(this, node, item, list); }});

СталоФункции оказываются мономорфными (им приходят объекты одного hidden class)

Page 124: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

114

var handlers = { Number: packNumber, String: packString, …};

walkAll(ast, function(node, item, list) { if (handlers.hasOwnProperty(node.type)) { handlers[node.type].call(this, node, item, list); }});

СталоПо результатам экспериментов, наиболее быстрый вариант решения

Page 125: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Общий итог

Page 126: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

116

• В 10+ раз быстрее

• В 7+ раз меньше потребление памяти

• В настоящий момент быстрее и эффективнее по ресурсам других структурных минификаторов (cssnano, clean-css)

Текущие результаты

goalsmashers.github.io/css-minification-benchmark/

Page 127: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Работы не закончены, можно сделать еще лучше ;)

117

Page 128: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

CSSO — сжимаем CSS

118

www.slideshare.net/basisjs/csso-css-2

Немного о принципах минификации, новых продвинутых оптимизациях и открытых вопросах

Page 129: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Бонус трек

Page 130: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Обкладывайте операции замерами

120

> echo '.test { color: green; }' | csso --debug## parsing done in 11 ms

Compress block #1[0.000s] init[0.001s] clean[0.001s] compress[0.004s] prepare[0.001s] initialMergeRuleset[0.000s] mergeAtrule[0.000s] disjoinRuleset[0.001s] restructShorthand[0.003s] restructBlock...

Page 131: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Hi-res timing

121

var startTime = process.hrtime();// your codevar elapsed = process.hrtime(startTime);console.log(parseInt(elapsed[0] * 1e3 + elapsed[1] / 1e6));

var startTime = performance.now();// your codeconsole.log(performance.now() - startTime);

Node.js

Browser

Page 132: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Memory usage

122

var mem = process.memoryUsage().heapUsed;// your codeconsole.log(process.memoryUsage().heapUsed - mem);

Очень грубый метод, лучшего простого способа пока нет :'(

Page 133: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Привычные инструменты

Page 134: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

devtool

124

Runs Node.js programs inside Chrome DevTools

npm install -g devtool

github.com/Jam3/devtool

Page 135: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

> devtool your-script.js

125

Page 136: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Заглядывайте под капот

Page 137: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

127

node --trace-hydrogen \ --trace-phase=Z \ --trace-deopt \ --code-comments \ --hydrogen-track-positions \ --redirect-code-traces \ --redirect-code-traces-to=code.asm \ --trace_hydrogen_file=code.cfg \ --print-opt-code \ your-script.js

Получаем данные о работе кода

Page 138: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

128

mrale.ph/irhydra/2/

code.asm code.cfg

Page 139: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,
Page 140: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Самое сложное научиться это понимать ;)

130

Page 141: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Заключение

Page 142: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Вам это все не нужно, но однажды может пригодиться

132

Page 143: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

133

• Подбирать решение под задачу

• Меньше тратить памяти

• Меньше мутаций

• Проще структуры

• Разбираться как выполняется код

• Перестать думать, что вы умней VM и наоборот ;)

Капитанские советы

Page 144: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Нет серебряной пули – экспериментируйте, заменяйте,

смотрите под капот

134

Page 145: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Изучайте алгоритмы и структуры данных – правильный их выбор может дать больше

чем оптимизация кода

135

Page 146: HolyJS - JUG Ru Grouppublic.jugru.org/holyjs/2016/spb/day_1/track_1/dvornov.pdfCSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург,

Роман Дворнов @rdvornov

[email protected]

Вопросы?

github.com/css/csso