Defront — про фронтенд-разработку и не только
12.8K subscribers
21 photos
1.09K links
Ламповый канал про фронтенд и не только. Всё самое полезное для опытных web-разработчиков

Обсуждение постов @defrontchat

Также советую канал @webnya
Download Telegram
Внезапно меня занесло в баг-трекер v8, где я увидел очень интересный пропозал от разработчика из Microsoft.

Как оказалось в v8 оператор in не оптимизирован. Все его вызовы приводят к дорогостоящим рантайм проверкам. В пропозале идёт речь про переход к использованию инлайн кешей и jit-оптимизаций при использовании in. Но есть известные проблемы, когда перебираются все ключи объекта, в этом случае оптимизаций нет.

Были произведены замеры с тестовой реализацией v8 на методе Array.prototype.indexOf, который вызывался 10 раз у массива с 10 миллионами элементов. Результаты хорошие - заметно ускорение в 4-10 раз на разных типах массивов.

#js #v8 #performance

https://docs.google.com/document/d/1tIfzywY8AeNVcy_sen-5Xev21MeZwjcU8QhSdzHvXig/edit
Бенедикт Мюрер - разработчик v8 - пару дней назад опубликовал дизайн-документ, посвящённый оптимизации конкатенации строк в JS.

Конкатенация строк - одна из основных операций над строками, у которой, как оказывается, есть хороший потенциал для ускорения. Если конкатенация будет оптимизирована, то решения для сервер сайд рендеринга станут ещё быстрее (именно на этом примере Бенедикт показывает важность оптимизации).

В документе также рассказывается про текущий механизм оптимизации строк - "ropes". Приводится пример того, как можно написать один вид конкатенации в двух разных вариантах: оптимизированном и неоптимизированном. Но разработчики v8 предупреждают, чтобы мы не кидались переписывать свой код. Движки будут учиться конкатенировать строки более эффективно самостоятельно, если с этим возникнут проблемы, тогда (возможно) в языке появится новый StringBuilder API.

#js #v8 #strings #future #performance

https://twitter.com/bmeurer/status/1102452256794906625
Вчера в блоге v8 в статье "Code caching for JavaScript developers" Лесджек Свирски рассказал про то, как работает кэширование кода на уровне JS-движка.

Есть два подхода к кэшированию кода в v8:
1. Isolate кэш, который находится в памяти в рамках одного процесса (вкладки браузера).
2. On-disk кэш, в котором находится скомпилированный код, использующийся разными вкладками.

Если скомпилированный JS-код находится в кэше, браузер не тратит время на повторную компиляцию. Это хорошо сказывается на времени запуска приложения. Поэтому Лесджек советует делать всё возможное, чтобы файлы менялись как можно реже, не менять url, по которым они доступны (url используется в качестве ключа для хэша, в котором лежит скомпилированный код) и отдавать 304-ый код для скриптов, которые не были изменены.

При этом можно сделать так, чтобы определённый код попал в кэш. Например, файлы меньше 1kb не попадают в кэш, поэтому можно объединить несколько таких файлов в один бандл. Также можно позаботиться о том, чтобы кэш не инвалидировался лишний раз и вынести весь библиотечный код в один бандл, а бизнес-логику в другой. Есть и другие трюки, например, с сервис воркерами, но при этом не гарантируется, что эвристика, определяющая необходимость в кэшировании, будет стабильна и что перечисленные хаки из статьи будут работать в будущем.

#v8 #performance

https://v8.dev/blog/code-caching-for-devs
Сатья Гунасекеран из команды разработчиков v8 сегодня твитнул про, то что в новом релизе Chrome 75 была добавлена реализация предложения TC39 Numeric Separators (stage 3).

С помощью этой фичи можно записывать числовые литералы в удобном виде, например, отделяя сотни от тысяч. В качестве разделителя используется символ подчёркивания. Подчёркивание может находится только между двумя цифрами, то есть его нельзя использовать в начале или конце числа.

const million = 1_000_000;
const billion = 1_000_000_000;


В твите есть ссылка на хорошую статью Акселя Раушмаера про новое предложение с разными примерами его использования и замечаниями, в каких случаях разделители в числах лучше не использовать.

#v8 #proposal #js

https://twitter.com/_gsathya/status/1120389255619055616?s=21
Рич Снапп написал статью с объяснением того, почему лучше отказаться от использования reduce вместе со spread объектов — "The reduce ({...spread}) anti-pattern".

