SIMD命令の高速化と処理メモリサイズの関係

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
たろ

SIMD命令の高速化と処理メモリサイズの関係

#1

投稿記事 by たろ » 9年前

先日はSIMDプログラミングの件でコメント頂きありがとうございました。
その続きの話になりますが、ご意見頂けると嬉しいです。よろしくお願いします。

環境: Core Duo T2300 (1.66GHz), メモリ2GB, WinXP Home SP3 (32bit), VC2008EE, C言語, Win32API

SIMD命令を使うと実際にどれくらい速くなるのか?試したところ、期待したほど速くありませんでした。
いろいろ試したところ、どうも処理するメモリサイズが小さいうちは効果が大きく、
メモリサイズが大きいと効果が薄れる?ように見えます。

これはどういうことか?また処理メモリサイズが大きくても効果を出すテクニックはあるのか?
というあたりで、詳しい方のご意見を聞きたくて質問させて頂きました。

具体的には、32bitビットマップ画像をメモリにロードして加工処理することを想定したテストプログラムを作りました。
「連続したメモリ領域を、単純に先頭から順次更新する処理を、何回も繰り返す」コマンドラインプログラムです。
処理するメモリサイズをだんだん大きくしていき、SIMD命令を使った場合と使わない場合の速度を比べます。
以下プログラムです。

コード:

#pragma comment(lib, "winmm.lib")

#include <windows.h>
#include <mmsystem.h>
#include <emmintrin.h>
#include <stdio.h>

// メモリサイズ。ビットマップ画像サイズに相当。
#define ALLOC_BYTE_BEGIN	(512 * 512 * 4)			// 開始
#define ALLOC_BYTE_END		(1024 * 1024 * 4)		// 終了
#define ALLOC_BYTE_UNIT		(1024 * 64)				// 増加単位バイト
// ループ回数。結果ハッキリさせるため適当に調節。
#define LOOP_COUNT_BEGIN	3000					// 開始
#define LOOP_COUNT_UNIT		100						// 減らす単位
#define LOOP_COUNT_MIN		500						// 最小

void SIMD( __m128i* p, UINT bytes )
{
	__m128i* end = (__m128i*)((LPBYTE)p + bytes);
	__m128i i255 = _mm_set1_epi8( 255 );

	for( ; p<end; p++ )		// 先頭から128bit(16byte)ずつ
	{
		*p = _mm_xor_si128( *p, i255 );
	}
}

void Normal( LPBYTE p, UINT bytes )
{
	LPBYTE end = p + bytes;

	for( ; p<end; p+=16 )	// 先頭から4ピクセル(16byte)ずつ
	{
		p[0] = ~p[0];	// B
		p[1] = ~p[1];	// G
		p[2] = ~p[2];	// R
		p[3] = ~p[3];	// A

		p[4] = ~p[4];	// B
		p[5] = ~p[5];	// G
		p[6] = ~p[6];	// R
		p[7] = ~p[7];	// A

		p[8] = ~p[8];	// B
		p[9] = ~p[9];	// G
		p[10] = ~p[10];	// R
		p[11] = ~p[11];	// A

		p[12] = ~p[12];	// B
		p[13] = ~p[13];	// G
		p[14] = ~p[14];	// R
		p[15] = ~p[15];	// A
	}
}

int main( void )
{
	UINT bytes, loop=LOOP_COUNT_BEGIN;

	for( bytes=ALLOC_BYTE_BEGIN; bytes<=ALLOC_BYTE_END; bytes+=ALLOC_BYTE_UNIT )
	{
		DWORD	t1, t2;
		LPVOID	p;
		UINT	n;

		p = VirtualAlloc( NULL, bytes, MEM_COMMIT, PAGE_READWRITE );
		if( !p ) return -1;

		VirtualLock( p, bytes );
		memset( p, 0xaa, bytes );

		t1 = timeGetTime();
		for( n=loop; n>0; n-- ) Normal( p, bytes );
		t1 = timeGetTime() - t1;

		t2 = timeGetTime();
		for( n=loop; n>0; n-- ) SIMD( p, bytes );
		t2 = timeGetTime() - t2;

		VirtualUnlock( p, bytes );
		VirtualFree( p, 0, MEM_RELEASE );

		printf("%u KB (%u回), Normal=%u.%usec, SIMD=%u.%usec, %0.1f倍!!\n",
				bytes/1024, loop, t1/1000, t1%1000, t2/1000, t2%1000, (float)t1/t2);

		loop -= LOOP_COUNT_UNIT;
		if( loop<LOOP_COUNT_MIN ) loop=LOOP_COUNT_MIN;
	}
	return 0;
}
これを実行した結果、はじめ処理メモリサイズが小さいうちは、SIMD命令の方が5倍近く高速に動作していますが、
2MBを超えたあたりから効果が薄れはじめ、最後4MBでは1.5倍くらいしか速くなっていません。

