SlideShare une entreprise Scribd logo
1  sur  146
Télécharger pour lire hors ligne
CSSO — история ускорения
Роман Дворнов
Avito
HolyJS
Санкт-Петербург, 2016
Работаю в Avito
Делаю SPA
Автор basis.js
Мейнтенер CSSO
За любую движуху, 

кроме голодовки ;)
ВремясжатияCSS(600Kb)
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 ms
clean-css
Изменение скорости CSSO
csso
500 ms
cssnano
23 250 ms
Почему это важно?
5
clean-css 3.4.16 cssnano 3.6.2 csso 2.1.1
ноябрь 2015
602 245 байт
430 240
2 039 ms
439 210
41 355 ms
435 629
714 ms
апрель 2016
822 021 байт
587 906
2 546 ms
604 167
76 665 ms
595 834
793 ms
июнь 2016
883 498 байт
632 007
2 588 ms
648 579
94 867 ms
639 495
803 ms
Время минификации CSS
Основной CSS файл ActiAgent.ru
Тренд изменения времени от размера 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%
7
Новые продвинутые оптимизации
(usage data, rename, etc)
245 823
Время
Размер (байт) 639 495
803 ms 2 513 ms
Лучше сжатие, но требует 2-3х больше времени
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 больших)
время минификации всех файлов
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
+ продвинутые оптимизации
9
CSS
сжатый
CSS
Процесс минификации
9
CSS
сжатый
CSS
Парсер
Процесс минификации
9
CSS
сжатый
CSS
ASTПарсер
Процесс минификации
9
CSS
сжатый
CSS
ASTПарсер
Компрессор
Процесс минификации
9
CSS
сжатый
CSS
AST
AST
Парсер
Компрессор
Процесс минификации
9
CSS
сжатый
CSS
AST
AST
Парсер
Компрессор
Транслятор
Процесс минификации
9
CSS
сжатый
CSS
AST
AST
Парсер
Компрессор
Транслятор
Процесс минификации
Минификатор
Парсер
Основные шаги
• Токенизация
• Построение дерева (лексер)
11
Токенизация
13
• whitespaces – [ nrtf]+
• keyword – [a-zA-aZ…]+
• number – [0-9]+
• string – "string" или 'string'
• comment – /* comment */
• punctuation – [;,.#{}[]()…]
Разбиение текста на токены
14
.foo {
width: 10px;
}
[
'.', 'foo', ' ', '{',
'n ', 'width', ':',
' ', '10', 'px', ';',
'n', '}'
]
Низкоуровневые штуки
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());
} ...
}
Ключевой цикл токенайзера
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());
} ...
}
Ключевой цикл токенайзера
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());
} ...
}
Ключевой цикл токенайзера
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());
} ...
}
Ключевой цикл токенайзера
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());
} ...
}
Ключевой цикл токенайзера
Обычный код – простор для
потери быстродействия
21
Строки vs. числа
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());
} ...
}
Сравнение символов
24
• Строка – последовательность чисел
• Извлечение символа = получение новой строки
• Сравнение символов ≈ сравнение строк
Нужно помнить
25
!!!
26
// выносим коды символов в константы
var STAR = 42;
var SLASH = 47;
var ZERO = 48;
var NINE = 57;
...
Делаем быстрее
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() + числовые константы
28
✔
Нет больше
StringCharFromCode
✔
Всегда сравнение
чисел
28
✔
Нет больше
StringCharFromCode
✔
Всегда сравнение
чисел
???
Загрузка значения
перед сравнением
Используй 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;
...
30
WTF?
– Вячеслав Егоров
“... [const] это все-таки неизменяемая привязка
переменной к значению ...
С другой стороны виртуальная машина может и
должна бы использовать это самое свойство
неизменяемости ...
V8 не использует, к сожалению.”
31
habrahabr.ru/company/jugru/blog/301040/#comment_9622474
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());
} ...
}
Но мы люди упоротые упорные
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());
} ...
}
Но мы люди упоротые упорные
Положительный эффект не замечен…
33
356ms – chatAt()
307ms – charCodeAt()
Результаты
на ~14% быстрее
Осторожней со строками
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());
} ...
}
Избегаем лишних чтений из строки
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%)
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;
}
}
...
}
Опасная операция!
38
Попытка чтения за пределами 

строки или массива 

приводит к деоптимизации
39
var codeNext = 0;
if (i + 1 < str.length) {
codeNext = str.charCodeAt(i + 1);
}
Избегаем выхода за пределы строки
292ms → 69 ms (в 4 раза быстрее!)
Меньше сравнений
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
} …
Много сравнений
42
var PUNCTUATION = {
9: 'Tab', // 't'
10: 'Newline', // 'n'
...
126: 'Tilde' // '~'
};
...
} else if (code in PUNCTUATION) {
// символ пунктуации
} ...
Проверка в словаре – очень дорого
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;
...
Создаем карту категорий
44
switch (code < CATEGORY_LENGTH ? CATEGORY[code] : 0) {
case DIGIT:
// число
case STRING:
// строка
case WHITESPACE:
// whitespace
case PUNCTUATOR:
// символ пунктуации
...
}
Уменьшаем число сравнений
69ms → 14 ms
(еще в 5 раз быстрее!)
Предварительные итоги
Результат: 356ms → 14 ms
Цифры получены на упрощенном
примере, на практике всё сложнее
Примеры тестов:
gist.github.com/lahmatiy/e124293c2f2b98e847d0f2bb54c2738e
46
47
• charAt() → charCodeAt()
• Избегайте лишних операций со строками
• Контролируйте выход за пределы
• Уменьшайте количество сравнений
Подводим итоги
Токен – не только строка
Нужна дополнительная информация
о токене: тип и локация
49
50
.foo {
width: 10px;
}
[
{
type: 'FullStop',
value: '.',
offset: 0,
line: 1,
column: 1
},
…
]
Как хранить?
51
Object Array
Понятнее Компактнее
{
type: 'FullStop',
value: '.',
offset: 1,
line: 2,
column: 3
}
['FullStop', '.', 1, 2, 3]
Размер токена
52
Object Array
64 байт 88 байт
Размер токена
52
Object Array
64 байт 88 байт
Наш CSS – 885Kb
256 163 токена
16,4 Мб 22,5 Мб
разница 6,1 Мб
Что быстрее?
53
Object Array
Время создания (литералов) сопоставимо,

