Pull to refresh

Сравнение операторов доступа к полям объектов

Reading time 10 min
Views 1.5K
Original author: Jackson Dunstan
Одной из клёвых особенностей AS3 (а ещё AS2 и JS) является возможность динамического доступа к полям любых объектов. Это приводит к созданию более «динамичного» кода, так как вам не нужно знать о существовании полей во время компиляции. Эта возможность, как и другие возможности динамических языков, может значительно повлиять на производительность приложения. Сегодня мы рассмотрим примеры, в которых будет показано, на сколько «медленным» является динамический доступ к полям.

Кстати, вот несколько примеров доступа к полям объектов:

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


Те же самые результаты в виде графиков:

imageimages.jacksondunstan.com/articles/1179/performance_all.png

Индексный оператор доступа был более «дорогим» в плане производительности для всех типов объектов, исключение составляет только Object. Даже можно сказать значительно более «дорогим». Для динамических объектов Array и Dictionary доступ к которым по прежнему «медленный», производительность улучшилась «всего-лишь» в 3 раза. Для объектов с «быстрым» доступом (не динамические классы), наблюдалось улучшение производительности в 400 раз! Теперь давайте посмотрим на результаты теста доступа только через оператор точки:

imageimages.jacksondunstan.com/articles/1179/performance_dot.png

Можно увидеть, что доступ к статическим полям значительно быстрее, чем доступ к динамическим полям. Запомните, что доступ к полям через оператор точки значительно быстрее, чем доступ к тем же полям через «индексный» оператор (за исключением класса Object). Это означает, что доступ к статическим полям, в сравнении с доступом к динамическим полям, является значительно более быстрым.

Теперь давайте взглянем на результаты тестов доступа через «индексный» оператор:

imageimages.jacksondunstan.com/articles/1179/performance_index.png

На данном графике видно, что разница в производительности большинства объектов «исчезла». Поля динамических классов, Dictionary и Array работают медленнее других классов. Странно, но данное утверждение относится и к Object, похоже, что в работе с этим классом нет особой разницы в способах доступа к полям.

Что касается сравнения чтения и записи полей, мы видим совершенно разные графики в зависимости от способа доступа. При использовании «индексного» оператора, запись всегда примерно на 10% медленнее, чем чтение. С другой стороны, оператор «точки» даёт противоречивые результаты. Динамические классы Object, Dictionary и Array с «идексным» оператором работают медленнее для записи, даже в случае со статическими полями, исключение составляют экземпляры динамических классов и объектов Class. Тем не менее, почти во всех классах разница в производительности составляет плюс/минус 20%.

В заключении можно сказать, что не нужно использовать индексный оператор доступа для улучшения производительности кода. Исключения могут составлять случаи, когда вы работаете с «чистыми» экземплярами Object, но они на столько медленны, что лучше избегать их использование в первую очередь.
Tags:
Hubs:
+10
Comments 20
Comments Comments 20

Articles