Как именно V8 оптимизирует / встроенный?

#javascript #optimization #google-chrome #inline #v8

#javascript #оптимизация #google-chrome #встроенный #v8

Вопрос:

Мне интересно, возможно ли получить информацию о том, как именно V8 оптимизирует и упорядочивает вещи.

Я создал три простые тестовые функции, которые вычисляют синус угла в градусах. Я поместил их все в замыкания, чтобы V8 мог встроить локальные переменные.


1. Используя предварительно вычисленную константу Math.PI / 180 , а затем выполните Math.sin(x * constant) .

Я использовал этот код:

 var test1 = (function() {
  var constant = Math.PI / 180; // only calculate the constant once

  return function(x) {
    return Math.sin(x * constant);
  };
})();
  

2. Вычисление константы на лету.

 var test2 = (function() {
  var pi = Math.PI; // so that the compiler knows pi cannot change
                    // and it can inline it (Math.PI could change
                    // at any time, but pi cannot)

  return function(x) {
    return Math.sin(x * pi / 180);
  };
})();
  

3. Использование буквенных чисел и вычисление константы на лету.

 var test3 = (function() {
  return function(x) {
    return Math.sin(x * 3.141592653589793 / 180);
  };
})();
  

Удивительно, но результаты были следующими:

 test1 - 25,090,305 ops/sec
test2 - 16,919,787 ops/sec
test3 - 16,919,787 ops/sec
  

Похоже, что pi он был встроен в test2 as test2 и test3 приводит к точно такому же количеству операций в секунду.

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

  • Почему константа не вычисляется предварительно, если вы не делаете этого вручную в этом случае?
  • Можно ли увидеть, как V8 точно оптимизирует функции на определенной веб-странице?

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

1. Я не уверен, распространяется ли концепция встраивания стиля C на виртуальные машины с JITed. Я просто размышляю, но я подозреваю, что V8 применяет оптимизацию времени выполнения к функциям, которые часто вызываются, но это, вероятно, трудно предсказать.

2. @mikerobi: Это может быть наивный вопрос — но разве нельзя было бы просто посмотреть, что делает V8 во время компиляции / оптимизации / встраивания, как какой-нибудь инструмент отладки?

3. Вероятно, это возможно, но я сомневаюсь, что кто-либо за пределами команды разработчиков V8 мог бы рассказать вам, как.

4. Ну, например, JIT в PyPy имеет обширное ведение журнала, и они начали создавать инструмент для просмотра всех промежуточных представлений, через которые проходил код (байт-код Python, JIT IR, машинный код), поэтому теоретически такие вещи должны быть возможны. Но я предполагаю, что это упрощается тем фактом, что (1) фактический JIT генерируется машиной и (2) это JIT трассировки.

5. На данный момент V8 не выполняет постоянное распространение. Также вы наказываете V8, создавая функции в цикле (настройка вызывается несколько раз): попробуйте модифицированный тестовый пример jsperf.com/optimizing-v8/2 вместо этого. Это устраняет накладные расходы, связанные с многократной повторной оптимизацией новорожденных экземпляров замыканий, и делает картину более четкой.

Ответ №1:

Обоснованное предположение по вашему первому вопросу:

Строго говоря, он не может постоянно складывать pi / 180 часть, потому что вы этого не делаете pi / 180 во второй и третьей функциях. Вы просто делите (x * pi) на 180 (умножение имеет приоритет).

Теперь вы можете спросить, почему он не изменяет порядок операций, чтобы получить что-то, что он может оптимизировать (кстати, этот процесс называется повторной ассоциацией) … в конце концов, результат эквивалентен ( a * b / c = (a * b) / c ). Так говорит математика, верно?

Ну, математика так говорит, но математика не использует числа с плавающей запятой. С плавающими значениями все сложнее. x * pi может быть округлено, и тогда изменение порядка приведет к другому результату. Ошибки, вероятно, будут незначительными, но, тем не менее, главное правило оптимизации компилятора таково: вы не должны изменять результат программы. Лучше выполнить неоптимальные несколько математических тестов, написанных неудачным образом, чем отклониться на пиксель (да, это может быть заметно) в каком-либо графическом коде.

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

1. Примечание на случай, если кто-то сомневается в моем утверждении, что функции будут давать разные результаты: Попробуйте это с x = 0.6784993546113602 , например. Я нашел это и многие другие числа с Math.random() .

2. Спасибо, это очень интересно. Это требует дополнительных исследований (например, с константой (a * b) * x ).

3. Использование pi * 180 * x действительно приводит к получению разных результатов — now test1 и test3 равны, но test2 стали медленнее. Я думаю, это можно объяснить с помощью operator precendece. Я собираюсь попытаться найти причину, по которой pi in test2 больше не будет встроен. jsperf.com/optimizing-v8-2

4. в таком случае, что это x * (pi / 180) дает? Компилятор теоретически мог бы вытащить pi / 180 прямо сейчас.

5. @cwolves: Когда я сравниваю свой первоначальный тест и тот же тест с добавлением круглых скобок, кажется, что улучшений практически нет: jsperf.com/optimizing-v8/2 и jsperf.com/optimizing-v8/3 . Это означало бы, что идея приоритета с плавающей запятой / оператора на самом деле не применима. Загадочно…

Ответ №2:

Чтобы ответить на ваш второй вопрос, вы можете увидеть байт-код, для которого V8 оптимизировал ваш JS с помощью этого инструмента:http://mrale.ph/irhydra/2 / . Это фантастика для низкоуровневой настройки кода в Chrome.