меняется в зависимости от задачи
Обработка объектов (чтение) гораздо быстрее
Нет простого ответа
gist.github.com/lahmatiy/90fa91d5ea89465d08c7db1e591e91c2
Меняем подход
Последовательные токенайзер и
лексер – проще,
но ведет к лишним затратам памяти
и медленней
55
Scanner
(ленивый токенайзер)
56
57
scanner.token // текущий токен или null
scanner.next() // переход к следующему токену
scanner.lookup(N) // заглядывание вперед, возвращает
// токен на N-ой позиции от текущей
Основное API
58
• lookup(N)

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

делает shift из lookup буфера, если он не пустой,
либо читает новый токен
Создается столько же токенов, 

но нужно меньше памяти в один
момент времени
59
60
• Переиспользование объектов токенов
• Пул токенов фиксированного размера
Неудачные эксперименты
Выигрыш практически не ощущается, но код
гораздо сложнее и в разных случаях нужен пул
разного размера, иногда очень большой
Сборка дерева
62
• Избавление от look ahead
• Array → Object
• Array → List
• CST → AST
• …
Как ускорялся
Look ahead
64
function getSelector() {
var node = ['Selector'];
while (i < tokens.length) {
if (checkId()) {
node.push(getId());
} else if (checkClass()) {
node.push(getClass());
} else ...
}
return node;
}
Было
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 узел
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 узел, 

у них есть гарантия, 

что впереди подходящая
последовательность
токенов
67
• Многократный проход по списку токенов
• Нужны все токены сразу
• Большое дублирование кода (проверок)
• Сложно определить местоположение, в
случае синтаксической ошибки в CSS
Проблемы
68
StyleSheet
Ruleset
Selector
SimpleSelector
Id | Class | Attribute …
…
Block
…
…
Насколько все плохо
checkStyleSheet()
проверяла всю
последовательность
токенов, прежде чем
начиналась сборка AST
– 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
70
while (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.Hash: // #
node.push(getId());
break;
case TokenType.FullStop: // .
node.push(getClass());
break;
…
}
Стало
Быстрая проверка типа
токена. В большинстве
случае этого
достаточно, чтобы
выбрать подходящую
функцию сборки
71
function getPseudo() {
var next = scanner.lookup(1);
if (next.type === TokenType.Colon) {
return getPseudoElement();
}
return getPseudoClass();
}
Стало
Заглядывать вперед
нужно редко, в этих
случаях используется
scanner.lookup(N)
72
if (scanner.token.type !== TokenType.Something) {
throwError('Something wrong here');
}
Стало
Если встречается что-то "не то" – 

тут же выбрасывается сообщение об ошибке
73
• Значительно быстрее
• Используется ленивый токенайзер
• Код проще, мало дублирования
• Точное местоположение для ошибок в CSS
Сплошные плюсы
Array → Object
75
[ "stylesheet", [
"atrules",
[
"atkeyword",
[ "ident", "import" ]
],
[ "s", " " ],
[ "string", "'path/to/file.css'" ]
]
]
Было – массив массивов
76
{
"type": "StyleSheet",
"rules": [{
"type": "Atrule",
"name": "import",
"expression": {
"type": "AtruleExpression",
"sequence": [ ... ]
},
"block": null
}]
}
Стало
77
• Требуется меньше памяти
• Быстрее работа с узлами
• Проще писать/читать код 

(node[1] vs. node.value)
Результат
78
Помни – меньше мутаций!
function createNode() {
var node = {
type: 'Type'
};
if (expression1) {
node.foo = 123;
}
if (expression2) {
node.bar = true;
}
return node;
}
При добавлении нового
свойства создается новый
hidden class (здесь до 4).
Узел одного типа, 

но объекты разных классов –
принимающие функции
станут полиморфными.
79
Добавляется новое свойство
→ меняется hidden class
80
Смена hidden class
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
82
Все стало проще
И функции работающие с объектами 

только этого типа будут мономорфными
CST → AST
84
• AST (Abstract Syntax Tree)

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

логическая структура + информация о
форматировании
AST vs. CST
85
• AST – не нужно форматирование

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

linters, autoprefixer, postcss и друзья
AST vs. CST
Раньше парсер возвращал CST,
то есть сохранял все 

пробелы, комментарии и разделители
86
87
.foo > .bar,
.baz {
border: 1px solid red; // I'm useless comment
}
Отмеченное желтым – не нужно, 

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

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

(т.к. не требуются лишние проверки)
88
89
{
"type": "Dimension",
"value": {
"type": "Number",
"value": "123"
},
"unit": {
"type": "Identifier",
"name": "px"
}
}
Упрощен формат некоторых узлов
{
"type": "Dimension",
"value": "123",
"unit": "px"
}
90
• Меньше узлов в дереве
• Не нужен обход, чтобы удалить ненужное
• Многое проще не добавлять в дерево,
чем потом искать и удалять
Результат
91
• Наш CSS – 885Kb
• было узлов – 281 750
• стало узлов – 139 858
• Экономия памяти ~7Mb
• В два раза быстрее обход
В цифрах
Подводим итоги
93
• В ~5 раз быстрее
• В ~3 раза меньше потребление памяти
• Проще формат
• Дешевле обход и обработка AST
Улучшения парсера
Компрессор
95
• Однопроходная реструктуризация
• Уменьшение числа обходов
• Уменьшение глубины обхода
• Минимум вычислений, мутаций узлов, копирования
• Учет специфики CSS
• …
Как ускорялся
Однопроходная
реструктуризация
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;
}
Трансформируем
пока есть
положительный
эффект
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;
}
На каждой итерации
• копирование дерева
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;
}
На каждой итерации
• копирование дерева
• трансляция в строку
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
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
😱
Стало
102
restruct(ast);
• Не ошибка – один проход
• Нет копирования AST, трансляции всего дерева в строку
• Пришлось поменять алгоритмы
• Меньше нагрузка на GC, быстрее в 3-4 раза
Глубина обхода
104
Наш CSS – 885Kb
139 858 узлов
Холостой обход дерева
31 ms
Многим трансформациям
необходимо обходить лишь
Ruleset и AtRule
105
106
• walkAll() – обход всех узлов
• walkRules() – обход только правил и at-rules
• walkRulesRight() – обход только правил 

