JS: Сколько времени требуется для вызова функции?

Итак, я программирую 2d Javascript физическое моделирование. Производительность хорошая, но я собираюсь сделать оптимизацию, чтобы сделать ее лучше. Поэтому, поскольку программа работает с большой физической геометрией, я делаю несколько расчетов Пифагорейской теоремы в программе. Всего около пяти расчетов; вместе они работают около миллиона раз в секунду. Итак, я полагал, что это повысит производительность, если я вложу этот простой код теоремы Пифагора в новую функцию и назвал ее; в конце концов, таким образом, браузер имеет меньше возможностей для компиляции. Итак, я запустил код в Firefox и получил.... 4000000% увеличения времени выполнения этого расчета.

Как? Это тот же код: Math.sqrt(x * x + y * y), так как добавление его как функции замедляет его? Я предполагаю, что причина в том, что функция требует времени для вызова без выполнения кода и добавления того, что миллион этих задержек в секунду замедляет его?

Это выглядит довольно тревожным для меня. Будет ли это также применяться к предопределенным js-функциям? Кажется маловероятным, и если да, то как им избежать?

Код, используемый для этого:

function x()
{
    dx=nx-mx;
    dy=ny-my;
    d=Math.sqrt(dx*dx+dy*dy);
    doStuff(...
}

Я пробовал это:

function x()
{
    dx=nx-mx;
    dy=ny-my;
    d=hypo(dx,dy);
    doStuff(...
}
function hypo(x,y)
{
    return Math.sqrt(x*x+y*y);
}

Спасибо!

Ответ 1

Вызовы функций незначительны или даже оптимизированы на предварительно скомпилированных языках, которых JS никогда не было. Кроме того, многое зависит от браузера.

Они являются гибелью всех характеристик интерпретируемых языков, которые JS были прежде всего до недавнего времени. В большинстве современных браузеров есть компиляторы JIT (Just In Time), которые являются огромным обновлением от интерпретаторов JS прошлого, но я считаю, что вызовы функций в другой области все еще требуют некоторых накладных расходов, потому что объект вызова JS должен определять, что на самом деле называется, и это означает продвигаясь вверх и вниз по различным целям.

Итак, как общее правило: если вам небезразличен IE8, а более низкие и более ранние версии Chrome и Firefox избегают периодов функциональных вызовов. Особенно внутри петель. Для JIT-браузеров я ожидал бы, что функция, определенная внутри другой функции, будет в целом полезна (но я все равно буду тестировать, поскольку это совершенно новая технология для IE9 и относительно новая для всех остальных).

Еще одна вещь, о которой нужно опасаться. Если функция особенно сложна, JIT может ничего не сделать для их оптимизации.

https://groups.google.com/forum/#!msg/closure-compiler-discuss/4B4IcUJ4SUA/OqYWpSklTE4J

Но важно понять, что когда что-то заблокировано и только вызывается внутри контекста, как функция внутри функции, для JIT должно быть легко оптимизировать. Определенный вне функции, он должен определить, какое определение этой функции вызывается точно. Это может быть внешняя функция. Это может быть глобальным. Это может быть свойство прототипа конструктора оконного объекта и т.д. На языке, где функции являются первоклассными, то есть их ссылки могут передаваться как args так же, как вы передаете данные, вы не можете действительно избежать этого шага вне вашего текущего контекста.

Итак, попробуйте определить hypo внутри X, чтобы узнать, что произойдет.

Еще несколько общих советов из интерпретируемого возраста, которые могут по-прежнему быть полезными в JIT:

  • "." оператор, как в someObject.property, является кешированием процесса. Это издержки накладные расходы, так как каждый связанный процесс поиска объекта вызова выполняется каждый раз, когда вы его используете. Я полагаю, что Chrome не сохранит результаты этого процесса, так как изменения родительских объектов или прототипов могут изменить то, что он на самом деле ссылается вне данного контекста. В вашем примере, если x используется в цикле (вероятно, хорошо или даже полезно, если x определен в той же функции, что и цикл в JITs - убийство в интерпретаторе), я бы попытался назначить Math.sqrt var, прежде чем использовать его в hypo. Слишком много ссылок на материал вне контекста вашей текущей функции может привести к тому, что некоторые JIT решит, что это не стоит того, чтобы оптимизировать, но это чистое предположение с моей стороны.

  • Возможно, это самый быстрый способ петли массива:

//assume a giant array called someArray
var i = someArray.length; //note the property lookup process being cached here
//'someArray.reverse()' if original order isimportant
while(i--){
  //now do stuff with someArray[i];
}

note: блок кода не работает по какой-либо причине.

Выполнение этого способа может быть полезным, поскольку оно в основном преобразует шаг inc/decment и логическое сравнение только в декремент, полностью устраняя необходимость в операторе сравнения слева и справа. Обратите внимание, что в JS оператор сокращения правой стороны означает, что я передается для оценки и затем декрементируется до его использования внутри блока. while(0) имеет значение false.

Ответ 2

К моему удивлению, кэширование поиска, предложенное Эриком, не делает многого для повышения производительности в моем браузере (Chromium, Linux), но, похоже, это приводит к снижению производительности: http://jsperf.com/inline-metric-distance

var optimizedDistance = (function () { 
    var sqrt = Math.sqrt;
    return function (x, y) { return sqrt(x * x + y * y); }
})();

медленнее, чем

var unoptimizedDistance = function(x, y) {
    return Math.sqrt(x * x + y * y);
}

Даже вызов псевдонима медленнее

var _sqrt = Math.sqrt; // _sqrt is slower than Math.sqrt!

Но опять же, это не точная наука, а измерения в реальной жизни все равно могут различаться.

Тем не менее, я бы пошел с использованием Math.sqrt.