Чи впливає використання анонімних функцій на продуктивність?


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;
}

Перший - охайніший, оскільки він не захаращує ваш код рідко використовуваними функціями, але чи важливо, що ви повторно заявляєте про цю функцію кілька разів?


Я знаю, що справа не в цьому, але що стосується чистоти / розбірливості коду, я думаю, що "правильний шлях" знаходиться десь посередині. "Безлад" рідко використовуваних функцій верхнього рівня дратує, але також і сильно вкладений код, який багато в чому залежить від анонімних функцій, які оголошуються в рядку з їх викликом (думаю, пекло зворотного виклику node.js). Як перший, так і другий можуть ускладнити налагодження / відстеження виконання.
Zac B

Наведені нижче тести продуктивності запускають функцію для тисяч ітерацій. Навіть якщо ви бачите суттєву різницю, більшість випадків використання не будуть робити це в ітераціях цього порядку. Тому краще вибрати все, що відповідає вашим потребам, і ігнорувати ефективність для цього конкретного випадку.
користувач

@nickf, звичайно, це занадто старе питання, але дивіться нову оновлену відповідь
Chandan Pasunoori

Відповіді:


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


Ви забули дужки перед тілом функції. Я щойно перевірив, вони потрібні.
Чіното Вокро

здається, результати тестування дуже залежать від 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, і кожен раз компілює її.


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 більше схожий на наступний, що насправді м’яко сприяє методу анону.

Використовуючи тестову структуру, подібну до вашої:


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, він не повільніший у мільйон разів, показуючи, що він з часом стає швидшим.

Мені цікавіше питання:

Як порівнюється повторний створення + запуск для створення раз + повторний запуск .

Якщо функція виконує складні обчислення, час створення об’єкта функції, швидше за все, незначний. Але як щодо надмірного створення у випадках, коли запуск швидкий? Наприклад:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.