比较Object/Dictionary/Array顺序读写性能

  • 本站文章除注明转载外,均为本站原创或者翻译。
  • 本站文章欢迎各种形式的转载,但请18岁以上的转载者注明文章出处,尊重我的劳动,也尊重你的智商;
  • 本站部分原创和翻译文章提供markdown格式源码,欢迎使用文章源码进行转载;
  • 本博客采用 WPCMD 维护;
  • 本文标题:比较Object/Dictionary/Array顺序读写性能
  • 本文链接:http://zengrong.net/post/1284.htm

2011-07-01更新:关于字符串创建时间的计算,以及一些新发现。


一个既需要顺序读取又需要随机读取的表,采用什么进行存储比较合适呢?

我们知道,Object和Dictionary适合随机存取,而Array和Vector适合顺序存取(关于Array和Vector的性能可以看这篇文章: Array/Vector/AS3DS/ds/dsforas性能比较 ),但它们之间的性能差距有大呢?

分析如下:

  1. Object的写性能低于Dictionary;
  2. Object与Dictionary的读写性能基本相当;
  3. Object在使用数字键名的时候,读写速度接近Array;
  4. AS会优化使用过的Object的字符串键名,因此对于整个程序的运行时间来说,创建字符串的时间不是问题;但对于性能测试来说,是很大的问题;
  5. AS也会优化创建变量的过程(或许是在编辑器层次优化?),所以对于字符串和数字等类型,把var放在循环外部,是一种良好的习惯,但对性能或许影响不大(相比而言,new的影响更大);
  6. 对于Object和Dictionary的读取,使用for each循环性能最高,已经接近Array了;
  7. 对于Array的读取,for循环性能最高。

为什么for each循环在Object与Array上的表现正好相反呢?这正体现了这两种数据结构的特性,for在顺序存取上性能高,for each在随机存取上性能高。因此,对于Object和Dictionary的读取,应尽量使用for each,而对Array和Vector则尽量使用for。

for each和for in循环在Object与Array上的性能,取决于Object是采用字符串键名还是采用数字键名。如果Object采用的是字符串键名,则for each和for in在Object上的表现就与Array一致。否则,她们的表现就与Array正好相反。

测试结果:

Object write:7582
Object for read:2309
Object for in read:234
Object for each read:103
Dictionary write:2557
Dictionary for read:2302
Dictionary for in read:242
Dictionary for each read:94
Array write:90
Array for read:64
Array for in read:81
Array for each read:79

package
{
import flash.display.Sprite;
import flash.utils.Dictionary;
import flash.utils.getTimer;

/**
 * 比较Object/Dictionary/Array顺序读写性能
 * @author zrong
 */
public class ObjectForSpeedTest extends Sprite
{
    public function ObjectForSpeedTest()
    {
        const max:int = 500000;
        var i:int = 0;
        var r:* = null;
        _obj = {};
        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            _obj['a'+i] = i;
        }
        trace2('Object write');

        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            r = _obj['a'+i];
        }
        trace2('Object for read');

        _tim = getTimer();
        for(var __objin:* in _obj)
        {
            r = _obj[__objin];
        }
        trace2('Object for in read');

        _tim = getTimer();
        for each(var __objeach:* in _obj)
        {
            r = __objeach;
        }
        trace2('Object for each read');

        _dic = new Dictionary();
        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            _dic['a'+i] = i;
        }
        trace2("Dictionary write");

        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            r = _dic['a'+i];
        }
        trace2("Dictionary for read");

        _tim = getTimer();
        for(var __dicin:* in _dic)
        {
            r = _dic[__dicin];
        }
        trace2('Dictionary for in read');

        _tim = getTimer();
        for each(var __diceach:* in _dic)
        {
            r = __diceach;
        }
        trace2('Dictionary for each read');

        _arr = [];
        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            _arr[i] = i;
        }
        trace2("Array write");

        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            r = _arr[i];
        }
        trace2("Array for read");

        _tim = getTimer();
        for(var __arrin:* in _arr)
        {
            r = _arr[__arrin];
        }
        trace2("Array for in read");

        _tim = getTimer();
        for each(var __arreach:* in _arr)
        {
            r = __arreach;
        }
        trace2("Array for each read");
    }

    private var _tim:int = 0;
    private var _obj:Object = {};
    private var _dic:Dictionary;
    private var _arr:Array = [];

    private function trace2($n:String):void
    {
        trace($n+':'+(getTimer() - _tim));
    }
}
}

2011-07-01更新:

测试环境:

  • SDK:4.5
  • FlashPlayer:10.3.181.22 debug

有网友在留言中提到,这个测试没有考虑字符串的创建时间,我仔细看了代码,确实是不够严谨。

将for循环中的var **放在了循环外部,不考虑var的创建时间,发现性能并没有什么变化。

