Влияет ли использование анонимных функций на производительность?

Мне было интересно, есть ли разница в производительности между использованием именованных функций и анонимных функций в Javascript?

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = function() {
        // do something
    };
}

против

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

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

12 ответов

Решение

Здесь проблема производительности заключается в стоимости создания нового функционального объекта на каждой итерации цикла, а не в том факте, что вы используете анонимную функцию:

for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = function() {
        // do something    
    };
}

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

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

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

var handler = function() {
    // do something    
};
for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = handler;
}

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

Кроме того, сверху может показаться, что нет разницы между:

function myEventHandler() { /* ... */ }

а также:

var myEventHandler = function() { /* ... */ }

Первый - это объявление функции, а второй - присвоение переменной анонимной функции. Хотя они могут показаться одинаковыми, JavaScript обрабатывает их немного по-разному. Чтобы понять разницу, я рекомендую прочитать " Неоднозначность объявления функций JavaScript ".

Фактическое время выполнения для любого подхода во многом будет зависеть от реализации браузером компилятора и времени выполнения. Для полного сравнения производительности современного браузера посетите сайт JS Perf

Вот мой тестовый код:

var dummyVar;
function test1() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = myFunc;
    }
}

function test2() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = function() {
            var x = 0;
            x++;
        };
    }
}

function myFunc() {
    var x = 0;
    x++;
}

document.onclick = function() {
    var start = new Date();
    test1();
    var mid = new Date();
    test2();
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "\n Test 2: " + (end - mid));
}

Результаты, достижения:
Тест 1: 142 мс Тест 2: 1983 мс

Похоже, что движок JS не распознает, что это одна и та же функция в Test 2, и компилирует ее каждый раз.

Как общий принцип проектирования, вы должны избегать многократного использования одного и того же кода. Вместо этого вы должны поднять общий код в функцию и выполнить эту (общую, хорошо протестированную, легко изменяемую) функцию из нескольких мест.

Если (в отличие от того, что вы выводите из своего вопроса) вы объявляете внутреннюю функцию один раз и используете этот код один раз (и у вас нет ничего идентичного в вашей программе), то, вероятно, к автономной функции (то есть, ребята, угадайте) обращаются так же Компилятор как обычная именованная функция.

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

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

Но если вы не пишете процедуры шифрования / дешифрования или что-то подобное, чувствительное к производительности, как отмечали многие другие, всегда лучше оптимизировать для элегантного, легко читаемого кода поверх быстрого кода.

Предполагая, что вы пишете хорошо спроектированный код, вопросы скорости должны быть обязанностью тех, кто пишет интерпретаторы / компиляторы.

Там, где мы можем оказать влияние на производительность, в операции объявления функций. Вот эталон объявления функций внутри контекста другой функции или за ее пределами:

http://jsperf.com/function-context-benchmark

В Chrome операция выполняется быстрее, если мы объявляем функцию снаружи, но в Firefox все наоборот.

В другом примере мы видим, что если внутренняя функция не является чистой функцией, она будет иметь недостаточную производительность и в Firefox: http://jsperf.com/function-context-benchmark-3

Как указано в комментариях к ответу @nickf: Ответ на

Создает ли функцию один раз быстрее, чем миллион раз

это просто да. Но, как показывает его опыт JS, он не медленнее в миллион раз, показывая, что со временем он становится быстрее.

Более интересный вопрос для меня:

Как повторное создание + запуск сравнивается с созданием один раз + повторный запуск.

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

// Variant 1: create once
function adder(a, b) {
  return a + b;
}
for (var i = 0; i < 100000; ++i) {
  var x = adder(412, 123);
}

// Variant 2: repeated creation via function statement
for (var i = 0; i < 100000; ++i) {
  function adder(a, b) {
    return a + b;
  }
  var x = adder(412, 123);
}

// Variant 3: repeated creation via function expression
for (var i = 0; i < 100000; ++i) {
  var x = (function(a, b) { return a + b; })(412, 123);
}

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

Разница, вероятно, становится существенной только в тех случаях, когда создание объекта функции является сложным при сохранении незначительного времени выполнения, например, если все тело функции помещается в if (unlikelyCondition) { ... },

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

Если вам будет проще найти код, производительность не является проблемой, если вы не собираетесь вызывать функцию миллионы раз.

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

Здесь есть действительно хорошая статья об оптимизации javascript и анонимных функций:

http://dev.opera.com/articles/view/efficient-javascript/?page=2

@nickf

Это довольно глупый тест, вы сравниваете там время выполнения и компиляции, которое, очевидно, будет стоить метода 1 (компилируется N раз, в зависимости от JS-движка) с методом 2 (компилируется один раз). Я не могу себе представить разработчика JS, который бы так проходил свой код написания пробации.

Гораздо более реалистичным подходом является анонимное присваивание, так как на самом деле вы используете метод document.onclick больше похож на следующий, который на самом деле слегка предпочитает метод anon.

Используя тестовую среду, аналогичную вашей:


function test(m)
{
    for (var i = 0; i < 1000000; ++i) 
    {
        m();
    }
}

function named() {var x = 0; x++;}

var test1 = named;

var test2 = function() {var x = 0; x++;}

document.onclick = function() {
    var start = new Date();
    test(test1);
    var mid = new Date();
    test(test2);
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "ms\n Test 2: " + (end - mid) + "ms");
}

@nickf

(Жаль, что у меня не было представителя, чтобы просто прокомментировать, но я только что нашел этот сайт)

Я хочу сказать, что здесь есть путаница между именованными / анонимными функциями и сценарием выполнения + компиляции в итерации. Как я проиллюстрировал, разница между anon + named незначительна сама по себе - я говорю, что ошибочный вариант использования.

Это кажется очевидным для меня, но если нет, то я думаю, что лучший совет - "не делай глупостей" (из которых одно постоянное смещение блоков + создание объектов в этом сценарии использования), и если ты не уверен, проверь!

То, что определенно ускорит ваш цикл в различных браузерах, особенно в браузерах IE, заключается в следующем:

for (var i = 0, iLength = imgs.length; i < iLength; i++)
{
   // do something
}

Вы поместили произвольную 1000 в условие цикла, но вы получите мой дрейф, если хотите просмотреть все элементы в массиве.

Ссылка почти всегда будет медленнее, чем ссылка. Подумайте об этом так - допустим, вы хотите напечатать результат добавления 1 + 1. Что имеет больше смысла:

alert(1 + 1);

или же

a = 1;
b = 1;
alert(a + b);

Я понимаю, что это действительно упрощенный способ взглянуть на это, но это иллюстративно, верно? Используйте ссылку, только если она будет использоваться несколько раз - например, какой из этих примеров имеет больше смысла:

$(a.button1).click(function(){alert('you clicked ' + this);});
$(a.button2).click(function(){alert('you clicked ' + this);});

или же

function buttonClickHandler(){alert('you clicked ' + this);}
$(a.button1).click(buttonClickHandler);
$(a.button2).click(buttonClickHandler);

Второй - лучшая практика, даже если в ней больше строк. Надеюсь, все это полезно. (и синтаксис jquery никого не отбрасывал)

Другие вопросы по тегам