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

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

  1package
  2{
  3import flash.display.Sprite;
  4import flash.utils.Dictionary;
  5import flash.utils.getTimer;
  6
  7/**
  8 * 比较Object/Dictionary/Array顺序读写性能
  9 * @author zrong
 10 */
 11public class ObjectForSpeedTest extends Sprite
 12{
 13	public function ObjectForSpeedTest()
 14	{
 15		const max:int = 500000;
 16		var i:int = 0;
 17		var r:* = null;
 18		_obj = {};
 19		_tim = getTimer();
 20		for(i=0;i<max;i++)
 21		{
 22			_obj['a'+i] = i;
 23		}
 24		trace2('Object write');
 25		
 26		_tim = getTimer();
 27		for(i=0;i<max;i++)
 28		{
 29			r = _obj['a'+i];
 30		}
 31		trace2('Object for read');
 32		
 33		_tim = getTimer();
 34		for(var __objin:* in _obj)
 35		{
 36			r = _obj[__objin];
 37		}
 38		trace2('Object for in read');
 39		
 40		_tim = getTimer();
 41		for each(var __objeach:* in _obj)
 42		{
 43			r = __objeach;
 44		}
 45		trace2('Object for each read');
 46		
 47		_dic = new Dictionary();
 48		_tim = getTimer();
 49		for(i=0;i<max;i++)
 50		{
 51			_dic['a'+i] = i;
 52		}
 53		trace2("Dictionary write");
 54		
 55		_tim = getTimer();
 56		for(i=0;i<max;i++)
 57		{
 58			r = _dic['a'+i];
 59		}
 60		trace2("Dictionary for read");
 61		
 62		_tim = getTimer();
 63		for(var __dicin:* in _dic)
 64		{
 65			r = _dic[__dicin];
 66		}
 67		trace2('Dictionary for in read');
 68		
 69		_tim = getTimer();
 70		for each(var __diceach:* in _dic)
 71		{
 72			r = __diceach;
 73		}
 74		trace2('Dictionary for each read');
 75		
 76		_arr = [];
 77		_tim = getTimer();
 78		for(i=0;i<max;i++)
 79		{
 80			_arr[i] = i;
 81		}
 82		trace2("Array write");
 83		
 84		_tim = getTimer();
 85		for(i=0;i<max;i++)
 86		{
 87			r = _arr[i];
 88		}
 89		trace2("Array for read");
 90		
 91		_tim = getTimer();
 92		for(var __arrin:* in _arr)
 93		{
 94			r = _arr[__arrin];
 95		}
 96		trace2("Array for in read");
 97		
 98		_tim = getTimer();
 99		for each(var __arreach:* in _arr)
100		{
101			r = __arreach;
102		}
103		trace2("Array for each read");
104	}
105	
106	private var _tim:int = 0;
107	private var _obj:Object = {};
108	private var _dic:Dictionary;
109	private var _arr:Array = [];
110	
111	private function trace2($n:String):void
112	{
113		trace($n+':'+(getTimer() - _tim));
114	}
115}
116}

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

代码如下:

  1package
  2{
  3import flash.display.Sprite;
  4import flash.utils.Dictionary;
  5import flash.utils.getTimer;
  6/**
  7 * 比较Object/Dictionary/Array顺序读写性能,改进var
  8 * @author zrong
  9 * @data 2011-07-01
 10 */
 11public class ObjectForSpeedTest extends Sprite
 12{
 13	public function ObjectForSpeedTest()
 14	{
 15		const max:int = 500000;
 16		var i:int = 0;
 17		var r:* = null;
 18		_obj = {};
 19
 20		_tim = getTimer();
 21		for(i=0;i<max;i++)
 22		{
 23			_obj['a'+i] = i;
 24		}
 25		trace2('Object write');
 26		
 27		_tim = getTimer();
 28		for(i=0;i<max;i++)
 29		{
 30			r = _obj['a'+i];
 31		}
 32		trace2('Object for read');
 33 
 34		var __objin:* = null;
 35		_tim = getTimer();
 36		for(__objin in _obj)
 37		{
 38			r = _obj[__objin];
 39		}
 40		trace2('Object for in read');
 41 
 42		var __objeach:* = null;
 43		_tim = getTimer();
 44		for each(__objeach in _obj)
 45		{
 46			r = __objeach;
 47		}
 48		trace2('Object for each read');
 49 
 50		_dic = new Dictionary();
 51		_tim = getTimer();
 52		for(i=0;i<max;i++)
 53		{
 54			_dic['a'+i] = i;
 55		}
 56		trace2("Dictionary write");
 57 
 58		_tim = getTimer();
 59		for(i=0;i<max;i++)
 60		{
 61			r = _dic['a'+i];
 62		}
 63		trace2("Dictionary for read");
 64 
 65		var __dicin:* = null;
 66		_tim = getTimer();
 67		for(__dicin in _dic)
 68		{
 69			r = _dic[__dicin];
 70		}
 71		trace2('Dictionary for in read');
 72 
 73		var __diceach:* = null;
 74		_tim = getTimer();
 75		for each(__diceach in _dic)
 76		{
 77			r = __diceach;
 78		}
 79		trace2('Dictionary for each read');
 80 
 81		_arr = [];
 82		_tim = getTimer();
 83		for(i=0;i<max;i++)
 84		{
 85			_arr[i] = i;
 86		}
 87		trace2("Array write");
 88 
 89		_tim = getTimer();
 90		for(i=0;i<max;i++)
 91		{
 92			r = _arr[i];
 93		}
 94		trace2("Array for read");
 95 
 96		var __arrin:* = null;
 97		_tim = getTimer();
 98		for(__arrin in _arr)
 99		{
100			r = _arr[__arrin];
101		}
102		trace2("Array for in read");
103 
104		var __arreach:* = null;
105		_tim = getTimer();
106		for each(__arreach in _arr)
107		{
108			r = __arreach;
109		}
110		trace2("Array for each read");
111	}
112 
113	private var _tim:int = 0;
114	private var _obj:Object = {};
115	private var _dic:Dictionary;
116	private var _arr:Array = [];
117 
118	private function trace2($n:String):void
119	{
120		trace($n+':'+(getTimer() - _tim));
121	}
122}
123}

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

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

 1var __key:Object = {};
 2
 3//建立字符串缓存
 4for(i=0;i<max;i++)
 5{
 6	__key['a'+i] = 'a'+i;
 7}
 8
 9_tim = getTimer();
10for(i=0;i<max;i++)
11{
12	_obj['a'+i] = i;
13}
14trace2('Object write');

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

那么这样写会如何?

1_tim = getTimer();
2for(i=0;i<max;i++)
3{
4	_obj[__key['a'+i]] = i;
5}
6trace2('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

部分代码如下:

 1_tim = getTimer();
 2for(i=0;i<max;i++)
 3{
 4	_obj[i] = i;
 5}
 6trace2('Object write');
 7
 8_tim = getTimer();
 9for(i=0;i<max;i++)
10{
11	r = _obj[i];
12}
13trace2('Object for read');

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