Одной из клёвых особенностей 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, но они на столько медленны, что лучше избегать их использование в первую очередь.
