Показаны сообщения с ярлыком performance. Показать все сообщения
Показаны сообщения с ярлыком performance. Показать все сообщения

16 июн. 2025 г.

Debug CLS: List DOM elements, their coordinates, and area changes for every layout shift

(new PerformanceObserver((list) => {

    const entries = list.getEntriesByType('layout-shift');

    entries.forEach((/*LayoutShift*/entry) => {

        console.log(entry);

        const /*Array<LayoutShiftAttribution>*/sources = entry.sources || [];

        const changes = [];

        for (const source of sources) {

            const { currentRect: cr, previousRect: pr, node } = source;

            const dx = cr.x - pr.x;

            const dy = cr.y - pr.y;

            const ds = cr.width * cr.height - pr.width * pr.height;

            changes.push({dx, dy, ds, node});

        }

        if (changes.length) {

            console.table(changes);

        }

    });

})).observe({ type: 'layout-shift', buffered: true });


3 нояб. 2023 г.

Benchmarking bun startup time

Single-line file:

console.log('Hello World')

Benchmark:

hyperfine --warmup 3 'bun test.js' 'node test.js'
Benchmark 1: bun test.js
  Time (mean ± σ): 8.9 ms ± 0.6 ms [User: 5.2 ms, System: 3.6 ms]
  Range (min … max): 7.5 ms … 11.8 ms 258 runs

Benchmark 2: node test.js
  Time (mean ± σ): 24.9 ms ± 1.1 ms [User: 19.7 ms, System: 3.6 ms]
  Range (min … max): 23.6 ms … 30.0 ms 111 runs

The difference is only about 20 ms.

ESLint'ing the project with 30000 files:

