Почему «Время для себя» так велико в эффективно пустой функции?

#javascript #google-chrome #google-chrome-devtools #profiling #v8

Вопрос:

У меня есть вычислительно тяжелая функция, которая вызывается много раз в цикле:

 function func() { // Some fluff
  for(let i = 0; i < 1000; i  ) {
    i *= 10
    i /= 10
  }
}

function run() {
  for(let i = 0; i < 100000; i  ) {
    func()
  }
}

run()
 

Когда я профилирую этот скрипт с помощью инструментов разработки Chrome, я получаю следующее:

введите описание изображения здесь

run имеет время автономной работы 887 мс из общего времени 1015 мс, хотя единственное, что он делает, — это повторный вызов func .

Я бы ожидал func , что у меня будет большая часть времени для себя, так как это функция листа.

Почему это так?

Комментарии:

1. Это либо ошибка, либо функция была автоматически встроена через некоторое время, когда она стала «горячей».

2. Разве это не показывает, что выполнение 100000 вызовов функций обходится дороже, чем 1000 итераций двух простых арифметических операций, что имеет смысл?

3. @wOxxOm Я бы выбрал последнее. Это не очень реалистичный код. Пытаться делать из этого выводы на самом деле не имеет смысла — код, возможно, был оптимизирован на лету. Другой код может работать по-другому. Важно профилировать реальный код.

4. @VLAZ Это произошло с моим реальным кодом, я просто пытался создать минимальный воспроизводимый пример, так как копирование всего моего проекта в вопрос SO не является идеальным.

5. @Phillip Не забывайте, что 1000 итераций двух простых арифметических операций сами по себе называются 100000 раз: на самом деле 100000000 итераций.

Ответ №1:

(Разработчик V8 здесь.)

функция была автоматически встроена через некоторое время, когда она стала «горячей».

Правильный. Как только оптимизирован, оптимизатор решает встроиться в него. run func После того, как профилировщик, то все время уходит на run .
(Чтобы проверить это, запустите фрагмент В d8 или node с --trace-turbo-inlining .)
Примечание: приступая к оптимизированный код для run занимает немного больше времени, чем обычно, в этом случае, потому что функция никогда не возвращает вам позвонил еще раз (а это лучшее время, чтобы переключиться с оптимизированного кода). Система немного ждет, пока это произойдет, а когда этого не происходит, run в конечном итоге «заменяется в стеке». Это типичный шаблон, который часто встречается в небольших тестах и тестах и редко встречается в реальном коде.

Разве это не показывает, что выполнение 100000 вызовов функций обходится дороже, чем 1000 итераций двух простых арифметических операций, что имеет смысл?

Нет, это не показывает этого; это всего лишь один конкретный способ, как можно было бы ввести в заблуждение этой микробной меткой.

Лично я слегка разочарован, увидев (с --print-opt-code ), что компилятор не понял, что i *= 10; i /= 10; это не операция и может быть полностью удален. Это был бы еще один отличный способ быть введенным в заблуждение здесь. Ну что ж, вероятно, есть какая-то причина, по которой компилятору сложнее, чем кажется, понять, что это преобразование будет одновременно применимым и безопасным…