и at-rules в обратном порядке
Walker под задачу
107
Наш CSS – 139 858 узлов
walkAll()
31 ms
Холостой обход дерева
walkRules(), walkRulesRight()
2 ms
обходится 6 154 узла (4%)
Меньше обходов
109
walkAll(ast, function(node) {
if (node.type === 'Number') {
…
}
});
walkAll(ast, function(node) {
if (node.type === 'String') {
…
}
});
…
Было
Для каждого типа узла
отдельный обход
Как мы помним, для
нашего дерева
операция стоит 31 ms
110
walkAll(ast, function(node) {
if (node.type === 'Number') {
…
}
});
walkAll(ast, function(node) {
if (node.type === 'String') {
…
}
});
…
Было
Функции получают все
узлы дерева, а они
разных hidden class.
Так функции будут
полиморфными, хотя
работают (обычно)
только с одним типом
узлов
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);
}
});
Стало
Один проход
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);
}
});
Стало
Единственная
полиморфная функция,
но простая
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)
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);
}
});
Стало
По результатам
экспериментов, наиболее
быстрый вариант решения
Общий итог
116
• В 10+ раз быстрее
• В 7+ раз меньше потребление памяти
• В настоящий момент быстрее и эффективнее 

по ресурсам других структурных минификаторов 

(cssnano, clean-css)
Текущие результаты
goalsmashers.github.io/css-minification-benchmark/
Работы не закончены,
можно сделать еще лучше ;)
117
CSSO — сжимаем CSS
118
www.slideshare.net/basisjs/csso-css-2
Немного о принципах минификации, 

новых продвинутых оптимизациях и открытых вопросах
Бонус трек
Обкладывайте операции замерами
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
...
Hi-res timing
121
var startTime = process.hrtime();
// your code
var elapsed = process.hrtime(startTime);
console.log(parseInt(elapsed[0] * 1e3 + elapsed[1] / 1e6));
var startTime = performance.now();
// your code
console.log(performance.now() - startTime);
Node.js
Browser
Memory usage
122
var mem = process.memoryUsage().heapUsed;
// your code
console.log(process.memoryUsage().heapUsed - mem);
Очень грубый метод,
лучшего простого способа пока нет :'(
Привычные инструменты
devtool
124
Runs Node.js programs inside Chrome DevTools
npm install -g devtool
github.com/Jam3/devtool
> devtool your-script.js
125
Заглядывайте под капот
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
Получаем данные о работе кода
128
mrale.ph/irhydra/2/
code.asm
code.cfg
CSSO – история ускорения
Самое сложное научиться это
понимать ;)
130
Заключение
Вам это все не нужно,
но однажды может пригодиться
132
133
• Подбирать решение под задачу
• Меньше тратить памяти
• Меньше мутаций
• Проще структуры
• Разбираться как выполняется код
• Перестать думать, что вы умней VM и наоборот ;)
Капитанские советы
Нет серебряной пули –
экспериментируйте, заменяйте, 

смотрите под капот
134
Изучайте алгоритмы и структуры данных –
правильный их выбор может дать больше
чем оптимизация кода
135
Роман Дворнов
@rdvornov
rdvornov@gmail.com
Вопросы?
github.com/css/csso

Contenu connexe

Tendances

Основы индексирования и расширенные возможности EXPLAIN в MySQL / Василий Лук...
Основы индексирования и расширенные возможности EXPLAIN в MySQL / Василий Лук...Основы индексирования и расширенные возможности EXPLAIN в MySQL / Василий Лук...
Основы индексирования и расширенные возможности EXPLAIN в MySQL / Василий Лук...Ontico
 
Haskell
HaskellHaskell
HaskellDevDay
 
Developing highload servers with Java
Developing highload servers with JavaDeveloping highload servers with Java
Developing highload servers with JavaAndrei Pangin
 
Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“
Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“
Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“Platonov Sergey
 
Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиvictor-yastrebov
 
Суперсилы Chrome DevTools — Роман Сальников, 2ГИС
Суперсилы Chrome DevTools — Роман Сальников, 2ГИССуперсилы Chrome DevTools — Роман Сальников, 2ГИС
Суперсилы Chrome DevTools — Роман Сальников, 2ГИСYandex
 
Михаил Матросов, “С++ без new и delete”
Михаил Матросов, “С++ без new и delete”Михаил Матросов, “С++ без new и delete”
Михаил Матросов, “С++ без new и delete”Platonov Sergey
 
Очередной скучный доклад про логгирование
Очередной скучный доклад про логгированиеОчередной скучный доклад про логгирование
Очередной скучный доклад про логгированиеPython Meetup
 
SPA инструменты
SPA инструментыSPA инструменты
SPA инструментыRoman Dvornov
 
Caching data outside Java Heap and using Shared Memory in Java
Caching data outside Java Heap and using Shared Memory in JavaCaching data outside Java Heap and using Shared Memory in Java
Caching data outside Java Heap and using Shared Memory in JavaAndrei Pangin
 
Java tricks for high-load server programming
Java tricks for high-load server programmingJava tricks for high-load server programming
Java tricks for high-load server programmingAndrei Pangin
 
Семь тысяч Rps, один go
Семь тысяч Rps, один goСемь тысяч Rps, один go
Семь тысяч Rps, один goBadoo Development
 
20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonov20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonovComputer Science Club
 
