пятница, 23 октября 2009 г.

Мифы и реальность. Повышаем производительность ActionScript3 при работе с числами

Изучая язык программирования, важно знать тонкости и недостатки. Одним из моментов, на которые необходимо обращать свое внимание, это производительность. Так как же улучшить производительность во Flex приложениях?

По данной теме можно найти массу рекомендации, как на сайте разработчиков Adobe(Flex Application Performance: Tips and Techniques for Improving Flex Server Performance), так и просто в статьях блогеров.

При изучении второго сегмента статей по производительности, у меня возникло некоторое замешательство. Например, в одном из постов приведены сравнения двух разных вариантов одной операции, первый выполнялся за 15мс, второй за 8мс, и было указано, что второй быстрее на 66%. Взглянув мельком на цифры, промелькнула мысль: "Первый выполнялся почти в 2 раза быстрее, откуда 66%?". Пересчитал, 15/8*100%-100%=87.5%, эти цифры вызвали удивление. При этом встречаются ошибки в которых значение на порядок больше, чем расчетное. Бездумные перепосты таких данных встречаются и на русском языке, что довольно таки печально :(. А если ошибки были допущены не только в вычислениях?! Дальше пойдет речь о моих экспериментах в тестировании производительности с числовыми типами данных.

Условия проведения тестов:
- 200000 проходов в каждом тестирование операции, если не указаны другие условия.
- Начальное и конечное значение в диапазоне чисел, которые прогоняются через тест, устанавливается в равноудаленные числа от 0. Например, если чисел 200000, то диапазон будет [-100000;100000].
- Каждый тип тестирования повторялся 1000 раз.
- Промежуточная и конечная проверка результатов выполнения методов.

Примечание: время выполнения тестов у вас может отличатся от полученного мной.

Максимально используйте int вместо Number

При тестировании типов данных int, Number решил проверить производительность их при "Присвоение целого значения", "Суммирование", "Умножение", "Побитовые сдвиги".

Присвоение целого значения
1v(14-32мс):
var o:Number = i;

2v(14-31мс):
var n:int = i;

Соотношение времени выполнения методов в процентах (v1/v2*100):
      минимальное-48.38709677419355%(1v-15мс; 2v-31мс)
      максимальное-213.33333333333334%(1v-32мс; 2v-15мс)

Количество раз (v2 лучше, чем v1) - 355
Количество раз (v1 лучше, чем v2) - 325

Исходник теста.

В максимальном соотношении, работа с типом int на 113%(213%-100%) быстрее, чем с типом Number. В минимальном соотношении, работа с типом Number на 108%(100%/48% *100%-100%) быстрее, чем с типом int. При этом только в 355 тестах из 1000 тип int выиграл, в свою очередь преимущество на стороне типа Number было в 325 тестах.


Суммирование
1v(14-18мс):
var o:Number = 0;
o = o + 2;

2v(14-19мс):
var n:int = 0;
n = n + 2;

Соотношение времени выполнения методов в процентах (v1/v2*100):
      минимальное-78.94736842105263%(1v-15мс; 2v-19мс)
      максимальное-128.57142857142858%(1v-18мс; 2v-14мс)

Количество раз (v2 лучше, чем v1) - 534
Количество раз (v1 лучше, чем v2) - 211

Исходник теста.

Максимальное соотношение - int быстрее на 29%(129%-100%)
Минимальное соотношение - Number быстрее на 27%(100%/79%*100%-100%)


Умножение

1v(15-18мс):
var o:Number = 0;
o = o * 2;

2v(14-32мс):
var n:int = 0;
n = n * 2;

Соотношение времени выполнения методов в процентах (v1/v2*100):
      минимальное-50%(1v-16мс; 2v-32мс)
      максимальное-128.57142857142858%(1v-18мс; 2v-14мс)

Количество раз (v2 лучше, чем v1) - 622
Количество раз (v1 лучше, чем v2) - 201

Исходник теста.

Максимальное соотношение - int быстрее на 29%(129%-100%)
Минимальное соотношение - Number быстрее на 100%(100%/50%*100%-100%)


Побитовые сдвиги

1v(16-32мс):
var o:Number = 2;
o = o << 1;

2v(14-30мс):
var n:int = 2;
n = n << 1;

Соотношение времени выполнения методов в процентах (v1/v2*100):      минимальное-53.333333333333336%(1v-16мс; 2v-30мс)
      максимальное-213.33333333333334%(1v-32мс; 2v-15мс)

Количество раз (v2 лучше, чем v1) - 804
Количество раз (v1 лучше, чем v2) - 17

Исходник теста.

Максимальное соотношение - int быстрее на 113%(213%-100%)
Минимальное соотношение - Number быстрее на 88%(100%/53%*100%-100%)

Результаты не совсем однозначны, но действительно, если обратить внимание на количество раз, когда тип int выигрывает в производительности у типа Number, то в большинстве случаев целесообразней использовать тип int.

Инициализация переменных


Для этого теста количество циклов уменьшил с 200000 до 100000.

1v(41-59мс):
for(i = start; i < end; i++){ 
var m:Number = 1;
var m2:Number = 1;
var m3:Number = 1;
var m4:Number = 1;
var m5:Number = 1;
var m6:Number = 1;
var m7:Number = 1;
var m8:Number = 1;
var m9:Number = 1;
var m10:Number = 1;
}

2v(8-14мс):
for(i = start; i < end; i++) { 
var m:Number = 1, m2:Number = 1, m3:Number = 1, m4:Number = 1, m5:Number = 1, m6:Number = 1, m7:Number = 1;var m8:Number = 1, m9:Number = 1, m10:Number = 1;
}

Соотношение времени выполнения методов в процентах(v1/v2*100):
      минимальное-307.14285714285717%(1v-43мс; 2v-14мс)
      максимальное-655.5555555555555%(1v-59мс; 2v-9мс)

Количество раз (v2 лучше, чем v1) - 1000
Количество раз (v1 лучше, чем v2) - 0

Исходник теста.

Второй вариант быстрее от 207% до 556%!

Умножение на число, которое является степенью числа 2


Если у вас есть умножение, где один из множителей является степенью числа 2, следующего вида:
x = x * 2;
x = x * 64;

Вы можете заменить его следующим выражением
x = x << 1;
x = x << 6;

По утверждению, замена на второй метод дает результат на 300% быстрее. Для проверки утверждения взял вариант с умножением на 64.

1v(14-32мс):
x = x * 64;

2v(14-29мс):
x = x << 6;

Соотношение времени выполнения методов в процентах (v1/v2*100):      минимальное-55.172413793103445%(1v-16мс; 2v-29мс) 
максимальное-213.33333333333334%(1v-32мс; 2v-15мс)

Количество раз (v2 лучше, чем v1) - 377
Количество раз (v1 лучше, чем v2) - 334

Исходник теста.

Максимальное соотношение - битовый сдвиг на 113%(213%-100%) быстрее.
Минимальное соотношение - обычное умножение на 82%(100%/55%*100%-100%) быстрее.

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

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

Деление на число, которое является степенью числа 2


Деление вида
x = x / 2;
x = x / 64;

Можно заменить на
x = x >> 1;
x = x >> 6;

По утверждениям, второй вариант будет быстрее работать на 350%. Сразу можно отнести данное высказывание к мифу, так как, есть одно "маленькое" ограничение, -1 >> 1 = -1, но не как -0.5! Проведу все же тестирования, для подтверждения производительности, за основу возьму вариант с делением на 64.

1v(16-20мс):
x = x / 64;

2v(14-18мс):
x = x >> 6;

Соотношение времени выполнения методов в процентах (v1/v2*100):      минимальное-94.11764705882352%(1v-16мс; 2v-17мс)
      максимальное-142.85714285714286%(1v-20мс; 2v-14мс)

Количество раз (v2 лучше, чем v1) - 849
Количество раз (v1 лучше, чем v2) - 4

Исходник теста.

Действительно, второй вариант работает быстрее но всего на 42%!

Операцию деления лучше заменить операцией умножения



1v(16-34мс):
x = x / 2;

2v(16-33мс):
x = x * 0.5;

Соотношение времени выполнения методов в процентах (v1/v2*100):
      минимальное-54.54545454545454%(1v-18мс; 2v-33мс)
      максимальное-200%(1v-34мс; 2v-17мс)

Количество раз (v2 лучше, чем v1) - 370
Количество раз (v1 лучше, чем v2) - 261

Исходник теста.

Максимальное соотношение: второй вариант быстрее на 100%(200%-100%)
Минимальное соотношение: первый вариант быстрее на 82%(100%/55%*100%-100%)

Второй метод в абсолютном выражении(процент+количество раз) немного быстрее, но это превосходство не постоянное.

Использование операции "x += i;" быстрее, чем "x = x + i;"



1v(14-31мс):
x = x + i;

2v(14-33мс):
x += i;

Соотношение времени выполнения методов в процентах (v1/v2*100):
      минимальное-45.45454545454545%(1v-15мс; 2v-33мс)
      максимальное-182.35294117647058%(1v-31мс; 2v-17мс)

Количество раз (v2 лучше, чем v1) - 342
Количество раз (v1 лучше, чем v2) - 336

Исходник теста.

Максимальное соотношение - второй вариант быстрее на 82%(182%-100%).
Минимальное соотношение - первый вариант быстрее на 122%(100%/45%*100%-100%)

Миф!

Смена знака числа


Выражение
i = -i;

можно заменить на следующие
i = ~i + 1;


Утверждают, что быстрее на 300%.

1v(15-21мс):
x = -i;

2v(14-32мс):
x = ~i + 1;

Соотношение времени выполнения методов в процентах (v1/v2*100):
      минимальное-53.125%(1v-17мс; 2v-32мс)
      максимальное-133.33333333333331%(1v-20мс; 2v-15мс)

Количество раз (v2 лучше, чем v1) - 720
Количество раз (v1 лучше, чем v2) - 18

Исходник теста.

Действительно, в большинстве случаев второй вариант быстрее, но всего на 33%!

Проверка на четность


Выражение
x = (i % 2) == 0;

можно записать следующим образом
x = (i & 1) == 0;


Утверждают что быстрее на 600%.

1v(18-35мс):
x = (i % 2) == 0;

2v(14-24мс):
x = (i & 1) == 0;

Соотношение времени выполнения методов в процентах (v1/v2*100):
      минимальное-79.16666666666666%(1v-19мс; 2v-24мс)
      максимальное-218.75%(1v-35мс; 2v-16мс)

Количество раз (v2 лучше, чем v1) - 999
Количество раз (v1 лучше, чем v2) - 1

Исходник теста.

Второй вариант быстрее, но всего на 119%!

Сравнения равенства знаков для двух значений


Выражение
x = a * b > 0;

можно заменить следующим образом
x = a ^ b >= 0;

Говорят быстрее на 35%.Если значение "a" будет равное 0, то для всех b >=0 первый вариант будет давать false, второй - true. Без этого ограничения утверждение - просто миф!
1v(15-19мс):
x = i * (i+1000) > 0;

2v(14-19мс):
x = (i ^ (i+1000)) >= 0;

Соотношение времени выполнения методов в процентах (v1/v2*100):
      минимальное-78.94736842105263%(1v-15мс; 2v-19мс)
      максимальное-128.57142857142858%(1v-18мс; 2v-14мс)

Количество раз (v2 лучше, чем v1) - 620
Количество раз (v1 лучше, чем v2) - 156


Исходник теста.

По данным можно сделать вывод, что действительно в большинстве случаев второй вариант будет быстрее на 29%(129%-100%).

Доступ к элементам массива Array


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

1v(3-6мс):
for each(var i:int in list){
var el:int = i;
}
2v(1-3мс):
var count:int = list.length;
for(i = 0; i < count; i++){ 
var el:int = list[i]; 
}

Соотношение времени выполнения методов в процентах (v1/v2*100):
      минимальное-100%(1v-3мс; 2v-3мс)
      максимальное-500%(1v-5мс; 2v-1мс)

Количество раз (v2 лучше, чем v1) - 972
Количество раз (v1 лучше, чем v2) - 0

Исходник теста.

Очень странно, но доступ к элементам по индексу производится быстрее, чем перебор самих элементов. Достигается разница на 400%!!!!

Выводы

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

Дальше приведу подборку статей по данной тематике, но прежде, чем вносить изменения, проверте производительность и идентичность результатов методов :).

More performance tuning in Actionscript 3
Bitwise gems - fast integer math
ActionScript 3.0 and AVM2: Performance Tuning
AS3 Speed tests
TextField Performance + Flex Builder Profiler + 360Flex
Benchmarking: Introduction

Комментариев нет:

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