Одной из клёвых особенностей AS3 (а ещё AS2 и JS) является возможность динамического доступа к полям любых объектов. Это приводит к созданию более «динамичного» кода, так как вам не нужно знать о существовании полей во время компиляции. Эта возможность, как и другие возможности динамических языков, может значительно повлиять на производительность приложения. Сегодня мы рассмотрим примеры, в которых будет показано, на сколько «медленным» является динамический доступ к полям.
Кстати, вот несколько примеров доступа к полям объектов:
В случае доступа через оператор точки, вам необходимо знать, к какому полю вы хотите обратиться во время компиляции. В противоположность этому, доступ к полям через «индекс» отключает все проверки ошибок на этапе компиляции и даёт возможность доступа к свойствам с помощью любых выражений: строки, свойства, результаты вычислений.
Зная это, я обновил приложение для тестирования производительности, которое создавалось для тестирования динамического доступа к полям, и включил в него операторы чтения и записи, а так же добавил больше тестов за 1 цикл, чтобы уменьшить «влияние» нагрузок циклов. Результаты тестирования и графики с наглядной информацией прилагаются.
Технические характеристики окружения, где проводились тесты:
* Flex SDK (MXMLC) 4.1.0.16076, компилирование в release режиме
* Релизная версия Flash Player 10.2.154.27
* 2.4 Ghz Intel Core i5
* Mac OS X 10.6.7
Полученные результаты:
Те же самые результаты в виде графиков:
images.jacksondunstan.com/articles/1179/performance_all.png
Индексный оператор доступа был более «дорогим» в плане производительности для всех типов объектов, исключение составляет только Object. Даже можно сказать значительно более «дорогим». Для динамических объектов Array и Dictionary доступ к которым по прежнему «медленный», производительность улучшилась «всего-лишь» в 3 раза. Для объектов с «быстрым» доступом (не динамические классы), наблюдалось улучшение производительности в 400 раз! Теперь давайте посмотрим на результаты теста доступа только через оператор точки:
images.jacksondunstan.com/articles/1179/performance_dot.png
Можно увидеть, что доступ к статическим полям значительно быстрее, чем доступ к динамическим полям. Запомните, что доступ к полям через оператор точки значительно быстрее, чем доступ к тем же полям через «индексный» оператор (за исключением класса Object). Это означает, что доступ к статическим полям, в сравнении с доступом к динамическим полям, является значительно более быстрым.
Теперь давайте взглянем на результаты тестов доступа через «индексный» оператор:
images.jacksondunstan.com/articles/1179/performance_index.png
На данном графике видно, что разница в производительности большинства объектов «исчезла». Поля динамических классов, Dictionary и Array работают медленнее других классов. Странно, но данное утверждение относится и к Object, похоже, что в работе с этим классом нет особой разницы в способах доступа к полям.
Что касается сравнения чтения и записи полей, мы видим совершенно разные графики в зависимости от способа доступа. При использовании «индексного» оператора, запись всегда примерно на 10% медленнее, чем чтение. С другой стороны, оператор «точки» даёт противоречивые результаты. Динамические классы Object, Dictionary и Array с «идексным» оператором работают медленнее для записи, даже в случае со статическими полями, исключение составляют экземпляры динамических классов и объектов Class. Тем не менее, почти во всех классах разница в производительности составляет плюс/минус 20%.
В заключении можно сказать, что не нужно использовать индексный оператор доступа для улучшения производительности кода. Исключения могут составлять случаи, когда вы работаете с «чистыми» экземплярами Object, но они на столько медленны, что лучше избегать их использование в первую очередь.
Кстати, вот несколько примеров доступа к полям объектов:
function foo(p:Point): void
{
p.x; // "dot" operator method of reading fields
p["x"]; // "index" operator method of reading fields
p.x = 1; // "dot" operator method of writing fields
p["x"] = 1; // "index" operator method of writing fields
}
В случае доступа через оператор точки, вам необходимо знать, к какому полю вы хотите обратиться во время компиляции. В противоположность этому, доступ к полям через «индекс» отключает все проверки ошибок на этапе компиляции и даёт возможность доступа к свойствам с помощью любых выражений: строки, свойства, результаты вычислений.
Зная это, я обновил приложение для тестирования производительности, которое создавалось для тестирования динамического доступа к полям, и включил в него операторы чтения и записи, а так же добавил больше тестов за 1 цикл, чтобы уменьшить «влияние» нагрузок циклов. Результаты тестирования и графики с наглядной информацией прилагаются.
package
{
import flash.display.*;
import flash.events.*;
import flash.utils.*;
import flash.text.*;
import flash.geom.*;
public class FieldAccessMethods extends Sprite
{
public static var VAL:Number=0;
private var __logger:TextField = new TextField();
private function row(...vals): void
{
__logger.appendText(vals.join(",")+"\n");
}
public function FieldAccessMethods()
{
__logger.autoSize = TextFieldAutoSize.LEFT;
addChild(__logger);
var i:int;
const REPS:int = 1000000;
var beforeTime:int;
var afterTime:int;
var readDotTime:int;
var writeDotTime:int;
var readIndexTime:int;
var writeIndexTime:int;
var p:Point = new Point(0,0);
var mp:MyPoint = new MyPoint();
var md:MyDynamic = new MyDynamic();
var d:Dictionary = new Dictionary();
d.x=0;
var a:Array = [0];
a.x=0;
var c:Class = DynamicAccess;
var dc:Class = MyDynamic;
var o:Object = {x:0};
row("Type", "Read (dot)", "Write (dot)", "Read (index)", "Write (index)");
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
p.x;p.x;p.x;p.x;p.x;
p.x;p.x;p.x;p.x;p.x;
}
afterTime = getTimer();
readDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
p.x=0;p.x=0;p.x=0;p.x=0;p.x=0;
p.x=0;p.x=0;p.x=0;p.x=0;p.x=0;
}
afterTime = getTimer();
writeDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
p["x"];p["x"];p["x"];p["x"];p["x"];
p["x"];p["x"];p["x"];p["x"];p["x"];
}
afterTime = getTimer();
readIndexTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
p["x"]=0;p["x"]=0;p["x"]=0;p["x"]=0;p["x"]=0;
p["x"]=0;p["x"]=0;p["x"]=0;p["x"]=0;p["x"]=0;
}
afterTime = getTimer();
writeIndexTime = afterTime - beforeTime;
row("Point", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
mp.x;mp.x;mp.x;mp.x;mp.x;
mp.x;mp.x;mp.x;mp.x;mp.x;
}
afterTime = getTimer();
readDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
mp.x=0;mp.x=0;mp.x=0;mp.x=0;mp.x=0;
mp.x=0;mp.x=0;mp.x=0;mp.x=0;mp.x=0;
}
afterTime = getTimer();
writeDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
mp["x"];mp["x"];mp["x"];mp["x"];mp["x"];
mp["x"];mp["x"];mp["x"];mp["x"];mp["x"];
}
afterTime = getTimer();
readIndexTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
mp["x"]=0;mp["x"]=0;mp["x"]=0;mp["x"]=0;mp["x"]=0;
mp["x"]=0;mp["x"]=0;mp["x"]=0;mp["x"]=0;mp["x"]=0;
}
afterTime = getTimer();
writeIndexTime = afterTime - beforeTime;
row("MyPoint", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
md.x;md.x;md.x;md.x;md.x;
md.x;md.x;md.x;md.x;md.x;
}
afterTime = getTimer();
readDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
md.x=0;md.x=0;md.x=0;md.x=0;md.x=0;
md.x=0;md.x=0;md.x=0;md.x=0;md.x=0;
}
afterTime = getTimer();
writeDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
md["x"];md["x"];md["x"];md["x"];md["x"];
md["x"];md["x"];md["x"];md["x"];md["x"];
}
afterTime = getTimer();
readIndexTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
md["x"]=0;md["x"]=0;md["x"]=0;md["x"]=0;md["x"]=0;
md["x"]=0;md["x"]=0;md["x"]=0;md["x"]=0;md["x"]=0;
}
afterTime = getTimer();
writeIndexTime = afterTime - beforeTime;
row("MyDynamic (existing)", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
md.z;md.z;md.z;md.z;md.z;
md.z;md.z;md.z;md.z;md.z;
}
afterTime = getTimer();
readDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
md.z=0;md.z=0;md.z=0;md.z=0;md.z=0;
md.z=0;md.z=0;md.z=0;md.z=0;md.z=0;
}
afterTime = getTimer();
writeDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
md["z"];md["z"];md["z"];md["z"];md["z"];
md["z"];md["z"];md["z"];md["z"];md["z"];
}
afterTime = getTimer();
readIndexTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
md["z"]=0;md["z"]=0;md["z"]=0;md["z"]=0;md["z"]=0;
md["z"]=0;md["z"]=0;md["z"]=0;md["z"]=0;md["z"]=0;
}
afterTime = getTimer();
writeIndexTime = afterTime - beforeTime;
row("MyDynamic (added)", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
d.x;d.x;d.x;d.x;d.x;
d.x;d.x;d.x;d.x;d.x;
}
afterTime = getTimer();
readDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
d.x=0;d.x=0;d.x=0;d.x=0;d.x=0;
d.x=0;d.x=0;d.x=0;d.x=0;d.x=0;
}
afterTime = getTimer();
writeDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
d["x"];d["x"];d["x"];d["x"];d["x"];
d["x"];d["x"];d["x"];d["x"];d["x"];
}
afterTime = getTimer();
readIndexTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
d["x"]=0;d["x"]=0;d["x"]=0;d["x"]=0;d["x"]=0;
d["x"]=0;d["x"]=0;d["x"]=0;d["x"]=0;d["x"]=0;
}
afterTime = getTimer();
writeIndexTime = afterTime - beforeTime;
row("Dictionary", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
a.x;a.x;a.x;a.x;a.x;
a.x;a.x;a.x;a.x;a.x;
}
afterTime = getTimer();
readDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
a.x=0;a.x=0;a.x=0;a.x=0;a.x=0;
a.x=0;a.x=0;a.x=0;a.x=0;a.x=0;
}
afterTime = getTimer();
writeDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
a["x"];a["x"];a["x"];a["x"];a["x"];
a["x"];a["x"];a["x"];a["x"];a["x"];
}
afterTime = getTimer();
readIndexTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
a["x"]=0;a["x"]=0;a["x"]=0;a["x"]=0;a["x"]=0;
a["x"]=0;a["x"]=0;a["x"]=0;a["x"]=0;a["x"]=0;
}
afterTime = getTimer();
writeIndexTime = afterTime - beforeTime;
row("Array", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
c.VAL;c.VAL;c.VAL;c.VAL;c.VAL;
c.VAL;c.VAL;c.VAL;c.VAL;c.VAL;
}
afterTime = getTimer();
readDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
c.VAL=0;c.VAL=0;c.VAL=0;c.VAL=0;c.VAL=0;
c.VAL=0;c.VAL=0;c.VAL=0;c.VAL=0;c.VAL=0;
}
afterTime = getTimer();
writeDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
c["VAL"];c["VAL"];c["VAL"];c["VAL"];c["VAL"];
c["VAL"];c["VAL"];c["VAL"];c["VAL"];c["VAL"];
}
afterTime = getTimer();
readIndexTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;
c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;c["VAL"]=0;
}
afterTime = getTimer();
writeIndexTime = afterTime - beforeTime;
row("Static Class", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
dc.VAL;dc.VAL;dc.VAL;dc.VAL;dc.VAL;
dc.VAL;dc.VAL;dc.VAL;dc.VAL;dc.VAL;
}
afterTime = getTimer();
readDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
dc.VAL=0;dc.VAL=0;dc.VAL=0;dc.VAL=0;dc.VAL=0;
dc.VAL=0;dc.VAL=0;dc.VAL=0;dc.VAL=0;dc.VAL=0;
}
afterTime = getTimer();
writeDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
dc["VAL"];dc["VAL"];dc["VAL"];dc["VAL"];dc["VAL"];
dc["VAL"];dc["VAL"];dc["VAL"];dc["VAL"];dc["VAL"];
}
afterTime = getTimer();
readIndexTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;
dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;dc["VAL"]=0;
}
afterTime = getTimer();
writeIndexTime = afterTime - beforeTime;
row("Dynamic Class", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
o.x;o.x;o.x;o.x;o.x;
o.x;o.x;o.x;o.x;o.x;
}
afterTime = getTimer();
readDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
o.x=0;o.x=0;o.x=0;o.x=0;o.x=0;
o.x=0;o.x=0;o.x=0;o.x=0;o.x=0;
}
afterTime = getTimer();
writeDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
o["x"];o["x"];o["x"];o["x"];o["x"];
o["x"];o["x"];o["x"];o["x"];o["x"];
}
afterTime = getTimer();
readDotTime = afterTime - beforeTime;
beforeTime = getTimer();
for (i=0; i < REPS; ++i)
{
o["x"]=0;o["x"]=0;o["x"]=0;o["x"]=0;o["x"]=0;
o["x"]=0;o["x"]=0;o["x"]=0;o["x"]=0;o["x"]=0;
}
afterTime = getTimer();
writeDotTime = afterTime - beforeTime;
row("Object", readDotTime, writeDotTime, readIndexTime, writeIndexTime);
}
private function foo(): void {}
}
}
class MyPoint
{
public var x:Number=0;
public var y:Number=0;
}
dynamic class MyDynamic
{
public var x:Number=0;
public var y:Number=0;
public static var VAL:Number=0;
}
Технические характеристики окружения, где проводились тесты:
* Flex SDK (MXMLC) 4.1.0.16076, компилирование в release режиме
* Релизная версия Flash Player 10.2.154.27
* 2.4 Ghz Intel Core i5
* Mac OS X 10.6.7
Полученные результаты:
Тип | Чтение (точка) | Запись (точка) | Чтение (индекс) | Запись (индекс) |
---|---|---|---|---|
Point | 2 | 12 | 816 | 892 |
MyPoint | 2 | 9 | 812 | 890 |
MyDynamic (existing) | 3 | 9 | 813 | 892 |
MyDynamic (added) | 555 | 369 | 855 | 1021 |
Dictionary | 305 | 427 | 995 | 1168 |
Array | 404 | 510 | 1207 | 1380 |
Static Class | 143 | 103 | 841 | 898 |
Dynamic Class | 140 | 103 | 809 | 886 |
Object | 831 | 1040 | 809 | 886 |
Те же самые результаты в виде графиков:
images.jacksondunstan.com/articles/1179/performance_all.png
Индексный оператор доступа был более «дорогим» в плане производительности для всех типов объектов, исключение составляет только Object. Даже можно сказать значительно более «дорогим». Для динамических объектов Array и Dictionary доступ к которым по прежнему «медленный», производительность улучшилась «всего-лишь» в 3 раза. Для объектов с «быстрым» доступом (не динамические классы), наблюдалось улучшение производительности в 400 раз! Теперь давайте посмотрим на результаты теста доступа только через оператор точки:
images.jacksondunstan.com/articles/1179/performance_dot.png
Можно увидеть, что доступ к статическим полям значительно быстрее, чем доступ к динамическим полям. Запомните, что доступ к полям через оператор точки значительно быстрее, чем доступ к тем же полям через «индексный» оператор (за исключением класса Object). Это означает, что доступ к статическим полям, в сравнении с доступом к динамическим полям, является значительно более быстрым.
Теперь давайте взглянем на результаты тестов доступа через «индексный» оператор:
images.jacksondunstan.com/articles/1179/performance_index.png
На данном графике видно, что разница в производительности большинства объектов «исчезла». Поля динамических классов, Dictionary и Array работают медленнее других классов. Странно, но данное утверждение относится и к Object, похоже, что в работе с этим классом нет особой разницы в способах доступа к полям.
Что касается сравнения чтения и записи полей, мы видим совершенно разные графики в зависимости от способа доступа. При использовании «индексного» оператора, запись всегда примерно на 10% медленнее, чем чтение. С другой стороны, оператор «точки» даёт противоречивые результаты. Динамические классы Object, Dictionary и Array с «идексным» оператором работают медленнее для записи, даже в случае со статическими полями, исключение составляют экземпляры динамических классов и объектов Class. Тем не менее, почти во всех классах разница в производительности составляет плюс/минус 20%.
В заключении можно сказать, что не нужно использовать индексный оператор доступа для улучшения производительности кода. Исключения могут составлять случаи, когда вы работаете с «чистыми» экземплярами Object, но они на столько медленны, что лучше избегать их использование в первую очередь.