Reform: путь к лучшему ORM
Reform: путь к лучшему ORMReform: путь к лучшему ORM
Reform: путь к лучшему ORMBadoo Development
 
Баба Яга против!
Баба Яга против!Баба Яга против!
Баба Яга против!Roman Dvornov
 
Михаил Щербаков "WinDbg сотоварищи"
Михаил Щербаков "WinDbg сотоварищи"Михаил Щербаков "WinDbg сотоварищи"
Михаил Щербаков "WinDbg сотоварищи"Mikhail Shcherbakov
 
Базы данных. Введение
Базы данных. ВведениеБазы данных. Введение
Базы данных. ВведениеVadim Tsesko
 
Deep Dive C# by Sergey Teplyakov
Deep Dive  C# by Sergey TeplyakovDeep Dive  C# by Sergey Teplyakov
Deep Dive C# by Sergey TeplyakovAlex Tumanoff
 

Tendances (20)

Основы индексирования и расширенные возможности EXPLAIN в MySQL / Василий Лук...
Основы индексирования и расширенные возможности EXPLAIN в MySQL / Василий Лук...Основы индексирования и расширенные возможности EXPLAIN в MySQL / Василий Лук...
Основы индексирования и расширенные возможности EXPLAIN в MySQL / Василий Лук...
 
Haskell
HaskellHaskell
Haskell
 
Developing highload servers with Java
Developing highload servers with JavaDeveloping highload servers with Java
Developing highload servers with Java
 
Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“
Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“
Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“
 
Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработки
 
Суперсилы Chrome DevTools — Роман Сальников, 2ГИС
Суперсилы Chrome DevTools — Роман Сальников, 2ГИССуперсилы Chrome DevTools — Роман Сальников, 2ГИС
Суперсилы Chrome DevTools — Роман Сальников, 2ГИС
 
Михаил Матросов, “С++ без new и delete”
Михаил Матросов, “С++ без new и delete”Михаил Матросов, “С++ без new и delete”
Михаил Матросов, “С++ без new и delete”
 
Очередной скучный доклад про логгирование
Очередной скучный доклад про логгированиеОчередной скучный доклад про логгирование
Очередной скучный доклад про логгирование
 
SPA инструменты
SPA инструментыSPA инструменты
SPA инструменты
 
Caching data outside Java Heap and using Shared Memory in Java
Caching data outside Java Heap and using Shared Memory in JavaCaching data outside Java Heap and using Shared Memory in Java
Caching data outside Java Heap and using Shared Memory in Java
 
Java tricks for high-load server programming
Java tricks for high-load server programmingJava tricks for high-load server programming
Java tricks for high-load server programming
 
Семь тысяч Rps, один go
Семь тысяч Rps, один goСемь тысяч Rps, один go
Семь тысяч Rps, один go
 
20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonov20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonov
 
Java 8 puzzlers
Java 8 puzzlersJava 8 puzzlers
Java 8 puzzlers
 
Progr labrab-4-2013-c++
Progr labrab-4-2013-c++Progr labrab-4-2013-c++
Progr labrab-4-2013-c++
 
Reform: путь к лучшему ORM
Reform: путь к лучшему ORMReform: путь к лучшему ORM
Reform: путь к лучшему ORM
 
Баба Яга против!
Баба Яга против!Баба Яга против!
Баба Яга против!
 
Михаил Щербаков "WinDbg сотоварищи"
Михаил Щербаков "WinDbg сотоварищи"Михаил Щербаков "WinDbg сотоварищи"
Михаил Щербаков "WinDbg сотоварищи"
 
Базы данных. Введение
Базы данных. ВведениеБазы данных. Введение
Базы данных. Введение
 
Deep Dive C# by Sergey Teplyakov
Deep Dive  C# by Sergey TeplyakovDeep Dive  C# by Sergey Teplyakov
Deep Dive C# by Sergey Teplyakov
 

En vedette

CSSO — сжимаем CSS (часть 2)
CSSO — сжимаем CSS (часть 2)CSSO — сжимаем CSS (часть 2)
CSSO — сжимаем CSS (часть 2)Roman Dvornov
 
DOM-шаблонизаторы – не только "быстро"
DOM-шаблонизаторы – не только "быстро"DOM-шаблонизаторы – не только "быстро"
DOM-шаблонизаторы – не только "быстро"Roman Dvornov
 
CSSO – compress CSS (english version)
CSSO – compress CSS (english version)CSSO – compress CSS (english version)
CSSO – compress CSS (english version)Roman Dvornov
 
Инструменты разные нужны, инструменты разные важны
Инструменты разные нужны, инструменты разные важныИнструменты разные нужны, инструменты разные важны
Инструменты разные нужны, инструменты разные важныRoman Dvornov
 
CSS parsing: performance tips & tricks
CSS parsing: performance tips & tricksCSS parsing: performance tips & tricks
CSS parsing: performance tips & tricksRoman Dvornov
 
Basis.js – «под капотом»
Basis.js – «под капотом»Basis.js – «под капотом»
Basis.js – «под капотом»Roman Dvornov
 
Карточный домик
Карточный домикКарточный домик
Карточный домикRoman Dvornov
 
Remote (dev)tools своими руками
Remote (dev)tools своими рукамиRemote (dev)tools своими руками
Remote (dev)tools своими рукамиRoman Dvornov
 
Жизнь в изоляции
Жизнь в изоляцииЖизнь в изоляции
Жизнь в изоляцииRoman Dvornov
 

En vedette (9)

CSSO — сжимаем CSS (часть 2)
CSSO — сжимаем CSS (часть 2)CSSO — сжимаем CSS (часть 2)
CSSO — сжимаем CSS (часть 2)
 
DOM-шаблонизаторы – не только "быстро"
DOM-шаблонизаторы – не только "быстро"DOM-шаблонизаторы – не только "быстро"
DOM-шаблонизаторы – не только "быстро"
 
CSSO – compress CSS (english version)
CSSO – compress CSS (english version)CSSO – compress CSS (english version)
CSSO – compress CSS (english version)
 