1024 KB (3000回), Normal=2.688sec, SIMD=0.562sec, 4.8倍!!
1088 KB (2900回), Normal=2.703sec, SIMD=0.563sec, 4.8倍!!
   :
2048 KB (1400回), Normal=2.702sec, SIMD=0.703sec, 3.8倍!!
2112 KB (1300回), Normal=2.422sec, SIMD=0.671sec, 3.6倍!!
2176 KB (1200回), Normal=2.282sec, SIMD=0.781sec, 2.9倍!!
   :
4032 KB (500回), Normal=1.812sec, SIMD=1.219sec, 1.5倍!!
4096 KB (500回), Normal=1.828sec, SIMD=1.250sec, 1.5倍!!

このように、処理メモリサイズが大きくなるとSIMD命令の効果が薄くなる理由として、
自分としては以下のように考えていますが、正しいでしょうか?

・処理メモリサイズが小さいうちは、CPUのキャッシュ(1次キャッシュ、2次キャッシュ)に乗るため速い。
・CPUキャッシュに乗らないサイズになると、メインメモリのアクセスが必要になり遅くなる。

また、上のプログラムのような、単純な1ループ、単純なシーケンシャルアクセス、単純なSIMD命令1つでは、
これ以上速くするのは難しいのかな・・と思ってしまいますが、テクニックがあったりするのでしょうか・・?

SIMD命令に「プリフェッチ命令」というのがあり、効果的な使い方がよくわからず適当に入れてみたことは
あるのですが、逆に遅くなってしまったような気がします。。

アセンブラコードを書かないのに生意気なことを言っているかもしれません・・ご容赦ください・・。

なお、マルチスレッドはとりあえず考えていません。マルチスレッド化したらどうなるか?も興味はありますが・・。
ひとまず「SIMD命令でどこまで速くなるのかな?」という点に絞っています。

よろしくお願いします。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 10年前
住所: 東海地方
連絡を取る:

Re: SIMD命令の高速化と処理メモリサイズの関係

#2

投稿記事 by softya(ソフト屋) » 9年前

メモリアクセスは、SIMDを使おうが早くなるわけではないのでSIMDで複雑なことをさせないと差は出ないと思いますよ。
キャッシュが聞いているときは早くなっているのでCPUの処理自体は早いと言えると思います。
あとは、出来るだけキャッシュが効く形でメモリを操作するように組む工夫が求められるわけです。

[追記]
私も最近のIA32タイプのCPUの動作に詳しいわけではないので、速度的な感触を持っておりませんので勘違いしているかも知れません。

パフォーマンスを気にするなら下記サイトの「インテル® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアル [日本語: PDF 形式 8,974 KB]」を読まれては?
「日本語技術資料のダウンロード」
http://www.intel.com/jp/download/index.htm
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ISLe
記事: 2648
登録日時: 10年前
連絡を取る:

Re: SIMD命令の高速化と処理メモリサイズの関係

#3

投稿記事 by ISLe » 9年前

ウチではこんな感じになりました。

環境: Core 2 Duo E6850 (3.00GHz), メモリ6GB, Win7 Ultimate SP1 (64bit), VC2010Express
>cl /O2 /Oi /Oy- /GL test.c
>test
1024 KB (3000回), Normal=1.239sec, SIMD=0.173sec, 7.2倍!!
1088 KB (2900回), Normal=1.270sec, SIMD=0.181sec, 7.0倍!!
   :
2048 KB (1400回), Normal=1.165sec, SIMD=0.170sec, 6.9倍!!
2112 KB (1300回), Normal=1.118sec, SIMD=0.161sec, 6.9倍!!
2176 KB (1200回), Normal=1.65sec, SIMD=0.158sec, 6.7倍!!
   :
4032 KB (500回), Normal=0.844sec, SIMD=0.145sec, 5.8倍!!
4096 KB (500回), Normal=0.861sec, SIMD=0.157sec, 5.5倍!!

下がってはいますが幅は大きくないですね。