В современном JavaScript код для создания объекта из массива объектов с одинаковыми свойствами можно написать так:
let result = items.reduce((acc, item) => ({
...acc, [item.name]: item.value
}), {})


Как оказывается в v8 такой код будет выполняться с квадратичной сложностью. Рич доказывает существование этой проблемы, разбирая байткод, который генерирует движок. В качестве альтернативы он предлагает использовать reduce с мутированием, или for...of, которые справляются с этой задачей за линейное время.

Если у вас в проекте используется подобный код, то по крайней мере стоит убедиться, что он ни на что не влияет. Но я бы его просто переписал, потому что он плохо читается (имхо, конечно).

#v8 #performance #algorithm

https://www.richsnapp.com/blog/2019/06-09-reduce-spread-anti-pattern
Эдди Османи написал новую статью про актуальные способы ускорения загрузки JavaScript и сокращения времени его инициализации — "The cost of JavaScript in 2019".

Вот, что мне показалось наиболее интересным. В Chrome 41 появился асинхронный парсинг и исполнение скриптов во время их загрузки (script streaming). На реальной практике это приводит к тому, что V8 парсит код быстрее его загрузки — парсинг с компиляцией завершается через несколько миллисекунд после того как заканчивается скачивание кода. В Chrome 71 был сделан ещё один шаг к ускорению — произошёл переход на task-based подход, с помощью которого async/deferred скрипты теперь парсятся параллельно.

Последние два года команда V8 очень много занималась оптимизацией парсера, он больше не является бутылочным горлышком. На сегодняшний день при оптимизации сайтов стоит прикладывать усилия к снижению времени исполнения кода в главном потоке. В качестве примера приводится Facebook. Благодаря комбинации code splitting (около 300 бандлов по ~30KB) и HTTP/2 на основном сайте социальной сети всего 30% кода выполняется в главном потоке, остальные 70% в так называемых Worker Threads, что приводит к снижению Time To Interactive (TTI).

В статье есть ссылка на видео с докладом Эдди, на основе которого была написана статья. В этом видео есть примеры более агрессивных оптимизаций. Например, Netflix чтобы ускорить свою приветственную страницу оторвал React и другие библиотеки, заменив их ванильным JavaScript. Индонезийский e-commerce сайт Tokopedia заменил React на Svelte для своих посадочных страниц. Размер бандла уменьшился с 320KB до 73KB. При этом и Netflix, и Tokopedia в фоне загружают бандлы с React, которые необходимы для других страниц.

Статья крутая. Советую почитать, если вам интересно узнать про современные подходы оптимизации JavaScript.

#js #performance #v8 #chrome

https://v8.dev/blog/cost-of-javascript-2019
Это, наверное, уже не новость, но вдруг вы не знали. В блоге v8, есть специальный раздел с описанием новых фич JavaScript и WebAssembly, которые планируется добавить в будущие версии языка и платформы.

С начала года там появилось шесть новых статей:
- String.prototype.matchAll
- Numeric separators
- Array.prototype.flat and Array.prototype.flatMap
- Promise combinators
- Object.fromEntries
- Symbol.prototype.description

Во всех статьях есть краткое объяснение работы новых фич и много примеров. По WebAssembly пока нет ни одной статьи, но это не снижает ценность ресурса.

#js #v8 #list

https://v8.dev/features
Эрик Кори — создатель библиотеки Irregexp, которая используется в Chrome, node.js и Firefox — написал статью про возможные оптимизации в движке регулярных выражений "Regexp backtracking in loops, and how we can optimize it away".

Эрик в начале статьи описывает то, как можно реализовать бэктрекинг (backtracking) с использованием стека. Он рассказывает, что это ведёт к большому потреблению памяти на длинных строках и что все движки регулярных выражений содержат обработчики специальных случаев, которые позволяют избавиться от этой проблемы. Эрик выдвигает предположение, что если бы движок мог распознавать naturally possessive квантификаторы, это бы позволило ещё больше сократить размер памяти необходимой для бэктрекинга.

Статья непростая, но её стоит почитать, если вам интересно узнать про текущие оптимизации в движках регулярных выражений и про оптимизации, которые возможно будут работать в следующих версиях Irregexp.

#v8 #regexp #performance

https://medium.com/@erik_68861/regexp-backtracking-in-loops-and-how-we-can-optimize-it-away-ef3b2590f87e
Недавно в блоге v8 Бенедикт Мойрер и Матиас Байненс написали пост про расследование причин деградации производительности в React — "The story of a V8 performance cliff in React".