Инструменты разные нужны, инструменты разные важны
Инструменты разные нужны, инструменты разные важныИнструменты разные нужны, инструменты разные важны
Инструменты разные нужны, инструменты разные важны
 
CSS parsing: performance tips & tricks
CSS parsing: performance tips & tricksCSS parsing: performance tips & tricks
CSS parsing: performance tips & tricks
 
Basis.js – «под капотом»
Basis.js – «под капотом»Basis.js – «под капотом»
Basis.js – «под капотом»
 
Карточный домик
Карточный домикКарточный домик
Карточный домик
 
Remote (dev)tools своими руками
Remote (dev)tools своими рукамиRemote (dev)tools своими руками
Remote (dev)tools своими руками
 
Жизнь в изоляции
Жизнь в изоляцииЖизнь в изоляции
Жизнь в изоляции
 

Similaire à CSSO – история ускорения

Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"
Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"
Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"Fwdays
 
Tech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVMTech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVMTech Talks @NSU
 
Как приручить дракона: введение в LLVM
Как приручить дракона: введение в LLVMКак приручить дракона: введение в LLVM
Как приручить дракона: введение в LLVMTech Talks @NSU
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода Pavel Tsukanov
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кодаAndrey Karpov
 
практические советы по улучшению качества кода
практические советы по улучшению качества кодапрактические советы по улучшению качества кода
практические советы по улучшению качества кодаYuri Afanasiev
 
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Yandex
 
Опыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаОпыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаAndrey Karpov
 
Rambler.iOS #9: Анализируй это!
Rambler.iOS #9: Анализируй это!Rambler.iOS #9: Анализируй это!
Rambler.iOS #9: Анализируй это!RAMBLER&Co
 
Статический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минутСтатический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минутAndrey Karpov
 
Кодогенерация на службе оптимизации, Игорь Чевдарь, СКБ Контур
 Кодогенерация на службе оптимизации, Игорь Чевдарь, СКБ Контур  Кодогенерация на службе оптимизации, Игорь Чевдарь, СКБ Контур
Кодогенерация на службе оптимизации, Игорь Чевдарь, СКБ Контур it-people
 
Улучшение качества открытого программного обеспечения с помощью инструментов ...
Улучшение качества открытого программного обеспечения с помощью инструментов ...Улучшение качества открытого программного обеспечения с помощью инструментов ...
Улучшение качества открытого программного обеспечения с помощью инструментов ...Andrey Karpov
 
Оптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templatesОптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templatesPlatonov Sergey
 
Оптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templatesОптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templatesPlatonov Sergey
 
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru GroupКак не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru GroupMail.ru Group
 
основы Java переменные, циклы
основы Java   переменные, циклыосновы Java   переменные, циклы
основы Java переменные, циклыSergey Nemchinsky
 
Принципы работы статического анализатора кода PVS-Studio
Принципы работы статического анализатора кода PVS-StudioПринципы работы статического анализатора кода PVS-Studio
Принципы работы статического анализатора кода PVS-StudioAndrey Karpov
 
разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3Eugeniy Tyumentcev
 

Similaire à CSSO – история ускорения (20)

Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"
Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"
Aleksei Milovidov "Let's optimize one aggregate function in ClickHouse"
 
Tech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVMTech Talks @NSU: Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVM
 
Как приручить дракона: введение в LLVM
Как приручить дракона: введение в LLVMКак приручить дракона: введение в LLVM
Как приручить дракона: введение в LLVM
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кода
 
практические советы по улучшению качества кода
практические советы по улучшению качества кодапрактические советы по улучшению качества кода
практические советы по улучшению качества кода
 
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
 
Опыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаОпыт разработки статического анализатора кода
Опыт разработки статического анализатора кода
 
Rambler.iOS #9: Анализируй это!
Rambler.iOS #9: Анализируй это!Rambler.iOS #9: Анализируй это!
Rambler.iOS #9: Анализируй это!
 
Статический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минутСтатический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минут
 
Кодогенерация на службе оптимизации, Игорь Чевдарь, СКБ Контур
 Кодогенерация на службе оптимизации, Игорь Чевдарь, СКБ Контур  Кодогенерация на службе оптимизации, Игорь Чевдарь, СКБ Контур
Кодогенерация на службе оптимизации, Игорь Чевдарь, СКБ Контур
 
Улучшение качества открытого программного обеспечения с помощью инструментов ...
Улучшение качества открытого программного обеспечения с помощью инструментов ...Улучшение качества открытого программного обеспечения с помощью инструментов ...
Улучшение качества открытого программного обеспечения с помощью инструментов ...
 
PascalABC.NET 2015-2016
PascalABC.NET 2015-2016PascalABC.NET 2015-2016
PascalABC.NET 2015-2016
 
Оптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templatesОптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templates
 
Оптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templatesОптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templates
 
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru GroupКак не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
 
основы Java переменные, циклы
основы Java   переменные, циклыосновы Java   переменные, циклы
основы Java переменные, циклы
 
Принципы работы статического анализатора кода PVS-Studio
Принципы работы статического анализатора кода PVS-StudioПринципы работы статического анализатора кода PVS-Studio
Принципы работы статического анализатора кода PVS-Studio
 
Parallel STL
Parallel STLParallel STL
Parallel STL
 
разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3
 

Plus de Roman Dvornov

Unit-тестирование скриншотами: преодолеваем звуковой барьер
Unit-тестирование скриншотами: преодолеваем звуковой барьерUnit-тестирование скриншотами: преодолеваем звуковой барьер
Unit-тестирование скриншотами: преодолеваем звуковой барьерRoman Dvornov
 
Масштабируемая архитектура фронтенда
Масштабируемая архитектура фронтендаМасштабируемая архитектура фронтенда
Масштабируемая архитектура фронтендаRoman Dvornov
 
CSS глазами машин
CSS глазами машинCSS глазами машин
CSS глазами машинRoman Dvornov
 
My Open Source (Sept 2017)
My Open Source (Sept 2017)My Open Source (Sept 2017)
My Open Source (Sept 2017)Roman Dvornov
 
