📜 ⬆️ ⬇️

Comparing Access Operators to Object Fields

One of the cool features of AS3 (and also AS2 and JS) is the ability to dynamically access the fields of any objects. This leads to the creation of more “dynamic” code, since you do not need to know about the existence of fields at compile time. This feature, like other dynamic language features, can significantly affect application performance. Today we will look at examples in which it will be shown how "slow" dynamic field access is.

By the way, here are some examples of access to the fields of objects:

function foo(p:Point): void { px; // "dot" operator method of reading fields p["x"]; // "index" operator method of reading fields px = 1; // "dot" operator method of writing fields p["x"] = 1; // "index" operator method of writing fields } 


In the case of access through a dot operator, you need to know which field you want to access at compile time. In contrast, access to the fields through the "index" disables all error checks at the compilation stage and allows access to the properties using any expressions: strings, properties, and results of calculations.
')
Knowing this, I updated the performance test application that was created to test dynamic field access, and included read and write statements, and also added more tests in 1 cycle to reduce the “impact” of cycle loads. Test results and graphs with visual information are attached.

 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(); dx=0; var a:Array = [0]; ax=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) { px;px;px;px;px; px;px;px;px;px; } afterTime = getTimer(); readDotTime = afterTime - beforeTime; beforeTime = getTimer(); for (i=0; i < REPS; ++i) { px=0;px=0;px=0;px=0;px=0; px=0;px=0;px=0;px=0;px=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) { dx;dx;dx;dx;dx; dx;dx;dx;dx;dx; } afterTime = getTimer(); readDotTime = afterTime - beforeTime; beforeTime = getTimer(); for (i=0; i < REPS; ++i) { dx=0;dx=0;dx=0;dx=0;dx=0; dx=0;dx=0;dx=0;dx=0;dx=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) { ax;ax;ax;ax;ax; ax;ax;ax;ax;ax; } afterTime = getTimer(); readDotTime = afterTime - beforeTime; beforeTime = getTimer(); for (i=0; i < REPS; ++i) { ax=0;ax=0;ax=0;ax=0;ax=0; ax=0;ax=0;ax=0;ax=0;ax=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) { ox;ox;ox;ox;ox; ox;ox;ox;ox;ox; } afterTime = getTimer(); readDotTime = afterTime - beforeTime; beforeTime = getTimer(); for (i=0; i < REPS; ++i) { ox=0;ox=0;ox=0;ox=0;ox=0; ox=0;ox=0;ox=0;ox=0;ox=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; } 


Technical characteristics of the environment where the tests were conducted:
* Flex SDK (MXMLC) 4.1.0.16076, compiled in release mode
* Release version of Flash Player 10.2.154.27
* 2.4 Ghz Intel Core i5
* Mac OS X 10.6.7

Results:
Type ofRead (full stop)Record (full stop)Reading (index)Record (index)
Point212816892
Mypoint29812890
MyDynamic (existing)39813892
MyDynamic (added)5553698551021
Dictionary3054279951168
Array40451012071380
Static class143103841898
Dynamic class140103809886
Object8311040809886


The same results in the form of graphs:

image images.jacksondunstan.com/articles/1179/performance_all.png

The index access operator was more “expensive” in terms of performance for all types of objects, with the exception of Object. I can even say much more "expensive." For the dynamic Array and Dictionary objects, access to which is still “slow”, the performance has improved “only-only” 3 times. For objects with “fast” access (non-dynamic classes), a performance improvement of 400 times was observed! Now let's look at the results of the access test only through the dot operator:

image images.jacksondunstan.com/articles/1179/performance_dot.png

You can see that access to static fields is much faster than access to dynamic fields. Remember that accessing fields through the dot operator is much faster than accessing the same fields through the "index" operator (with the exception of the Object class). This means that access to static fields, in comparison with access to dynamic fields, is much faster.

Now let's take a look at the results of access tests through the "index" operator:

image images.jacksondunstan.com/articles/1179/performance_index.png

This graph shows that the performance difference of most objects has "disappeared". The fields of dynamic classes, Dictionary and Array are slower than other classes. Strange, but this statement applies to Object, it seems that in working with this class there is not much difference in the way the fields are accessed.

As for the comparison of reading and writing fields, we see completely different graphs depending on the access method. When using the "index" operator, writing is always about 10% slower than reading. On the other hand, the “point” operator gives conflicting results. The dynamic Object, Dictionary and Array classes with the “IDEX” operator are slower to write, even in the case of static fields, with the exception of instances of dynamic classes and Class objects. However, in almost all classes, the performance difference is plus / minus 20%.

In conclusion, you can say that you do not need to use the index access operator to improve the performance of the code. Exceptions can be cases when you work with “clean” Object instances, but they are so slow that it is better to avoid using them in the first place.

Source: https://habr.com/ru/post/118655/


All Articles