В декабре прошлого года разработчики React столкнулись со странным поведением v8. Если было запущено профилирование, то падала производительность кода во время фазы commit. Как оказалось, проблема заключалась в следующем коде:
class FiberNode {
constructor() {
this.actualStartTime = 0;
Object.preventExtensions(this);
}
}

const node1 = new FiberNode();
const node2 = new FiberNode();


V8 внутри использует разные представления для чисел. Для 32-битных целых чисел используется small integer (Smi), для чисел с плавающей запятой — HeapNumber и MutableHeapNumber. Для создаваемых объектов v8 применяет оптимизации для снижения потребления памяти. Одна из таких оптимизаций гарантирует эффективное переиспользование памяти, если создаются похожие друг на друга объекты.

В коде класса FiberNode, который работал во время профилирования, значение поля объекта, на котором был применён preventExtensions, менялось со Smi на HeapNumber. Этот кейс не был учён в v8, и движок начинал аллоцировать дополнительную память. Видимая просадка производительности происходила из-за того, что в реальном React-приложении создаётся десятки тысяч объектов такого типа.

Баг был исправлен в v8, но разработчики React смогли устранить проблему раньше на своей стороне. Для этого они стали инициализировать поле объекта HeapNumber'ом (this.actualStartTime = NaN). В конце статьи Бенедикт и Матиас рекомендуют инициализировать поля объекта такими значениями, внутреннее представление которых не будет меняться со временем.

Мне статья понравилась. Рекомендую, прочитать всем, кто интересуется внутренностями v8.

#v8 #internals #performance

https://v8.dev/blog/react-cliff
Сегодня будет ещё один релизный пост. Пару часов назад в блоге V8 появился анонс фич, которые попадут в версию 7.8.

Script streaming — загрузка скриптов из сети в парсер без ожидания главного потока Chrome — теперь будет работать для preload-скриптов (в Chrome 76 и выше). Эта фича позволит сократить время инициализации страницы.

При компиляции JavaScript в байткод движок собирает специальные таблицы, которые матчат байткод на конкретные позиции в исходном коде. Эти таблицы потребляют память. С версии 7.8 они будут генерироваться, только во время создания стектрейсов. Разработчики пошли на компромисс, так как эта операция требует повторного парсинга и перекомпиляции. В результате потребление памяти было уменьшено на 1-2.5%.

Ускорили деструктуризацию объектов и работу регулярных выражений в случае, когда искомый паттерн не может быть найден в строке.

Поддержка Wasm С/C++ API перешла из экспериментального статуса в официальный. Это API позволяет использовать V8 как движок для исполнения WebAssembly в C/C++-приложениях. Был ускорен старт wasm-модулей.

#release #v8

https://v8.dev/blog/v8-release-78
Начиная с версии 7.9 в V8 изменяется работа с регулярными выражениями. Патрик Тиер и Ана Пешко написали пост с объяснением всех деталей — "Improving V8 regular expressions".

По умолчанию V8 компилирует регулярные выражения в нативный код при их первом запуске. Это влечёт за собой нагрузку на память. Около полугода назад как часть работы над "JIT-less V8" был добавлен интерпретатор для регулярных выражений. С версии 7.9 по умолчанию регулярные выражения будут исполняться с его помощью. Компиляция будет происходить только после того как одно и то же регулярное выражение будет выполняться несколько раз (hot-path). Новая стратегия позволяет уменьшить потребление оперативной памяти на 4-7%. Интерпретатор регулярных выражений был оптимизирован, теперь он в 2 раза быстрее. Это позволило не сильно просадить метрики по скорости относительно скомпилированных регулярок.

В статье много технических деталей того, как это всё работает. Читать сложно, но интересно.

#v8 #regexp #performance

https://v8.dev/blog/regexp-tier-up
Сегодня увидел в твиттере новость — в V8 v7.9 появилась реализация пропозала "RegExp Match Indices" (пока спрятана за экспериментальным флагом). Майа Лекова из команды разработки движка написала статью с объяснением, где он может быть полезен.

Методы Regexp#exec(), String#match() и String#matchAll() ищут заданный шаблон в строке и в случае успеха возвращают "match object". В нём находится индекс первого символа строки, который совпал с шаблоном. Этой информации недостаточно, если в выражении есть скобочные группы (capturing group), и если мы хотим получить позицию каждой сматченной группы. Именно эту проблему решает "Match Indicies" — он расширяет возвращаемый match object новым свойством indicies, в котором находятся позиции всех найденных групп. В статье есть пример того, как это свойство может быть использовано для понятного отображения ошибок парсинга кода.

