FlashPlayer10でByteArrayや配列、Vectorなどの速度比較

FlashPlayer10でダイナミックな音声生成できるようになったので、ちょっと遊んでみようと思ったのですが、その前に波形データなどを内部的にどう持つのがいいか迷ったのでちょっと調べてみた。

調べたいこと:

  1. 現在のサンプル(波形データ/-1.0〜1.0のNumber)を左右チャンネル分取得して
  2. データに何らかの処理を加えて
  3. 書き戻す

っていう処理をするのにどういう形でデータを持つのが一番処理速度的によさそうか。

テスト内容

  1. Sound.samplesCallbackDataがByteArrayなのでそのまま使えるようにByteArrayで読み書きする
  2. 配列に[左,右,左...]という順番でデータを入れて処理する
  3. 左右チャンネル別に配列を作って処理
  4. 配列に「Sample」という左右チャンネルのサンプルデータを持ったクラスのインスタンスを並べて処理
  5. Vectorに同じように「Sample」クラスのインスタンスを入れて処理
  6. 左右ちゃんねる分の二つのVector.作って処理

処理内容は「左右チャンネルの値を取得して、それぞれにMath.random()をかけて書き戻す」で一応統一。

package  
{
	import flash.display.Sprite;
	import flash.utils.ByteArray;
	import org.sazameki.audio.core.Sample;
	import flash.utils.getTimer;

	public class ByteArrayTest extends Sprite
	{
		
		public function ByteArrayTest() 
		{
			var ba:ByteArray = new ByteArray();
			var ar:Array = new Array();
			var arl:Array = new Array();
			var arr:Array = new Array();
			var ar2:Array = new Array();
			var vec:Vector.<Sample>=new Vector.<Sample>();
			var vecL:Vector.<Number>=new Vector.<Number>();
			var vecR:Vector.<Number>=new Vector.<Number>();
			var i:int;
			for (i = 0; i < 44100; i++)
			{
				ba.writeFloat(Math.random());
				ba.writeFloat(Math.random());
				ar.push(Math.random());
				ar.push(Math.random());
				arl.push(Math.random());
				arr.push(Math.random());
				vec.push(new Sample(Math.random(), Math.random()));
				ar2.push(new Sample(Math.random(), Math.random()));
				vecL.push(Math.random());
				vecR.push(Math.random());
			}
			
			var time:int;
			var left:Number;
			var right:Number;
			var smpl:Sample;
			var k:int;
			trace("ByteArrayStart:" +(time = getTimer()) );
			for (k = 0; k < 100; k++)
			{
				ba.position = 0;
				for (i = 0; i < 44100; i++)
				{
					left = ba.readFloat();
					right = ba.readFloat();
					ba.position = ba.position - 8;
					ba.writeFloat(left*Math.random());
					ba.writeFloat(right*Math.random());
				}
			}

			trace("cost:" + (getTimer() - time));
			trace("ArrayStart:" +(time = getTimer()) );
			for (k = 0; k < 100; k++)
			{
				for (i = 0; i < 88200; i+=2)
				{
					ar[i] = ar[i] * Math.random();
					ar[i+1] = ar[i+1] * Math.random();
				}
			}			
			trace("cost:" + (getTimer() - time));
			trace("Array2Start:" +(time = getTimer()) );
			for (k = 0; k < 100; k++)
			{
				for (i = 0; i < 44100; i++)
				{
					arl[i] = arl[i] * Math.random();
					arr[i] = arr[i] * Math.random();
				}
			}			
			trace("cost:" + (getTimer() - time));

			
			trace("SampleStart:" +(time = getTimer()) );
			for (k = 0; k < 100; k++)
			{
				for (i = 0; i < 44100; i++)
				{
					smpl = ar2[i];
					smpl.left = smpl.left * Math.random();
					smpl.right = smpl.right * Math.random();
				}
			}			
			
			trace("cost:" + (getTimer() - time));
			
			trace("VectorStart:" +(time = getTimer()) );
			for (k = 0; k < 100; k++)
			{
				for (i = 0; i < 44100; i++)
				{
					smpl = vec[i];
					smpl.left = smpl.left * Math.random();
					smpl.right = smpl.right * Math.random();
				}
			}			
			
			trace("cost:" + (getTimer() - time));
			
			trace("Vector2Start:" +(time = getTimer()) );
			for (k = 0; k < 100; k++)
			{
				for (i = 0; i < 44100; i++)
				{
					vecL[i] = vecL[i] * Math.random();
					vecR[i] = vecR[i] * Math.random();
				}
			}			
			
			trace("cost:" + (getTimer() - time));
		}
		
		
	}
	
}
package org.sazameki.audio.core {
	
	public class Sample {
		
		public var left:Number;
		public var right:Number;
		
		public function Sample(left:Number=0.0,right:Number=0.0) {
			this.left=left;
			this.right=right;
		}
		
		public function toString():String{
			return('[l:'+left+',r:'+right+']');
		}
		
	}
	
}

結果

ByteArrayStart:1218
cost:6905
ArrayStart:8124
cost:4999
Array2Start:13124
cost:4517
SampleStart:17641
cost:2766
VectorStart:20408
cost:2861
Vector2Start:23270
cost:2503

プログラムがわりと適当なのでどの程度信憑性があるかは謎ですが、色々試してみた感じだと結局popforgeでも使われてた「Sample」クラスを配列に入れる方式がソースの見やすさ含めて優秀。FP10に最適化して徹底的に速度重視するならやはりVectorを活用する方向、かなぁ…もうちょっと調べてみよう。