Использование профилировщика на основе выборки в 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 DevTools’ CPU 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 измеряет производительность реального мира.