Яндекс.Метрика

    Javascript

    Оптимизация JavaScript: Scope, Low level ES vs ES5 Array methods

    Сегодня мы будем тестировать 2 блока кода, выполняющие следующую операцию:
    Дается массив, необходимо выбрать все элементы, степень 2 которых больше 5.

    В синем углу Вариант А: Низкоуровневый код — старый и страшный (поддающийся частичной оптимизации)
    1.    for (i = 0, res = []; i < c; i++) {
    2.        t = a[i];
    3.        if (t >= 2.236067) {
    4.            continue;
    5.        } else {
    6.            res.push(t * t);
    7.        }
    8.    }

    В красном углу Вариант Б: Высокоуровневый код — молодой и красивый (не поддающийся частичной оптимизации)
    1. a.map(function (t) { return t * t}).filter(function (t) { return t > 5});

    Битвы будут происходить на 3 аренах.
    1. AO args — Параметры объекта активации функции.
    2. AO — Локальные переменные объекта активации функции.
    3. Global — Глобальные переменные.


    В арсенале у нас последние стабильные версии всех популярных браузеров.

    Весь код теста:
    1.  
    2. // * * * * * * * * * * * * * * * * *
    3. // Activation object Arguments scope
    4. // * * * * * * * * * * * * * * * * *
    5.  
    6. (function (a, dt, index, i, c, r, t, res) {
    7. r = [];
    8. c = a.length;
    9.  
    10. dt = new Date();
    11. index = 20000;
    12. while (index--) {
    13.    a.map(function (t) { return t * t}).filter(function (t) { return t > 5});
    14. }
    15. r[0] = (new Date() - dt);
    16.  
    17. dt = new Date();
    18. index = 20000;
    19. while (index--) {
    20.    for (i = 0, res = []; i < c; i++) {
    21.        t = a[i];
    22.        if (t >= 2.236067) {
    23.            continue;
    24.        } else {
    25.            res.push(t * t);
    26.        }
    27.    }
    28. }
    29. r[1] = (new Date() - dt);
    30.  
    31. alert('ao args: ' + r);
    32. }([1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10]));
    33.  
    34. // * * * * * * * * * * * * * * * * *
    35. // Activation object scope
    36. // * * * * * * * * * * * * * * * * *
    37.  
    38. (function () {
    39. var a = [1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10],
    40.     dt,
    41.     index,
    42.     i,
    43.     c,
    44.     r,
    45.     t,
    46.     res
    47.     ;
    48.  
    49. r = [];
    50. c = a.length;
    51.  
    52. dt = new Date();
    53. index = 20000;
    54. while (index--) {
    55.    a.map(function (t) { return t * t}).filter(function (t) { return t > 5});
    56. }
    57. r[0] = (new Date() - dt);
    58.  
    59. dt = new Date();
    60. index = 20000;
    61. while (index--) {
    62.    for (i = 0, res = []; i < c; i++) {
    63.        t = a[i];
    64.        if (t >= 2.236067) {
    65.            continue;
    66.        } else {
    67.            res.push(t * t);
    68.        }
    69.    }
    70. }
    71. r[1] = (new Date() - dt);
    72.  
    73. alert('ao:      ' + r);
    74. }());
    75.  
    76. // * * * * * * * * * * * * * * * * *
    77. // Global scope
    78. // * * * * * * * * * * * * * * * * *
    79.  
    80. var a = [1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10],
    81.     dt,
    82.     index,
    83.     i,
    84.     c,
    85.     r,
    86.     t,
    87.     res
    88.     ;
    89.  
    90. r = [];
    91. c = a.length;
    92.  
    93. dt = new Date();
    94. index = 20000;
    95. while (index--) {
    96.    a.map(function (t) { return t * t}).filter(function (t) { return t > 5});
    97. }
    98. r[0] = (new Date() - dt);
    99.  
    100. dt = new Date();
    101. index = 20000;
    102. while (index--) {
    103.    for (i = 0, res = []; i < c; i++) {
    104.        t = a[i];
    105.        if (t >= 2.236067) {
    106.            continue;
    107.        } else {
    108.            res.push(t * t);
    109.        }
    110.    }
    111. }
    112. r[1] = (new Date() - dt);
    113.  
    114. alert('global:  ' + r);
    115.  


    Код: pastebin.com/mqBdkXZG

    Результаты


    FF high level low level
    AO args 458 92
    AO 474 122
    Global 479 162
    Opera high level low level
    AO args 416 22
    AO 427 21
    Global 428 49
    Chrome high level low level
    AO args 83 9
    AO 89 8
    Global 98 28
    Sa high level low level
    AO args 153 21
    AO 146 24
    Global 147 25
    IE8 high level low level
    AO args выбыл 441
    AO выбыл 393
    Global выбыл 822

    Итоги


    Как видим, наблюдается тенденция AO args|AO быстрее Global. Это ясно из спецификации ECMAScript — обращение к переменным объекта активации быстрее потому, что он(АО) лежит «ближе к коду» чем глобальный объект.
    Низкоуровневый код, в разы быстрее всокоуровневого потому что в ECMAScript нет блоков как в Ruby, для каждого элемента массива вызывается функция, а вызов функции затратная операция для JS. Высокоуровневый код медленнее до 20 раз!
    Chrome такой быстрый потому что имеет JIT компилляцию, но для часто используемых блоков кода (1 прогон даст одинаковый результат).
    Интересный момент показывает Firefox: AO args (low) 92; AO (low) 122; AO args на четверть быстрее. Хотя все эти переменные что AO args, что AO лежат в одном объекте, но в ФФ AO args, судя по результатам, выделяется в отдельный объект.

    Ещё интересный тест.
    1. (function (r, dt, index, i, j) {
    2.  
    3. dt = new Date();
    4. index = 50000;
    5. while(index--) {
    6.  
    7. // Block A
    8.   for (i = 0, j= 0; i < 20; i++) {
    9.     j++;
    10.   }
    11. // -------
    12.  
    13. }
    14. r[0] = new Date - dt;
    15.  
    16. dt = new Date();
    17. index = 50000;
    18. while(index--) {
    19.  
    20. // Block B
    21.   j = 0;
    22.   j++;j++;j++;j++;j++;
    23.   j++;j++;j++;j++;j++;
    24.   j++;j++;j++;j++;j++;
    25.   j++;j++;j++;j++;j++;
    26. // -------
    27.  
    28. }
    29. r[1] = new Date - dt;
    30.  
    31. alert(r);
    32.  
    33. }([]));


    Какой блок будет быстрее? Ответ: pastebin.com/hXxQb6pk

    UPD В конечном итоге все упирается перерисовку интерфейса (reflow, redraw), однако, как показала моя практика, оптимизации reflow было не достаточно. Значительную часть съедали вызовы анонимных функций, устранив их был получен хороший прирост, особенно в древних браузерах, из-за которых мы и страдаем. Не воспринимайте эту статью как руководство к действию, пишите код как считаете удобным для вас. Я советую производить оптимизацию по необходимости.