Rempl – крутая платформа для крутых инструментов
Rempl – крутая платформа для крутых инструментовRempl – крутая платформа для крутых инструментов
Rempl – крутая платформа для крутых инструментовRoman Dvornov
 
Как построить DOM
Как построить DOMКак построить DOM
Как построить DOMRoman Dvornov
 
Компонентный подход: скучно, неинтересно, бесперспективно
Компонентный подход: скучно, неинтересно, бесперспективноКомпонентный подход: скучно, неинтересно, бесперспективно
Компонентный подход: скучно, неинтересно, бесперспективноRoman Dvornov
 
Basis.js - почему я не бросил разрабатывать свой фреймворк (extended)
Basis.js - почему я не бросил разрабатывать свой фреймворк (extended)Basis.js - почему я не бросил разрабатывать свой фреймворк (extended)
Basis.js - почему я не бросил разрабатывать свой фреймворк (extended)Roman Dvornov
 
basis.js - почему я не бросил разрабатывать свой фреймворк
basis.js - почему я не бросил разрабатывать свой фреймворкbasis.js - почему я не бросил разрабатывать свой фреймворк
basis.js - почему я не бросил разрабатывать свой фреймворкRoman Dvornov
 

Plus de Roman Dvornov (10)

Unit-тестирование скриншотами: преодолеваем звуковой барьер
Unit-тестирование скриншотами: преодолеваем звуковой барьерUnit-тестирование скриншотами: преодолеваем звуковой барьер
Unit-тестирование скриншотами: преодолеваем звуковой барьер
 
Масштабируемая архитектура фронтенда
Масштабируемая архитектура фронтендаМасштабируемая архитектура фронтенда
Масштабируемая архитектура фронтенда
 
CSS глазами машин
CSS глазами машинCSS глазами машин
CSS глазами машин
 
My Open Source (Sept 2017)
My Open Source (Sept 2017)My Open Source (Sept 2017)
My Open Source (Sept 2017)
 
Rempl – крутая платформа для крутых инструментов
Rempl – крутая платформа для крутых инструментовRempl – крутая платформа для крутых инструментов
Rempl – крутая платформа для крутых инструментов
 
Component Inspector
Component InspectorComponent Inspector
Component Inspector
 
Как построить DOM
Как построить DOMКак построить DOM
Как построить DOM
 
Компонентный подход: скучно, неинтересно, бесперспективно
Компонентный подход: скучно, неинтересно, бесперспективноКомпонентный подход: скучно, неинтересно, бесперспективно
Компонентный подход: скучно, неинтересно, бесперспективно
 
Basis.js - почему я не бросил разрабатывать свой фреймворк (extended)
Basis.js - почему я не бросил разрабатывать свой фреймворк (extended)Basis.js - почему я не бросил разрабатывать свой фреймворк (extended)
Basis.js - почему я не бросил разрабатывать свой фреймворк (extended)
 