На данный момент "RegExp Match Indices" находится на третьем этапе добавления в стандарт. Вполне возможно, что он попадёт в грядущий стандарт ECMAScript 2020 (ох... красивая цифра).

#js #regexp #proposal #v8

https://v8.dev/features/regexp-match-indices
Среди разработчиков иногда проскакивала шутка: "Что будет, когда V8 доберётся до версии V8?". И вот сегодня зарелизился V8 version 8.0 — V8. Лесджек Свирски рассказал про новые фичи в движке.

В новой версии было оптимизировано потребление памяти — график из статьи показывает падение на 40%. Оптимизация была достигнута за счёт компрессии tagged values (указателей на кучу V8 и small integers). Хорошим побочным эффектом оптимизации стало улучшение производительности движка на реальных сайтах. В статье мало подробностей, но обещают написать подробнее в другом посте.

Оптимизировали работу со встроенными в язык методами Function.prototype.apply, Reflect.apply и методами Array. Теперь код, в котором используются эти методы, оптимизируется с помощью Turbofan.

Теперь в движке есть полноценная поддержка Optional chaining и Nullish coalescing. Это новые фичи языка, которые помогают при обработке nullish-значений:

function Component(props) {
const enable = props?.enabled ?? true;
// …
}


#v8 #release #announcement

https://v8.dev/blog/v8-release-80
В блоге v8 появилась статья про использование SIMD в WebAssembly — "Fast, parallel applications with WebAssembly SIMD".

SIMD (Single Instruction, Multiple Data) — набор низкоуровневых инструкций процессора, которые позволяют распараллеливать обработку данных в рамках одного ядра. SIMD используется для ускорения работы кодеков, алгоритмов обработки изображений, компьютерного зрения и т.п. Чтобы web-приложения могли использовать SIMD-операции, разрабатывается расширение стандарта WebAssembly. В окончательном стандарте набор инструкций будет ограничиваться только теми инструкциями, которые можно использовать вне зависимости от архитектуры процессора.

Разработчики v8 представили экспериментальную реализацию предложения, благодаря ей демо отслеживания жестов рук ускорилось в пять раз. Демку можно запустить в последней версии Chrome, включив "WebAssembly SIMD support" в chrome://flags/.

#webassembly #v8

https://v8.dev/features/simd
https://storage.googleapis.com/mediapipe-viz.appspot.com/wasm-demos/hand-tracking-simd/hand_tracking_demo.html
Андрей Печкуров написал статью про внутреннее устройство Map в V8 — "[V8 Deep Dives] Understanding Map Internals".

В V8 для Map используется детерминированная хэш-таблица (deterministic hash table) — структура данных, которая гарантирует порядок обхода хранящихся в ней значений (в порядке их добавления в Map). Все данные для организации структуры данных находятся в одном большом массиве, который поделён на три логические части: заголовок, хэш-таблицу и данные. При добавлении и удалении значений из Map алгоритм периодически создаёт новую таблицу, поэтому операции вставки и удаления могут иметь временную сложность O(N). Операция извлечения данных из Map работает за O(1).

Интересная статья. Рекомендую почитать, если интересуетесь тем, как работает V8 изнутри.

#internals #v8 #algorithm

https://itnext.io/v8-deep-dives-understanding-map-internals-45eb94a183df
Зейнеп Канкара в блоге V8 написала статью про Indicium — новый инструмент рантайм анализа V8 — "Indicium: V8 runtime tracer tool".

Чтобы понять, зачем нужен этот инструмент, нужно немного разобраться в кишках V8. Для представления JavaScript-объектов V8 использует специальную структуру — Map (не тот Map, который определён в ECMAScript). Благодаря этой структуре движок экономит оперативную память при работе с большим числом однотипных объектов. Map в свою очередь использует оптимизацию Inline Cache (IC) для быстрого доступа к свойствам объектов.

Внутри V8 уже есть всё необходимое для получения информации о Map и IC, и уже есть готовые инструменты для их анализа. Indicium представляет эту информацию в удобном виде, связывая между собой Map и IC. С помощью него можно проанализировать создание объектов и быстро выявить проблемные места в исходном коде.

Indicium — это ещё один полезный инструмент для анализа проблем производительности в Chromium и Node.js.

#performance #tool #v8