関係ないかもしれませんが、CPUが省電力モードに入ってるとか。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 10年前
住所: 東海地方
連絡を取る:

Re: SIMD命令の高速化と処理メモリサイズの関係

#4

投稿記事 by softya(ソフト屋) » 9年前

こちらも実行してみました。ちなみに環境は、
AMD Athlon II X4 630(2.8Ghz) Windows7 Pro(64bit) 4GB
Visual Studio 2008 リリースビルド Win32
です。
1024 KB (3000回), Normal=2.590sec, SIMD=0.821sec, 3.2倍!!
1088 KB (2900回), Normal=2.651sec, SIMD=0.811sec, 3.3倍!!
   :
2048 KB (1400回), Normal=3.11sec, SIMD=0.771sec, 3.9倍!!
2112 KB (1300回), Normal=2.320sec, SIMD=0.726sec, 3.2倍!!
2176 KB (1200回), Normal=2.588sec, SIMD=0.696sec, 3.7倍!!
   :
4032 KB (500回), Normal=1.695sec, SIMD=0.504sec, 3.4倍!!
4096 KB (500回), Normal=1.727sec, SIMD=0.558sec, 3.1倍!!
私も差が少ないですね。
AMDは、Core 2 Duo E6850 (3.00GHz),に大負け OTL

[考察]
CPUキャッシュの量がCPUの種類ごとに大分違うので、そえrが原因かも知れません。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 10年前
住所: 東海地方
連絡を取る:

Re: SIMD命令の高速化と処理メモリサイズの関係

#5

投稿記事 by softya(ソフト屋) » 9年前

キャッシュを試しにOFFにしてみました。
VirtualAlloc( NULL, bytes, MEM_COMMIT, PAGE_READWRITE|PAGE_NOCACHE );
激遅!なので回数を1/10に減らしてあります。
1024 KB (300回), Normal=47.766sec, SIMD=4.844sec, 9.9倍!!
1088 KB (290回), Normal=48.969sec, SIMD=4.969sec, 9.9倍!!
   :
2048 KB (140回), Normal=45.609sec, SIMD=4.704sec, 9.7倍!!
2112 KB (130回), Normal=43.922sec, SIMD=4.515sec, 9.7倍!!
2176 KB (120回), Normal=41.796sec, SIMD=4.235sec, 9.9倍!!
   :
4032 KB (50回), Normal=31.688sec, SIMD=3.187sec, 9.9倍!!
4096 KB (50回), Normal=32.359sec, SIMD=3.250sec, 10.0倍!!
でも、SIMD命令のメモリアクセスが効率的なことが判明。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

nissy
記事: 11
登録日時: 10年前

Re: SIMD命令の高速化と処理メモリサイズの関係

#6

投稿記事 by nissy » 9年前

自分自身が詳しいわけではないですが、以下のページのメモリのプリフェッチという項目はどうでしょうか。
http://www1.icnet.ne.jp/nsystem/simd_to ... retch.html

たろ

Re: SIMD命令の高速化と処理メモリサイズの関係

#7

投稿記事 by たろ » 9年前

コメントたくさんありがとうございます!
遅くなりすみません。1つずつコメントいたします。

softya(ソフト屋)さん

>メモリアクセスは、SIMDを使おうが早くなるわけではないのでSIMDで複雑なことをさせないと差は出ないと思いますよ。
>キャッシュが聞いているときは早くなっているのでCPUの処理自体は早いと言えると思います。
>あとは、出来るだけキャッシュが効く形でメモリを操作するように組む工夫が求められるわけです。

はい。今回試してみて、メモリアクセスの最適化というのを、SIMD命令とは別に考える必要があるんだなと感じました。
「出来るだけキャッシュが効く形でメモリ操作をする工夫」ができるようになりたいですが、自分はまだ実際にコーディング
にまで落とし込めるレベルではないとも思います・・。

実際どうするんだろう・・例えば、「メモリを飛び飛びであちこちアクセスしてる」箇所があったら、それをある程度まとめて
連続領域でアクセスする実装に変える、とかでしょうか?そうすると、今回の場合は、もうすでに連続領域のアクセスに
なっているので、これ以上の工夫が思いつきません・・。

>パフォーマンスを気にするなら下記サイトの「インテル® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアル [日本語: PDF 形式 8,974 KB]」を読まれては?

ありがとうございます。読んでみます!
・・と気楽に読めるような代物ではありませんでした・・学術論文をみている気分です・・(^_^;)
この資料の内容についていけるようになったら自分を褒めてあげたいレベル・・。
ひとまず最終的にはこんな話になるのはわかりました。

