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

89

Мне было интересно, есть ли разница в производительности между использованием именованных функций и анонимных функций в 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;
}

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

Nickf
источник
Я знаю, что это не вопрос, но что касается чистоты / разборчивости кода, я думаю, что «правильный путь» находится где-то посередине. Раздражает «беспорядок» редко используемых функций верхнего уровня, как и сильно вложенный код, который во многом зависит от анонимных функций, которые объявляются вместе с их вызовом (подумайте, что это ад обратного вызова node.js). И первое, и второе могут затруднить отладку / отслеживание выполнения.
Zac B
Приведенные ниже тесты производительности запускают функцию для тысяч итераций. Даже если вы заметите существенную разницу, в большинстве случаев это не будет повторяться в указанном порядке. Следовательно, лучше выбрать то, что соответствует вашим потребностям, и игнорировать производительность в этом конкретном случае.
пользователь
@nickf, конечно, это слишком старый вопрос, но посмотрите новый обновленный ответ
Чандан Пасунори

Ответы:

89

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

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.

Атиф Азиз
источник
Вы забыли круглые скобки перед телом функции. Я только что протестировал, они обязательны.
Chinoto Vokro
похоже, что результаты тестов очень зависят от js-движка!
aleclofabbro
3
Разве в примере JS Perf нет недостатка: случай 1 определяет только функцию, тогда как случаи 2 и 3, кажется, случайно вызывают функцию.
bluenote10
Итак, используя это рассуждение, означает ли это, что при разработке node.jsвеб-приложений лучше создавать функции вне потока запросов и передавать их как обратные вызовы, чем создавать анонимные обратные вызовы?
Xavier T Mukodi,
23

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

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-движок не распознает одну и ту же функцию в Test2 и каждый раз компилирует ее.

Nickf
источник
3
В каком браузере проводился этот тест?
andynil
5
Время для меня в Chrome 23: (2 мс / 17 мс), IE9: (20 мс / 83 мс), FF 17: (2 мс / 96 мс)
Davy8
Ваш ответ заслуживает большего веса. Мое время на Intel i5 4570S: Chrome 41 (1/9), IE11 (1/25), FF36 (1/14). Очевидно, что анонимная функция в цикле работает хуже.
ThisClark
3
Этот тест не так полезен, как кажется. Ни в одном из примеров внутренняя функция не выполняется. Фактически весь этот тест показывает, что создание функции в 10000000 раз быстрее, чем создание функции один раз.
Nucleon
2

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

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

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

Том Лейс
источник
1

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

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

Джо Скора
источник
1

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

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

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

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

Пабло Эсторнут
источник
0

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

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

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

Сарханис
источник
0

ссылка почти всегда будет медленнее, чем то, на что она ссылается. Подумайте об этом так - допустим, вы хотите напечатать результат сложения 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 никого не скинул)

Мэтт Локкамп
источник
0

@nickf

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

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

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

аннаката
источник
0

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

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

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

Кристофер Токар
источник
0

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

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

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

пкоркоран
источник
0

@nickf

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

Гораздо более реалистичным подходом является анонимное присвоение, поскольку на самом деле вы используете для своего документа метод. 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");
}
аннаката
источник
0

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

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

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

Для меня более интересный вопрос:

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

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

// 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) { ... }.

синий
источник