https://v8.dev/blog/system-analyzer
В блоге V8 Мартин Бидлингмайер опубликовал статью про новый вспомогательный движок регэкспов, который позволяет избежать катастрофических откатов — "An additional non-backtracking RegExp engine".

V8 по умолчанию использует регэксп-движок Irregexp, который в свою очередь использует алгоритм бэктрекинга для проверки паттернов регэкспов. Этот алгоритм может приводить к катастрофическим откатам (сatastrophic backtracking). Катастрофический откат — это ситуация, когда проверка регулярного выражения не может быть выполнена за разумное время из-за экспоненциального роста количества возможных вариантов, которые должны быть обработаны движком. Катастрофические откаты могут использоваться для совершения DOS-атак, когда web-сервис обрабатывает входные данные пользователей с помощью регулярных выражений.

В V8 версии v8.8 был добавлен новый экспериментальный движок, который не подвержен проблеме экспоненциального взрыва, но гораздо медленнее (на данный момент) Irregexp. Его можно включить с помощью экспериментальных флагов V8.

Довольно хардкорная статья. Рекомендую почитать всем, кто интересуется темой разработки браузеров и безопасностью.

#v8 #security #internals

https://v8.dev/blog/non-backtracking-regexp
На прошлой неделе Ингвар Степанян рассказал о новых фичах девятой версии V8 — "V8 release v9.0".

В регулярных выражениях появилась поддержка флага /d, благодаря которому в результате выполнения регулярки (match object) появляется свойство indicies с позициями текущих скобочных групп (capturing group).

На порядки ускорен доступ к полям родительского объекта с помощью super.

Последовательность токенов for ( async of теперь больше не парсится.

В WebAssembly появилась экспериментальная поддержка инлайна враппера JS-to-Wasm для более эффективного преобразования параметров функций при их передаче из JS в Wasm. Эта фича будет особенно полезна в тех случаях, когда вызов Wasm-функции находится на горячем участке JavaScript-кода.

На данный момент V8 v9.0 находится в бете. Стабильная версия выйдет вместе с Chrome 90.

#v8 #release

https://v8.dev/blog/v8-release-90
Андрей Печкуров — участвует в разработке Node.js — написал статью про внутреннее устройство Math.random в V8 — "[V8 Deep Dives] Random Thoughts on Math.random()".

V8 использует генератор псевдослучайных чисел (PRNG), поставляемый окружением (Node.js, Chromium) или откатывается на системный источник случайности (например, /dev/urandom в Linux), если он не был задан в окружении. После того как генератор был проинициализирован seed-значением, все последующие случайные числа генерируются детерминировано с помощью алгоритма xorshift128+ и сохраняются в пуле из 64 значений, который заполняется по мере необходимости. Использование пула заранее сгенерированных случайных чисел очень распространённый подход, который используется при реализации PRNG.

Math.random не рекомендуется использовать для задач, связанных с безопасностью, потому что под капотом он не использует криптографически безопасный генератор псевдослучайных чисел (CSPRNG). Для таких задач вместо Math.random рекомендуется использовать генератор из Web Crypto API или модуля crypto в Node.js.

#js #v8 #internals #security

https://apechkurov.medium.com/v8-deep-dives-random-thoughts-on-math-random-fb155075e9e5
Поиск причины деградации времени сборки Webpack 5

Оуэн Хэннесси поделился историей поиска бага, из-за которого время запуска dev-сервера Webpack замедлилось в пятнадцать раз — "Understanding why our build got 15x slower with Webpack 5".

Проблема возникла после добавления невинной тёмной темы. Первые подозрения упали на CSS-in-JS-библиотеку Linaria. С помощью профилировщика внутри библиотеки была найдена проблемная функция, в которой использовался метод массива .concat(). Переписывание кода без использования .concat() решило проблему. Однако оставались вопросы, из-за того что в оригинальном коде просадки скорости не было при сборке проекта с помощью Webpack 4. Надо было исследовать исходники V8.

В V8 у метода .concat() есть две ветки выполнения кода — медленная и быстрая. Медленная ветка начинает работать в том случае, если движок хотя бы один раз устанавливал Symbol.isConcatSpreadable. В Webpack 5 это происходило в коде, отвечающем за обратную совместимость с четвёртой версией. Для решения проблемы разработчики Webpack добавили экспериментальную опцию backCompat, которая полностью отключает обратную совместимость, избавляясь от ещё большего количества проблемного кода.

#v8 #performance #webpack

https://engineering.tines.com/blog/understanding-why-our-build-got-15x-slower-with-webpack