ありがとうございました。

たろ

Re: SIMD命令の高速化と処理メモリサイズの関係

#8

投稿記事 by たろ » 9年前

ISLeさん、softya(ソフト屋)さん、試して頂きありがとうございます!
環境によってぜんぜん違うんですね・・驚きました。

ISLeさんのCore 2 Duo E6850は、L2キャッシュが4MBでしょうか。つまり4MBのメモリ操作なら
ほぼぜんぶキャッシュに乗ってしまうということでしょうか。だから下がり幅が少ないのかな。

softyaさんは、ほんとうに差が少なく安定してますね。Athlon II X4 630はL2キャッシュが2MBでしょうか。
ということは2MB以上はキャッシュに乗らないはず・・なのになぜ遅くならない・・?不思議です。

CPUの省電力モードの話、たしかに「電源オプション」の「VAIO省電力設定」でCPUが「自動調節(1.6GHzと1.0GHzを行ったり来たり)」
になっていたので、「動作優先(常に1.6GHz)」に変えて試してみました。結果は特に変わりませんでした。

私のCPUは、L2キャッシュが2MBなので、メモリサイズが2MBを超えたあたりからどんどん落ちていくということかな。
そうするとsoftyaさんのAthlon環境が安定しているのがやはり不思議です・・うらやましい。

ベンチマークみたいでおもしろかったです(^^)ありがとうございました。

たろ

Re: SIMD命令の高速化と処理メモリサイズの関係

#9

投稿記事 by たろ » 9年前

softya(ソフト屋)さん

PAGE_NOCACHE 知りませんでした。私もこれを入れて、回数1/10で試してみました。
やばい、こんなに違うんですね。CPUの温度がずっと90°C前後でファンが唸りを上げてました(笑)

1024 KB (300回), Normal=72.238sec, SIMD=7.32sec, 10.3倍!!
   :
2048 KB (140回), Normal=67.510sec, SIMD=6.631sec, 10.2倍!!
   :
4096 KB (50回), Normal=47.609sec, SIMD=4.638sec, 10.3倍!!

CPUキャッシュの偉大さを実感しました。。
そして最後までずっと10倍くらいで変わらない。なるほど・・。

あと、Normal()関数のコードが1バイトずつメモリアクセスしているのが遅いことに気づきました。
以下のように変えてみたら、

コード:

void Normal( UINT32* p, UINT bytes )
{
	UINT32* end = (UINT32*)((LPBYTE)p + bytes);

	for( ; p<end; p+=4 )	// 先頭から4ピクセル(16byte)ずつ
	{
		p[0] = ~p[0];
		p[1] = ~p[1];
		p[2] = ~p[2];
		p[3] = ~p[3];
	}
}
劇的に速くなって驚きました。(PAGE_NOCACHEは入れていません)

1024 KB (3000回), Normal=0.811sec, SIMD=0.550sec, 1.5倍!!
1088 KB (2900回), Normal=0.782sec, SIMD=0.563sec, 1.4倍!!
   :
2048 KB (1400回), Normal=0.784sec, SIMD=0.566sec, 1.4倍!!
2112 KB (1300回), Normal=0.892sec, SIMD=0.714sec, 1.2倍!!
2176 KB (1200回), Normal=0.991sec, SIMD=0.796sec, 1.2倍!!
   :
4032 KB (500回), Normal=1.271sec, SIMD=1.240sec, 1.0倍!!
4096 KB (500回), Normal=1.294sec, SIMD=1.270sec, 1.0倍!!

SIMD命令との差もわずかになってしまい、やはりもっと複雑なことをさせないと差が出ないということに。
結局、このテストプログラムはSIMDよりメモリアクセスとCPUキャッシュの性能を見るものになっているということでしょうか。
もっと勉強して出直してきたいと思います。。。

たろ

Re: SIMD命令の高速化と処理メモリサイズの関係

#10

投稿記事 by たろ » 9年前

nissyさん、ありがとうございます。

教えて頂いたサイト、とてもよさそうです!画像処理の具体例がうれしいです。

と言っても、すぐ理解できるほどアセンブラの読み書きができませんでした・・。
まずはこれくらいできるようにならないと、Intelの資料なんてとても理解できないですね。。

今回作ってみたプログラムはまだほんの入口を覗いただけだったようなので、
もっと精進して出直してきたいと思います。

ありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る