basis.js - почему я не бросил разрабатывать свой фреймворк
basis.js - почему я не бросил разрабатывать свой фреймворкbasis.js - почему я не бросил разрабатывать свой фреймворк
basis.js - почему я не бросил разрабатывать свой фреймворк
 

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

  • 1. CSSO — история ускорения Роман Дворнов Avito HolyJS Санкт-Петербург, 2016
  • 2. Работаю в Avito Делаю SPA Автор basis.js Мейнтенер CSSO За любую движуху, 
 кроме голодовки ;)
  • 3. ВремясжатияCSS(600Kb) 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 ms clean-css Изменение скорости CSSO csso 500 ms cssnano 23 250 ms
  • 5. 5 clean-css 3.4.16 cssnano 3.6.2 csso 2.1.1 ноябрь 2015 602 245 байт 430 240 2 039 ms 439 210 41 355 ms 435 629 714 ms апрель 2016 822 021 байт 587 906 2 546 ms 604 167 76 665 ms 595 834 793 ms июнь 2016 883 498 байт 632 007 2 588 ms 648 579 94 867 ms 639 495 803 ms Время минификации CSS Основной CSS файл ActiAgent.ru
  • 6. Тренд изменения времени от размера 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%
  • 7. 7 Новые продвинутые оптимизации (usage data, rename, etc) 245 823 Время Размер (байт) 639 495 803 ms 2 513 ms Лучше сжатие, но требует 2-3х больше времени
  • 8. 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 больших) время минификации всех файлов
  • 9. 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 + продвинутые оптимизации
  • 18. Основные шаги • Токенизация • Построение дерева (лексер) 11
  • 20. 13 • whitespaces – [ nrtf]+ • keyword – [a-zA-aZ…]+ • number – [0-9]+ • string – "string" или 'string' • comment – /* comment */ • punctuation – [;,.#{}[]()…] Разбиение текста на токены
  • 21. 14 .foo { width: 10px; } [ '.', 'foo', ' ', '{', 'n ', 'width', ':', ' ', '10', 'px', ';', 'n', '}' ]
  • 23. 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()); } ... } Ключевой цикл токенайзера
  • 24. 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()); } ... } Ключевой цикл токенайзера
  • 25. 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()); } ... } Ключевой цикл токенайзера
  • 26. 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()); } ... } Ключевой цикл токенайзера
  • 27. 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()); } ... } Ключевой цикл токенайзера
  • 28. Обычный код – простор для потери быстродействия 21
  • 30. 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()); } ... } Сравнение символов
  • 31. 24 • Строка – последовательность чисел • Извлечение символа = получение новой строки • Сравнение символов ≈ сравнение строк Нужно помнить
  • 33. 26 // выносим коды символов в константы var STAR = 42; var SLASH = 47; var ZERO = 48; var NINE = 57; ... Делаем быстрее
  • 34. 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() + числовые константы
  • 37. Используй 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; ...
  • 39. – Вячеслав Егоров “... [const] это все-таки неизменяемая привязка переменной к значению ... С другой стороны виртуальная машина может и должна бы использовать это самое свойство неизменяемости ... V8 не использует, к сожалению.” 31 habrahabr.ru/company/jugru/blog/301040/#comment_9622474
  • 40. 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()); } ... } Но мы люди упоротые упорные
  • 41. 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()); } ... } Но мы люди упоротые упорные Положительный эффект не замечен…
  • 42. 33 356ms – chatAt() 307ms – charCodeAt() Результаты на ~14% быстрее
  • 44. 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()); } ... } Избегаем лишних чтений из строки
  • 45. 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%)
  • 46. 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; } } ... } Опасная операция!
  • 47. 38 Попытка чтения за пределами 
 строки или массива 
 приводит к деоптимизации
  • 48. 39 var codeNext = 0; if (i + 1 < str.length) { codeNext = str.charCodeAt(i + 1); } Избегаем выхода за пределы строки 292ms → 69 ms (в 4 раза быстрее!)
  • 50. 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 } … Много сравнений
  • 51. 42 var PUNCTUATION = { 9: 'Tab', // 't' 10: 'Newline', // 'n' ... 126: 'Tilde' // '~' }; ... } else if (code in PUNCTUATION) { // символ пунктуации } ... Проверка в словаре – очень дорого
  • 52. 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; ... Создаем карту категорий
  • 53. 44 switch (code < CATEGORY_LENGTH ? CATEGORY[code] : 0) { case DIGIT: // число case STRING: // строка case WHITESPACE: // whitespace case PUNCTUATOR: // символ пунктуации ... } Уменьшаем число сравнений 69ms → 14 ms (еще в 5 раз быстрее!)
  • 55. Результат: 356ms → 14 ms Цифры получены на упрощенном примере, на практике всё сложнее Примеры тестов: gist.github.com/lahmatiy/e124293c2f2b98e847d0f2bb54c2738e 46
  • 56. 47 • charAt() → charCodeAt() • Избегайте лишних операций со строками • Контролируйте выход за пределы • Уменьшайте количество сравнений Подводим итоги
  • 57. Токен – не только строка
  • 58. Нужна дополнительная информация о токене: тип и локация 49
  • 59. 50 .foo { width: 10px; } [ { type: 'FullStop', value: '.', offset: 0, line: 1, column: 1 }, … ]
  • 60. Как хранить? 51 Object Array Понятнее Компактнее { type: 'FullStop', value: '.', offset: 1, line: 2, column: 3 } ['FullStop', '.', 1, 2, 3]
  • 62. Размер токена 52 Object Array 64 байт 88 байт Наш CSS – 885Kb 256 163 токена 16,4 Мб 22,5 Мб разница 6,1 Мб
  • 63. Что быстрее? 53 Object Array Время создания (литералов) сопоставимо,
 меняется в зависимости от задачи Обработка объектов (чтение) гораздо быстрее Нет простого ответа gist.github.com/lahmatiy/90fa91d5ea89465d08c7db1e591e91c2
  • 65. Последовательные токенайзер и лексер – проще, но ведет к лишним затратам памяти и медленней 55
  • 67. 57 scanner.token // текущий токен или null scanner.next() // переход к следующему токену scanner.lookup(N) // заглядывание вперед, возвращает // токен на N-ой позиции от текущей Основное API
  • 68. 58 • lookup(N)
 заполняет буфер токенов до позиции N, если еще не заполнен, возвращает N-1 токен из буфера • next()
 делает shift из lookup буфера, если он не пустой, либо читает новый токен
  • 69. Создается столько же токенов, 
 но нужно меньше памяти в один момент времени 59
  • 70. 60 • Переиспользование объектов токенов • Пул токенов фиксированного размера Неудачные эксперименты Выигрыш практически не ощущается, но код гораздо сложнее и в разных случаях нужен пул разного размера, иногда очень большой
  • 72. 62 • Избавление от look ahead • Array → Object • Array → List • CST → AST • … Как ускорялся
  • 74. 64 function getSelector() { var node = ['Selector']; while (i < tokens.length) { if (checkId()) { node.push(getId()); } else if (checkClass()) { node.push(getClass()); } else ... } return node; } Было
  • 75. 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 узел
  • 76. 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 узел, 
 у них есть гарантия, 
 что впереди подходящая последовательность токенов
  • 77. 67 • Многократный проход по списку токенов • Нужны все токены сразу • Большое дублирование кода (проверок) • Сложно определить местоположение, в случае синтаксической ошибки в CSS Проблемы
  • 78. 68 StyleSheet Ruleset Selector SimpleSelector Id | Class | Attribute … … Block … … Насколько все плохо checkStyleSheet() проверяла всю последовательность токенов, прежде чем начиналась сборка AST
  • 79. – 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
  • 80. 70 while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.Hash: // # node.push(getId()); break; case TokenType.FullStop: // . node.push(getClass()); break; … } Стало Быстрая проверка типа токена. В большинстве случае этого достаточно, чтобы выбрать подходящую функцию сборки
  • 81. 71 function getPseudo() { var next = scanner.lookup(1); if (next.type === TokenType.Colon) { return getPseudoElement(); } return getPseudoClass(); } Стало Заглядывать вперед нужно редко, в этих случаях используется scanner.lookup(N)
  • 82. 72 if (scanner.token.type !== TokenType.Something) { throwError('Something wrong here'); } Стало Если встречается что-то "не то" – 
 тут же выбрасывается сообщение об ошибке
  • 83. 73 • Значительно быстрее • Используется ленивый токенайзер • Код проще, мало дублирования • Точное местоположение для ошибок в CSS Сплошные плюсы
  • 85. 75 [ "stylesheet", [ "atrules", [ "atkeyword", [ "ident", "import" ] ], [ "s", " " ], [ "string", "'path/to/file.css'" ] ] ] Было – массив массивов
  • 86. 76 { "type": "StyleSheet", "rules": [{ "type": "Atrule", "name": "import", "expression": { "type": "AtruleExpression", "sequence": [ ... ] }, "block": null }] } Стало
  • 87. 77 • Требуется меньше памяти • Быстрее работа с узлами • Проще писать/читать код 
 (node[1] vs. node.value) Результат
  • 88. 78 Помни – меньше мутаций! function createNode() { var node = { type: 'Type' }; if (expression1) { node.foo = 123; } if (expression2) { node.bar = true; } return node; } При добавлении нового свойства создается новый hidden class (здесь до 4). Узел одного типа, 
 но объекты разных классов – принимающие функции станут полиморфными.
  • 91. 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
  • 92. 82 Все стало проще И функции работающие с объектами 
 только этого типа будут мономорфными
  • 94. 84 • AST (Abstract Syntax Tree)
 логическая структура • CST (Concrete Syntax Tree)
 логическая структура + информация о форматировании AST vs. CST
  • 95. 85 • AST – не нужно форматирование
 linters, minifiers, beautifiers, … • CST – форматирование важно
 linters, autoprefixer, postcss и друзья AST vs. CST
  • 96. Раньше парсер возвращал CST, то есть сохранял все 
 пробелы, комментарии и разделители 86
  • 97. 87 .foo > .bar, .baz { border: 1px solid red; // I'm useless comment } Отмеченное желтым – не нужно, 
 но сохранялось в дереве, а компрессор это все удалял
  • 98. Так же теперь парсер не собирает заведомо неверные конструкции, 
 что упрощает и ускоряет дальнейшую работу с деревом 
 (т.к. не требуются лишние проверки) 88
  • 99. 89 { "type": "Dimension", "value": { "type": "Number", "value": "123" }, "unit": { "type": "Identifier", "name": "px" } } Упрощен формат некоторых узлов { "type": "Dimension", "value": "123", "unit": "px" }
  • 100. 90 • Меньше узлов в дереве • Не нужен обход, чтобы удалить ненужное • Многое проще не добавлять в дерево, чем потом искать и удалять Результат
  • 101. 91 • Наш CSS – 885Kb • было узлов – 281 750 • стало узлов – 139 858 • Экономия памяти ~7Mb • В два раза быстрее обход В цифрах
  • 103. 93 • В ~5 раз быстрее • В ~3 раза меньше потребление памяти • Проще формат • Дешевле обход и обработка AST Улучшения парсера
  • 105. 95 • Однопроходная реструктуризация • Уменьшение числа обходов • Уменьшение глубины обхода • Минимум вычислений, мутаций узлов, копирования • Учет специфики CSS • … Как ускорялся
  • 107. 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; } Трансформируем пока есть положительный эффект
  • 108. 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; } На каждой итерации • копирование дерева
  • 109. 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; } На каждой итерации • копирование дерева • трансляция в строку
  • 110. 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
  • 111. 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 😱
  • 112. Стало 102 restruct(ast); • Не ошибка – один проход • Нет копирования AST, трансляции всего дерева в строку • Пришлось поменять алгоритмы • Меньше нагрузка на GC, быстрее в 3-4 раза
  • 114. 104 Наш CSS – 885Kb 139 858 узлов Холостой обход дерева 31 ms
  • 116. 106 • walkAll() – обход всех узлов • walkRules() – обход только правил и at-rules • walkRulesRight() – обход только правил 
 и at-rules в обратном порядке Walker под задачу
  • 117. 107 Наш CSS – 139 858 узлов walkAll() 31 ms Холостой обход дерева walkRules(), walkRulesRight() 2 ms обходится 6 154 узла (4%)
  • 119. 109 walkAll(ast, function(node) { if (node.type === 'Number') { … } }); walkAll(ast, function(node) { if (node.type === 'String') { … } }); … Было Для каждого типа узла отдельный обход Как мы помним, для нашего дерева операция стоит 31 ms
  • 120. 110 walkAll(ast, function(node) { if (node.type === 'Number') { … } }); walkAll(ast, function(node) { if (node.type === 'String') { … } }); … Было Функции получают все узлы дерева, а они разных hidden class. Так функции будут полиморфными, хотя работают (обычно) только с одним типом узлов
  • 121. 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); } }); Стало Один проход
  • 122. 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); } }); Стало Единственная полиморфная функция, но простая
  • 123. 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)
  • 124. 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); } }); Стало По результатам экспериментов, наиболее быстрый вариант решения
  • 126. 116 • В 10+ раз быстрее • В 7+ раз меньше потребление памяти • В настоящий момент быстрее и эффективнее 
 по ресурсам других структурных минификаторов 
 (cssnano, clean-css) Текущие результаты goalsmashers.github.io/css-minification-benchmark/
  • 127. Работы не закончены, можно сделать еще лучше ;) 117
  • 128. CSSO — сжимаем CSS 118 www.slideshare.net/basisjs/csso-css-2 Немного о принципах минификации, 
 новых продвинутых оптимизациях и открытых вопросах
  • 130. Обкладывайте операции замерами 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 ...
  • 131. Hi-res timing 121 var startTime = process.hrtime(); // your code var elapsed = process.hrtime(startTime); console.log(parseInt(elapsed[0] * 1e3 + elapsed[1] / 1e6)); var startTime = performance.now(); // your code console.log(performance.now() - startTime); Node.js Browser
  • 132. Memory usage 122 var mem = process.memoryUsage().heapUsed; // your code console.log(process.memoryUsage().heapUsed - mem); Очень грубый метод, лучшего простого способа пока нет :'(
  • 134. devtool 124 Runs Node.js programs inside Chrome DevTools npm install -g devtool github.com/Jam3/devtool
  • 137. 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 Получаем данные о работе кода
  • 140. Самое сложное научиться это понимать ;) 130
  • 142. Вам это все не нужно, но однажды может пригодиться 132
  • 143. 133 • Подбирать решение под задачу • Меньше тратить памяти • Меньше мутаций • Проще структуры • Разбираться как выполняется код • Перестать думать, что вы умней VM и наоборот ;) Капитанские советы
  • 144. Нет серебряной пули – экспериментируйте, заменяйте, 
 смотрите под капот 134
  • 145. Изучайте алгоритмы и структуры данных – правильный их выбор может дать больше чем оптимизация кода 135