読者です 読者をやめる 読者になる 読者になる

プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

画像のデータを格納する型は何が良い?

java

私は画像処理屋なのですが、ハードウェアに頼らず、Java側で画像処理をするとき漠然と「8bitならint型に詰め込む、16bitならlong型に詰め込む」とやってきていました。

それってどれほど正しいのか?

というわけで、ふいに思い立ったので計ってみた。


後述するソースで計ってみたところ結果は
int配列 32ミリ秒(16bitカラーなら単に倍の64ミリ秒でしょうね)
byte配列43ミリ秒
long配列43ミリ秒(8bitカラー)
long配列42ミリ秒(16bitカラー)


というわけで、私の予想通りの結果となりました。



ある程度理由を述べるなら、Javaの配列アクセス速度の問題です。
JavaはCの様に単にポインタの指す位置を読み取っているのではなく、範囲チェックをしてから読み込みをしています。故にめっちゃおそい。


だから、なるべく配列の読み込み書き込みは減らした方が良いのです。
byte型の配列にして4回読み込むよりも、やや面倒でもint型の配列から値を1回だけ読み込みレジストリに書き込んで、後はビット演算で処理した方が速いんですね。

ちなみに、32bitCPUでlong型の演算は遅いのでいったんint型に格納していますが、64bitのMacで計測しても何故かint型にいったん格納した方が速かった。




StopWatchは自作の時間計測クラス

	static int[] intArray = new int[10000];
	static byte[] byteArray = new byte[40000];
	static long[] longArray = new long[5000];
	static long[] longArrayfor16bitCollor= new long[10000];
	static StopWatch stopWatch = StopWatch.createUnsafeMiliStopWatch();
	static int A,R,G,B;

	public static void intArray(){
		int c;
		stopWatch.start();
		for(int t=0;t<1000;t++)
		for(int k=0;k<10000;k++){
			c=intArray[k];
			A += c >>>24;
			R += c >>18 & 0xff;
			G += c >> 8 &0xff;
			B += c & 0xff;
		}
		stopWatch.lapAndStop();
	}

	public static void byteArray(){
		stopWatch.start();
		for(int t=0;t<1000;t++)
		for(int k=0;k<10000;k++){
			A += byteArray[k*4]&0xff;
			R += byteArray[k*4+1]&0xff;
			G += byteArray[k*4+2]&0xff;
			B += byteArray[k*4+3]&0xff;
		}
		stopWatch.lapAndStop();
	}

	public static void longArrayfor8BitCollor(){
		int c;
		stopWatch.start();
		for(int t=0;t<1000;t++)
		for(int k=0;k<10000;k++){
			c = (int)(longArray[k>>1]>>>36*(k&1));
			A += c >>>24;
			R += c >>18 & 0xff;
			G += c >> 8 &0xff;
			B += c & 0xff;
		}
		stopWatch.lapAndStop();
	}
	public static void longArrayfor16bitCollor(){
		long c;
		int cu,cd;
		stopWatch.start();
		for(int t=0;t<1000;t++)
		for(int k=0;k<10000;k++){
			c = longArrayfor16bitCollor[k];
			cu = (int) (c>>32);
			cd = (int)c;
			A += cu >>>16;
			R += cu & 0xffff;
			G += cd >>> 16;
			B += cd & 0xffff;
		}
		stopWatch.lapAndStop();
	}

	public static void busyWait(int time){
		long t = System.currentTimeMillis();
		long l = time*1000L;
		while(System.currentTimeMillis()-t<l){}
	}


	public static void main(String[] args) {


		//ネイティブにいったんコンパイル。
		intArray();
		byteArray();
		longArrayfor8BitCollor();
		longArrayfor16bitCollor();
		intArray();
		byteArray();
		longArrayfor8BitCollor();
		longArrayfor16bitCollor();


		stopWatch.reset();
		busyWait(5);//コンパイルやJVMの安定を待つためビジー待機する。

		//測定開始
		intArray();
		byteArray();
		longArrayfor8BitCollor();
		longArrayfor16bitCollor();
		long[] ll=stopWatch.getLapTime();
		for(long lo:ll)System.out.printf("%d milisec\n",lo);
	}