346
 

Оптимизация циклов

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

1. Оптимизация логического сравнения при итерациях цикла.

Рассмотрим обычный цикл for:

for (var i = 0; i < aList.length; i++) {
	// тело цикла
}

При каждой итерации этого цикла происходит сравнение счетчика i со свойством length массива aList. Но как известно, обращение к константе или обычной переменной гораздо быстрее, чем получение значения через свойства объектов. Поэтому этот цикл можно оптимизировать путем замены этого значения на переменную:

// переменная len будет хранить длину массива
var len = aList.length;
for (var i = 0; i < len; i++) {
	// тело цикла
}

// то же, что и предыдущий пример, но получение длины
// массива и инициализация счетчика объединены в одно выражение
for (var i = 0, len = aList.length; i < len; i++) {
	// тело цикла
}

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

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

for (var i = aList.length - 1; i >= 0; i--) {
	// тело цикла
}

В этом примере, счетчик инициализируется индексом последнего элемента в массиве (который на 1 меньше длины массива), затем при каждом цикле производится сравнение с нулем (можно также использовать выражение i > -1) и уменьшение счетчика на единицу.

Тот же цикл можно еще более ускорить за счет объединения выражений сравнения и декремента:

for (var i = aList.length; --i >= 0;) {
	// тело цикла
}

Это самый быстрый из известных мне циклов. Кроме высокой скорости прохода, он имеет еще и очень компактный код.

2. Использование do … while вместо while.

Можно заменить циклы while на do … while чтобы увеличить скорость выполнения. Допустим мы имеем следующий цикл:

var i = 0;
while (i < aList.length) {
	// тело цикла
	i++;
}

Этот код можно переписать с использованием do … while без изменения результата выполнения:

var i = 0;
do {
	// тело цикла

	i++;
} while (i < aList.length);

Этот цикл будет работать быстрее цикла while, но его можно еще более оптимизировать, прогоняя его в обратном порядке:

var i = aList.length - 1;
do {
	// тело цикла

	i--;
} while (i >= 0);

И еще более ускоряем его путем объединения декремента со сравнением:

var i = aList.length - 1;
do {
	// тело цикла

} while (--i >= 0);

В итоге получаем максимально оптимизированный цикл.

3. “Разворачивание” циклов.

Вместо того, чтобы выполнять одно выражение внутри цикла при каждом его прохождении, можно “развернуть” его, выполняя сразу несколько выражений при одной итерации. Рассмотрим пример:

var aList = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
var iSum = 0;

for (var i = 0, len = aList.length; i < len; i++) {
	iSum += aList[i];
}

Этот цикл выполняет 20 итераций, каждый раз добавляя очередное значение массива к переменной iSum. Ту же операцию можно выполнять несколько раз внутри цикла, каждый раз увеличивая значение счетчика на единицу:

var aList = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
var iSum = 0;

for (var i = 0, len = aList.length; i < len; i++) {
	iSum += aList[i];
	i++;
	iSum += aList[i];
	i++;
	iSum += aList[i];
	i++;
	iSum += aList[i];
	i++;
}

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

var aList = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
var iSum = 0;

for (var i = 0, len = aList.length; i < len; i++) {
	iSum += aList[i++];
	iSum += aList[i++];
	iSum += aList[i++];
	iSum += aList[i++];
}

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


Три упомянутых в статье метода довольно таки просты, но при этом способны значительно увеличить производительность. Если она конечно же для вас важна…


Добавить в закладки:

Комментарии на “Оптимизация циклов”

  1. 1)
    ‘do-while’ крайне редко используется в javascript потому как кроме того, что эта конструкция в общем-то не быстрее ‘while’, она ещё и требует разового выполнения правой от ‘do’ части, а это неудобно при работе с коллекциями/массивами, приходится следить за индексом, чуть подробнее когда-то отвечал тут - http://forum.htmlbook.ru/viewtopic.php?pid=37149#p37149

    2)
    там где вы сравниваете ‘–i’ с нулём можно уйти от этого, оставив ‘i–’ в одиночестве, ‘while’ всё равно превратит число в булев, при нуле цикл остановится.

  2. Тут ошибка:
    var i = aList.length;
    do {
    // тело цикла

    } while (–i >= 0);
    начальное значение индекса за пределом размера массива.

    использую
    for (i = aList.length; –i;) {
    // тело цикла
    }

    Осторожно, внутри обьявления цикла for бесполезно писать VAR i ,
    переменная i всеравно не будет локальной внутри цикла,
    она будет локальной для функции в которой объялен цикл.
    Что черевато конфликтами при невнимательности.

  3. Алексей
    Спасибо за замечание, это действительно ошибка. Должно быть var i = aList.length - 1;
    Что же касается объявления i, то var для того и нужен, чтобы объявлять переменную локальной внутри функции. Иначе переменная i будет объявлена как глобальная, что чревато проблемами, в том числе и с производительностью. Кроме того, невозможно объявить переменную локальной внутри цикла.

  4. Алексей, предложенный вами вариант не точен, т.к. цикл не дойдёт до первого элемента - aList[0]. Внутри ‘for’ объявлять переменную можно, никакой разницы кроме стилистической. ;)

    Автор, вы ошиблись не в том, что не так задали начальное значение ‘i’, а в самом использовании ‘do-while’. Представьте, что массив/коллекция с нулевой длиной (обычное дело), в этом случае вы что с вариантом ‘i=aList.length’, что с вариантом ‘i=aList.length-1′ выйдете за пределы массива/коллекции и моментально получите ошибку. Требуется дополнительный контроль индекса. Помножьте это на то, что ‘do-while’ по большому счёту не быстрее ‘while’ и поймёте, почему в javascript эту конструкцию практически не используют ради скорости, только и исключительно по делу. Я бы рекомендацию в п.2 снял вообще. Удачи. ;)

  5. Zeroglif, а вы проверте, прекрасно доходит до 0,
    только я ожибся надо двойной минус после переменной индекса во втором параметре for ставить.
    тогда при каждом цикле сначала провериться можно ли продолжать (i==true или что тоже самое не равно 0),
    а затем уменьшится на еденицу и начнет исполнять тело цикла.

    for (i = aList.length; i—;) {
    // тело цикла
    }

  6. > Zeroglif, а вы проверте, прекрасно доходит до 0, только я ошибся
    Так “я ошибся” или “прекрасно доходит”??? Одно из двух. ;) Предложенный вами вариант был неточен, ошибку вы уже нашли.

  7. Zeroglif
    Информацию о do…while я нашел в книге “Professional JavaScript for WebDevelopers” (N. Zakas). Тест которым я сравнивал while и do…while я выложил здесь. В FF и IE я получил ускорение 9% и 6% соответственно (каждый цикл прогонялся 1000 раз и затем вычислялось среднее значение). В опере разницы практически не было.
    Что касается массивов/коллекций с нулевой длиной, то тут вы на все 100 правы - если есть вероятность того, что массив может быть нулевой длины, то нужно выполнять проверку, что не очень то удобно.
    Ну а стоит ли использовать этот метод оставлю на суд читателей. Все таки ситуации разные бывают.

  8. Подскажите пожалуйста, что быстрее работает while или for?!

  9. @Даниил
    Честно сказать, затрудняюсь ответить. Посмотрите вот эту страницу: blog.sun.com - этот тест производительности был сделан совсем недавно. Думаю там вы найдете ответ.

Оставить комментарий

JSToolbox создан на основе WordPress