find . \( -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" \) | wc -l
   30623

hyperfine --warmup 3 --ignore-failure 'npx eslint --ext .js,.jsx,.ts,.tsx .' 'bunx eslint --ext .js,.jsx,.ts,.tsx .'
Benchmark 1: npx eslint --ext .js,.jsx,.ts,.tsx .
  Time (mean ± σ): 6.896 s ± 0.047 s [User: 15.452 s, System: 0.621 s]
  Range (min … max): 6.831 s … 6.980 s 10 runs

Benchmark 2: bunx eslint --ext .js,.jsx,.ts,.tsx .
  Time (mean ± σ): 6.776 s ± 0.046 s [User: 15.423 s, System: 0.578 s]
  Range (min … max): 6.715 s … 6.849 s 10 runs

The difference is about 100 ms.

3 авг. 2020 г.

Tools for performance analysis in Node.js

  • node-tick-processor easy-to-install processor for the v8 profiler log
  • node-gcstats exposes stats about V8 GC after it has been executed
  • 0x single-command flamegraph profiling
  • autocannon HTTP/1.1 benchmarking tool written in node, with support for HTTP pipelining and HTTPS
  • k6.io open source load testing tool and SaaS for engineering teams
  • why-did-you-render monkey patches React to notify you about avoidable re-renders (works with React Native as well)

3 июн. 2020 г.

Profiling JavaScript code with Linux perf command

Profile V8 internals on Linux:

$ perf record d8 --perf-basic-prof test.js
$ perf report


Easy installation of V8 engine: https://www.npmjs.com/package/jsvu

Customize output of Benchmark.js results

By default Benchmark.js recommends the following code to view results of a benchmark:

.on('cycle', function(event) {
    console.log(String(event.target));
})

Example output:

<test 1> x 123,456 ops/sec ±5.67% (75 runs sampled)
<test 2> x 112,678 ops/sec ±4.56% (77 runs sampled)

The problem is that operations per second are not additive: if <test 2> has some added/removed code, we don't know the cost of that code in milliseconds (how many milliseconds it adds or removes from the total execution time).

To get that information, change the output code as follows:

.on('cycle', function(event) { const stats = event.target.stats; const sortedSample = stats.sample.sort((a, b) => b - a); const median = sortedSample[sortedSample.length >> 1]; const fastest = sortedSample[sortedSample.length - 1]; console.log( String(event.target), '\n\tfastest:', fastest * 1000000, 'μs/op', '\n\tmean:', stats.mean * 1000000, 'μs/op', // '±' + stats.rme.toFixed(2) + '%', '\n\tmedian:', median * 1000000, 'μs/op' ); })

here stats.rme means "relative margin of error" and is the same "±5.67%" as in example output above, so there is no need to duplicate it

Same code in TypeScript:

.on('cycle', function(event: { target: { stats: Stats }; }) { const stats: Stats = event.target.stats; const sortedSample = stats.sample.sort((a, b) => b - a); const median = sortedSample[sortedSample.length >> 1]; const fastest = sortedSample[sortedSample.length - 1]; console.log( String(event.target), '\n\tfastest:', fastest * 1000000, 'μs/op', '\n\tmean:', stats.mean * 1000000, 'μs/op', '\n\tmedian:', median * 1000000, 'μs/op' ); })

Type description (borrowed from https://benchmarkjs.com/docs):

type Stats = { /** The sample standard deviation. */ deviation: number, /** The sample arithmetic mean (secs). */ mean: number, /** The margin of error. */ moe: number, /** The relative margin of error (expressed as a percentage of the mean). */ rme: number, /** The array of sampled periods. */ sample: number[], /** The standard error of the mean. */ sem: number, /** The sample variance. */ variance: number };


10 апр. 2020 г.

19 мар. 2020 г.

Профилирование скорости server-side рендеринга (SSR) компонентов на React + TypeScript

Подготовка

Для запуска ts tsx файлов в Node.js устанавливаем пакет ts-node:

npm i ts-node

Чтобы при запуске игнорировать импорт стилей в файле компонента (который может выглядеть примерно так: import './MyComponent.scss';), устанавливаем пакет ignore-styles:

npm i ignore-styles

Создание файла с бенчмарком

Создаём tsx файл, в котором импортируем наш компонент MyComponent, задаём props и вызываем renderToString():

import * as React from 'react';
import { renderToString } from 'react-dom/server';
import { MyComponent } from './MyComponent';

const props = {/* ... */};
console.log(renderToString(<MyComponent {...props} />));

Запускаем его, чтобы убедиться, что всё работает без ошибок, компонент рендерится и в консоль выводится правильная разметка компонента:

NODE_ENV=production node -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

Если не хватит памяти, надо добавить параметр --max_old_space_size=4096

NODE_ENV=production node --max_old_space_size=4096 -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

Если памяти всё равно не хватит - надо поставить вместо 4096 число побольше. Запоминаем подобранные параметры.

Убедившись, что всё работает, в файле бенчмарка убираем вывод в консоль и дописываем вызов renderToString() в цикле:

import * as React from 'react';
import { renderToString } from 'react-dom/server';
import { MyComponent } from './MyComponent';

const props = {/* ... */};

for (let i = 0; i < 1000; i++) {
    renderToString(<MyComponent {...props} />);
}

Запуск бенчмарка

Запускаем бенчмарк (в подобранную ранее командную строку добавляется параметр --prof):

NODE_ENV=production node --prof -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

или

NODE_ENV=production node --prof --max_old_space_size=4096 -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

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

После каждого запуска должны создаваться файлы isolate*.log.

Теперь вместо числа 1000 надо подобрать такое, чтобы второй запуск бенчмарка занимал достаточно продолжительное время (десятки секунд): меняем число, запускаем два раза, оцениваем продолжительность второго запуска.

После этого удаляем все накопившиеся файлы isolate*.log, запускаем бенчмарк ещё раз - мы должны получить ровно один файл isolate*.log.

Обработка и анализ собранного профиля

Полученный файл isolate*.log надо обработать, чтобы получить на выходе читабельный профиль выполнения:

node --prof-process isolate-0x104000800-v8.log >isolate-0x104000800-v8.txt

Вместо isolate-0x104000800-v8 надо подставить своё имя файла. Также для обработки лога можно использовать отдельный пакет https://www.npmjs.com/package/tick-processor, часто он лучше обрабатывает лог и не теряет данные, в обличие от встроенного в саму ноду процессора логов:

tick-processor isolate-0x104000800-v8.log >isolate-0x104000800-v8.txt

В текстовом файле мы видим, какие именно методы v8 и нативного C++ кода вызывались и сколько времени заняли. Подробное описание содержимого и методов его анализа - тема, достойная отдельной большой статьи.

16 апр. 2019 г.

V8 Runtime Call Stats on Timeline

  1. Go to chrome://flags/#enable-devtools-experiments
  2. Enable DevTools experiments
  3. Restart Chrome
  4. Open DevTools - Settings - Experiments
  5. Hit Shift 6 times
  6. Check the option called Timeline: V8 Runtime Call Stats on Timeline
  7. Close then re-open DevTools

1 мар. 2019 г.

V8 --prof


  1. Run Chrome or Node.js with --prof flag
    chrome --js-flags='--prof' --no-sandbox 'http://localhost:8080/'
    
    node --prof script.js
  2. Install tick processor https://github.com/dex4er/js-tick-processor
    
    npm install -g tick-processor
  3. tick-processor isolate-0x123456-v8.log >isolate-0x123456-v8.txt

1 окт. 2018 г.

Визуализация результатов профилирования в chrome://tracing

При профилировании сложных библиотек и фреймворков типа React и Angular в полученных результатах очень много визуального шума от всевозможных обёрток и утилитных функций, которые загромождают стек. Можно и нужно сделать свой собственный код для более высокоуровневого профилирования (например, в React замерять только времена стандартных методов жизненного цикла компонента: shouldComponentUpdate, render и т.д.).

Встаёт вопрос визуализации полученных данных. Таблички - хорошо, но таймлайн или flame chart - нагляднее и легче для анализа. Можно быстро набросать визуализатор с помощью Google Charts Timeline, но хочется чего-то более мощного и интерактивного, как в Dev tools, с возможностью масштабирования и перемещения вдоль временной оси.

1 июл. 2018 г.

21 мая 2018 г.

Background tab in Chrome browser: how it affects JS

This presentation from Chrome developers shows technical details regarding what happens to a web page when it goes into background (the user switches to another tab)

Freezing:

  • ~2013: timers are stopped on mobile after 5 minutes
  • M67: loading tasks are stopped on mobile after 5 minutes
  • M68 (experiment): page is frozen on desktop after 1 hour without network activity
  • M69: page will be frozen or discarded based on the API it uses
  • ~2020: page will be frozen after loading
Throttling:
  • 2011: setTimeout/setInterval are fired once a second
  • M56: timers are delayed to limit CPU usage to 1%
  • M68: workers are throttled
  • 2018: non-timer tasks are throttled
  • ~2020: page will be throttled during loading depending on the foreground activity