Перейти к основному содержимому

Использование профилировщика на основе выборки в V8

V8 имеет встроенный профилировщик на основе выборки. Профилирование отключено по умолчанию, но его можно включить с помощью опции командной строки --prof. Сэмплер записывает стеки как JavaScript-кода, так и C/C++-кода.

Сборка

Соберите оболочку d8, следуя инструкциям Сборка с использованием GN.

Командная строка

Чтобы начать профилирование, используйте опцию --prof. При профилировании V8 создает файл v8.log, содержащий данные профилирования.

Windows:

build\Release\d8 --prof script.js

Другие платформы (замените ia32 на x64, если хотите профилировать сборку x64):

out/ia32.release/d8 --prof script.js

Обработка полученного вывода

Обработка файлов журнала выполняется с использованием JS скриптов через оболочку d8. Для этого бинарный файл d8 (или символическая ссылка, или d8.exe на Windows) должен находиться в корне ваших исходных файлов V8 или в пути, указанном переменной окружения D8_PATH. Примечание: этот бинарный файл используется только для обработки лога, но не для фактического профилирования, поэтому версия и другие параметры не имеют значения.

Убедитесь, что d8, используемый для анализа, не был собран с параметром is_component_build!

Windows:

tools\windows-tick-processor.bat v8.log

Linux:

tools/linux-tick-processor v8.log

macOS:

tools/mac-tick-processor v8.log

Веб-интерфейс для --prof

Предварительная обработка лога с --preprocess (для разрешения C++ символов и др.).

$V8_PATH/tools/linux-tick-processor --preprocess > v8.json

Откройте tools/profview/index.html в вашем браузере и выберите файл v8.json там.

Пример вывода

Результат статистического профилирования из benchmarks\v8.log, (4192 тиков, 0 неопределенных, 0 исключенных).

[Общие библиотеки]:
тик общий nonlib имя
9 0.2% 0.0% C:\WINDOWS\system32\ntdll.dll
2 0.0% 0.0% C:\WINDOWS\system32\kernel32.dll

[JavaScript]:
тик общий nonlib имя
741 17.7% 17.7% LazyCompile: am3 crypto.js:108
113 2.7% 2.7% LazyCompile: Scheduler.schedule richards.js:188
103 2.5% 2.5% LazyCompile: rewrite_nboyer earley-boyer.js:3604
103 2.5% 2.5% LazyCompile: TaskControlBlock.run richards.js:324
96 2.3% 2.3% Builtin: JSConstructCall
...

[C++]:
тик общий nonlib имя
94 2.2% 2.2% v8::internal::ScavengeVisitor::VisitPointers
33 0.8% 0.8% v8::internal::SweepSpace
32 0.8% 0.8% v8::internal::Heap::MigrateObject
30 0.7% 0.7% v8::internal::Heap::AllocateArgumentsObject
...


[GC]:
тик общий nonlib имя
458 10.9%

[Нисходящий (тяжелый) профиль]:
Примечание: процент показывает долю конкретного вызывающего в общем
количестве вызовов его родителя.
Вызывающие, занимающие менее 2.0%, не показаны.

тик родитель имя
741 17.7% LazyCompile: am3 crypto.js:108
449 60.6% LazyCompile: montReduce crypto.js:583
393 87.5% LazyCompile: montSqrTo crypto.js:603
212 53.9% LazyCompile: bnpExp crypto.js:621
212 100.0% LazyCompile: bnModPowInt crypto.js:634
212 100.0% LazyCompile: RSADoPublic crypto.js:1521
181 46.1% LazyCompile: bnModPow crypto.js:1098
181 100.0% LazyCompile: RSADoPrivate crypto.js:1628
...

Профилирование веб-приложений

Современные оптимизированные виртуальные машины могут запускать веб-приложения на высокой скорости. Однако не следует полагаться исключительно на них для достижения отличной производительности: тщательно оптимизированный алгоритм или менее затратная функция могут значительно повысить скорость работы во всех браузерах. Chrome DevToolsCPU Profiler поможет вам проанализировать узкие места в вашем коде. Но иногда нужно копать глубже и более детально: здесь на помощь приходит внутренний профилировщик V8.