[trace] Object write:7388
[trace] Object for read:2281
[trace] Object for in read:230
[trace] Object for each read:111
[trace] Dictionary write:2575
[trace] Dictionary for read:2294
[trace] Dictionary for in read:234
[trace] Dictionary for each read:109
[trace] Array write:91
[trace] Array for read:64
[trace] Array for in read:91
[trace] Array for each read:89

代码如下:

package
{
import flash.display.Sprite;
import flash.utils.Dictionary;
import flash.utils.getTimer;
/**
 * 比较Object/Dictionary/Array顺序读写性能,改进var
 * @author zrong
 * @data 2011-07-01
 */
public class ObjectForSpeedTest extends Sprite
{
    public function ObjectForSpeedTest()
    {
        const max:int = 500000;
        var i:int = 0;
        var r:* = null;
        _obj = {};

        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            _obj['a'+i] = i;
        }
        trace2('Object write');

        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            r = _obj['a'+i];
        }
        trace2('Object for read');

        var __objin:* = null;
        _tim = getTimer();
        for(__objin in _obj)
        {
            r = _obj[__objin];
        }
        trace2('Object for in read');

        var __objeach:* = null;
        _tim = getTimer();
        for each(__objeach in _obj)
        {
            r = __objeach;
        }
        trace2('Object for each read');

        _dic = new Dictionary();
        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            _dic['a'+i] = i;
        }
        trace2("Dictionary write");

        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            r = _dic['a'+i];
        }
        trace2("Dictionary for read");

        var __dicin:* = null;
        _tim = getTimer();
        for(__dicin in _dic)
        {
            r = _dic[__dicin];
        }
        trace2('Dictionary for in read');

        var __diceach:* = null;
        _tim = getTimer();
        for each(__diceach in _dic)
        {
            r = __diceach;
        }
        trace2('Dictionary for each read');

        _arr = [];
        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            _arr[i] = i;
        }
        trace2("Array write");

        _tim = getTimer();
        for(i=0;i<max;i++)
        {
            r = _arr[i];
        }
        trace2("Array for read");

        var __arrin:* = null;
        _tim = getTimer();
        for(__arrin in _arr)
        {
            r = _arr[__arrin];
        }
        trace2("Array for in read");

        var __arreach:* = null;
        _tim = getTimer();
        for each(__arreach in _arr)
        {
            r = __arreach;
        }
        trace2("Array for each read");
    }

    private var _tim:int = 0;
    private var _obj:Object = {};
    private var _dic:Dictionary;
    private var _arr:Array = [];

    private function trace2($n:String):void
    {
        trace($n+':'+(getTimer() - _tim));
    }
}
}

smithfox 讨论了一下,觉得留言中的“没考虑字符串的创建时间”应该指的是'a'+i这部分,于是写了个测试,发现50万个a+i的创建,确实需要消耗600毫秒的时间。

于是乎,我对创建字符串进行了优化,首先想到的方法是创建一个临时的Object,将50万个字符串丢进去做缓存。写Object的时候,从缓存中读入值,应该会省掉这600毫秒了吧?下面将只展示部分代码:

var __key:Object = {};

//建立字符串缓存
for(i=0;i<max;i++)
{
    __key['a'+i] = 'a'+i;
}

_tim = getTimer();
for(i=0;i<max;i++)
{
    _obj['a'+i] = i;
}
trace2('Object write');

由于粗心,上面对_obj进行写入的时候,并没有调用__key中缓存的值,但就是这样,速度却降到了2500毫秒

那么这样写会如何?

_tim = getTimer();
for(i=0;i<max;i++)
{
    _obj[__key['a'+i]] = i;
}
trace2('Object write');

为了调用__key中的缓存,我必须知道键名,而键名也是用'a'+i拼出来的,这个创建字符串的过程,依然在循环中。准确的说,在这个循环中不仅仅包含写入的时间,还包含创建字符串以及从Object中读取的时间。但即使是这样,速度也仅有2626毫秒

看着这有如神助的速度,我只能猜想Adobe在编译器上做了优化,或者在AVM中进行了缓存,这个优化针对Object或者Dictionary的键名。只要是使用过一次的键名,就会被缓存下来供下次使用。

那么再来看看对Object使用数字键名的结果吧:

[trace] Object write:142
[trace] Object for read:82
[trace] Object for in read:136
[trace] Object for each read:110
[trace] Dictionary write:7539
[trace] Dictionary for read:2349
[trace] Dictionary for in read:242
[trace] Dictionary for each read:110
[trace] Array write:127
[trace] Array for read:69
[trace] Array for in read:92
[trace] Array for each read:88

部分代码如下:

_tim = getTimer();
for(i=0;i<max;i++)
{
    _obj[i] = i;
}
trace2('Object write');

_tim = getTimer();
for(i=0;i<max;i++)
{
    r = _obj[i];
}
trace2('Object for read');

如此可见,对Object使用数字键名的速度,居然已经和数组相仿。或许,它们在AVM中就是一回事吧。