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

25 марта 2008

В предыдущей статье, посвященной оптимизации скриптов (Оптимизация скорости выполнения скриптов 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++];
}

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


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


Ускорение скорости загрузки страниц

13 марта 2008

Существуют достаточно много способов ускорения загрузки web-страниц, среди которых наиболее действенными можно назвать следующие:

  1. Уменьшение количества HTTP запросов к серверу.
  2. Использование кэширования
  3. GZip сжатие страниц
  4. Уменьшение размера файлов JavaScript и CSS

Конечно же, можно вручную уменьшить размер стилей, скриптов и самих web-страниц простым удалением пробелов, сжимать все это используя GZip (подробнее об этом здесь (англ.)), однако более практично использовать утилиту PHP Speedy. Как видно из названия, эта утилита написана для сайтов на PHP и предназначена для ускорения их загрузки. Она использует все вышеперечисленные методики: уменьшение количества запросов на сервер обеспечивается путем объединения нескольких скриптов (стилей) в один, который кэшируется для дальнейшего использования; скрипты, стили и сама страница сжимаются путем удаления лишнего форматирования (пробелов, табов, переноса строк), скрипты JavaScript сжимаются утилитой JSMin. Чтобы визуально продемонстрировать эффект применения утилиты, я разместил ниже 2 изображения с сайта PHP Speedy: первое показывает лог загрузки до использования PHP Speedy, сделанный в Firebug, второе - при использовании PHP Speedy.

Без использования PHP Speedy - 14 запросов, 4,44 секунды
Без использования PHP Speedy
При использовании PHP Speedy - 4 запроса, 1,1 секунды
При использовании PHP Speedy

Читать дальше »

Создание объекта XMLHTTPRequest

8 февраля 2008

Очень часто для создания объекта XMLHTTPRequest используется схема с использованием структуры try … catch из-за того, что в разных браузерах этот объект создаётся по разному. В Internet Explorer объект XMLHTTPRequest создаётся при помощи ActiveX, в то время как другие браузеры используют собственный объект XMLHTTPRequest. Схема, о которой я говорю представена ниже:

function getXMLHTTPRequest() {
	try {
		req = new XMLHttpRequest();
	} catch(err1) {
		try {
			req = new ActiveXObject("Msxml2.XMLHTTP");
		} catch (err2) {
			try {
				req = new ActiveXObject("Microsoft.XMLHTTP");
			} catch (err3) {
				req = false;
			}
		}
	}
	return req;
}

Читать дальше »

Запуск скриптов, требующих значительного времени выполнения.

29 января 2008

Большим препятствием для запуска скриптов, которые выполняются продолжительное время, является тот факт, что в JavaScript нет многопотоковой обработки. Это значит, что окно браузера не отвечает на события пользователя до тех пор, пока выполняющийся скрипт не закончит своей работы. Следовательно, скрипты, которые выполняются 1, 2 или более секунд, "замораживают" пользовательский интерфейс, что не совсем приятно для пользователя.

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

Читать дальше »

Оптимизация скорости выполнения скриптов JavaScript.

19 января 2008

О том, насколько важна оптимизация скриптов JavaScript, можно судить из сравнения с производительностью других языков программирования. JavaScript приблизительно в 5000 раз медленнее в сравнении с языком C, в 100 раз медленнее Java и в 10 раз медленнее Perl. Ниже представлены несколько простых методик увеличения производительности скриптов:

  1. Учитывайте область действия переменных.
  2. Не применяйте with.
  3. Храните часто употребляемые значения в локальных переменных.
  4. Уменьшайте количество выражений.
  5. Использование DOM.
  6. Используйте join() при конкатенации большого количества значений.
  7. Использование замыканий (closures).

Читать дальше »