Давайте используем этот профилировщик, чтобы изучить демо-версию Mandelbrot Explorer, которую Microsoft выпустила вместе с IE10. После выпуска демо V8 исправил баг, который замедлял расчет неоправданно (что привело к слабой производительности Chrome в блоге о демо), и дополнительно оптимизировал движок, реализовав более быструю аппроксимацию exp(), чем предоставляют стандартные системные библиотеки. После этих изменений демо стало работать в 8 раз быстрее, чем измерялось ранее в Chrome.

Но что, если вы хотите, чтобы код выполнялся быстрее во всех браузерах? Сначала вы должны понять, что занимает ваш процессор. Запустите Chrome (Windows и Linux Canary) с следующими параметрами командной строки, которые заставят его выводить информацию о тиках профайлера (в файл v8.log) для указанного URL-адреса, который в нашем случае был локальной версией демонстрации Мандельброта без веб-воркеров:

./chrome --js-flags='--prof' --no-sandbox 'http://localhost:8080/'

При подготовке тестового случая убедитесь, что он начинает свою работу сразу после загрузки, и закройте Chrome, когда вычисление будет завершено (нажмите Alt+F4), чтобы в журнале были только те тики, которые вас интересуют. Также обратите внимание, что веб-воркеры пока еще не профилируются корректно с использованием этой техники.

Затем обработайте файл v8.log с помощью скрипта tick-processor, поставляемого с V8 (или нового практичного веб-версии):

v8/tools/linux-tick-processor v8.log

Вот интересный фрагмент обработанного вывода, который должен привлечь ваше внимание:

Результаты статистического профилирования от null, (14306 тиков, 0 неучтено, 0 исключено).
[Общие библиотеки]:
тики всего не библиотека название
6326 44.2% 0.0% /lib/x86_64-linux-gnu/libm-2.15.so
3258 22.8% 0.0% /.../chrome/src/out/Release/lib/libv8.so
1411 9.9% 0.0% /lib/x86_64-linux-gnu/libpthread-2.15.so
27 0.2% 0.0% /.../chrome/src/out/Release/lib/libwebkit.so

В верхней секции показывается, что V8 проводит больше времени внутри специфичной для ОС системной библиотеки, чем в своем собственном коде. Давайте посмотрим, что является причиной, изучив секцию вывода «снизу вверх», где вложенные строки можно прочитать как «был вызван» (а строки, начинающиеся с символа *, означают, что функция была оптимизирована TurboFan):

[Снизу вверх (тяжелый) профиль]:
Примечание: процент показывает долю конкретного вызывающего функции в общей
сумме вызовов его родителя.
Вызывающие функции, занимающие менее 2.0%, не отображаются.

тики родитель название
6326 44.2% /lib/x86_64-linux-gnu/libm-2.15.so
6325 100.0% LazyCompile: *exp native math.js:91
6314 99.8% LazyCompile: *calculateMandelbrot http://localhost:8080/Demo.js:215

Более 44% общего времени производится выполнением функции exp() внутри системной библиотеки! С учетом некоторой нагрузки при вызове системных библиотек это означает, что около двух третей общего времени проводится расчетом Math.exp().

Если посмотреть на JavaScript-код, то видно, что exp() используется исключительно для создания плавной палитры градаций серого. Существует множество способов создания плавной палитры градаций серого, но предположим, что вам действительно нравятся экспоненциальные градиенты. Здесь вступает в игру алгоритмическая оптимизация.

Вы заметите, что exp() вызывается с аргументом в диапазоне -4 < x < 0, так что мы можем безопасно заменить его на его аппроксимацию Тейлора для данного диапазона, которая дает такую же плавную градацию с использованием лишь одного умножения и пары делений:

exp(x) ≈ 1 / ( 1 - x + x * x / 2) для -4 < x < 0

Изменение алгоритма таким образом увеличивает производительность на дополнительные 30% по сравнению с последней версией Canary и в 5 раз по сравнению с системной библиотекой Math.exp() в Chrome Canary.

Этот пример показывает, как внутренний профайлер V8 может помочь вам лучше понять узкие места вашего кода, и как более умный алгоритм может значительно повысить производительность.

Чтобы узнать больше о том, как проводить тесты, представляющие современные сложные и требовательные веб-приложения, прочтите Как V8 измеряет производительность реального мира.