Javascript →
Оптимизация JavaScript: Scope, Low level ES vs ES5 Array methods
Сегодня мы будем тестировать 2 блока кода, выполняющие следующую операцию:
Дается массив, необходимо выбрать все элементы, степень 2 которых больше 5.
В синем углу Вариант А: Низкоуровневый код — старый и страшный (поддающийся частичной оптимизации)
В красном углу Вариант Б: Высокоуровневый код — молодой и красивый (не поддающийся частичной оптимизации)
Битвы будут происходить на 3 аренах.
1. AO args — Параметры объекта активации функции.
2. AO — Локальные переменные объекта активации функции.
3. Global — Глобальные переменные.
В арсенале у нас последние стабильные версии всех популярных браузеров.
Весь код теста:
Код: pastebin.com/mqBdkXZG
Как видим, наблюдается тенденция 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, судя по результатам, выделяется в отдельный объект.
Ещё интересный тест.
Какой блок будет быстрее? Ответ: pastebin.com/hXxQb6pk
UPD В конечном итоге все упирается перерисовку интерфейса (reflow, redraw), однако, как показала моя практика, оптимизации reflow было не достаточно. Значительную часть съедали вызовы анонимных функций, устранив их был получен хороший прирост, особенно в древних браузерах, из-за которых мы и страдаем. Не воспринимайте эту статью как руководство к действию, пишите код как считаете удобным для вас. Я советую производить оптимизацию по необходимости.
Дается массив, необходимо выбрать все элементы, степень 2 которых больше 5.
В синем углу Вариант А: Низкоуровневый код — старый и страшный (поддающийся частичной оптимизации)
- for (i = 0, res = []; i < c; i++) {
- t = a[i];
- if (t >= 2.236067) {
- continue;
- } else {
- res.push(t * t);
- }
- }
В красном углу Вариант Б: Высокоуровневый код — молодой и красивый (не поддающийся частичной оптимизации)
- a.map(function (t) { return t * t}).filter(function (t) { return t > 5});
Битвы будут происходить на 3 аренах.
1. AO args — Параметры объекта активации функции.
2. AO — Локальные переменные объекта активации функции.
3. Global — Глобальные переменные.
В арсенале у нас последние стабильные версии всех популярных браузеров.
Весь код теста:
- // * * * * * * * * * * * * * * * * *
- // Activation object Arguments scope
- // * * * * * * * * * * * * * * * * *
- (function (a, dt, index, i, c, r, t, res) {
- r = [];
- c = a.length;
- dt = new Date();
- index = 20000;
- while (index--) {
- a.map(function (t) { return t * t}).filter(function (t) { return t > 5});
- }
- r[0] = (new Date() - dt);
- dt = new Date();
- index = 20000;
- while (index--) {
- for (i = 0, res = []; i < c; i++) {
- t = a[i];
- if (t >= 2.236067) {
- continue;
- } else {
- res.push(t * t);
- }
- }
- }
- r[1] = (new Date() - dt);
- alert('ao args: ' + r);
- }([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]));
- // * * * * * * * * * * * * * * * * *
- // Activation object scope
- // * * * * * * * * * * * * * * * * *
- (function () {
- 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],
- dt,
- index,
- i,
- c,
- r,
- t,
- res
- ;
- r = [];
- c = a.length;
- dt = new Date();
- index = 20000;
- while (index--) {
- a.map(function (t) { return t * t}).filter(function (t) { return t > 5});
- }
- r[0] = (new Date() - dt);
- dt = new Date();
- index = 20000;
- while (index--) {
- for (i = 0, res = []; i < c; i++) {
- t = a[i];
- if (t >= 2.236067) {
- continue;
- } else {
- res.push(t * t);
- }
- }
- }
- r[1] = (new Date() - dt);
- alert('ao: ' + r);
- }());
- // * * * * * * * * * * * * * * * * *
- // Global scope
- // * * * * * * * * * * * * * * * * *
- 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],
- dt,
- index,
- i,
- c,
- r,
- t,
- res
- ;
- r = [];
- c = a.length;
- dt = new Date();
- index = 20000;
- while (index--) {
- a.map(function (t) { return t * t}).filter(function (t) { return t > 5});
- }
- r[0] = (new Date() - dt);
- dt = new Date();
- index = 20000;
- while (index--) {
- for (i = 0, res = []; i < c; i++) {
- t = a[i];
- if (t >= 2.236067) {
- continue;
- } else {
- res.push(t * t);
- }
- }
- }
- r[1] = (new Date() - dt);
- alert('global: ' + r);
Код: 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, судя по результатам, выделяется в отдельный объект.
Ещё интересный тест.
- (function (r, dt, index, i, j) {
- dt = new Date();
- index = 50000;
- while(index--) {
- // Block A
- for (i = 0, j= 0; i < 20; i++) {
- j++;
- }
- // -------
- }
- r[0] = new Date - dt;
- dt = new Date();
- index = 50000;
- while(index--) {
- // Block B
- j = 0;
- j++;j++;j++;j++;j++;
- j++;j++;j++;j++;j++;
- j++;j++;j++;j++;j++;
- j++;j++;j++;j++;j++;
- // -------
- }
- r[1] = new Date - dt;
- alert(r);
- }([]));
Какой блок будет быстрее? Ответ: pastebin.com/hXxQb6pk
UPD В конечном итоге все упирается перерисовку интерфейса (reflow, redraw), однако, как показала моя практика, оптимизации reflow было не достаточно. Значительную часть съедали вызовы анонимных функций, устранив их был получен хороший прирост, особенно в древних браузерах, из-за которых мы и страдаем. Не воспринимайте эту статью как руководство к действию, пишите код как считаете удобным для вас. Я советую производить оптимизацию по необходимости.
21.12.2010 01:50+0300