2014年01月23日

GCCのトランザクションメモリサポート

GCC のリリースノートを眺めていたら、4.7 からトランザクションメモリのサポートが入っていたことに気付いたので、少し調べてみました。



・トランザクションメモリとは?

トランザクションメモリは、マルチスレッドプログラムにおいて、複数のスレッドからアクセスされる共有変数(メモリ)を保護するためのしくみのようです。
共有変数の保護には、従来は mutex などのロックが使用されてきました。しかし、ロックを使用したプログラム片(関数など)は、ロックの取得と解放の順番が重要なので、基本的には入れ子にできないという問題があります。常にプログラム全体に散らばるロックを意識する必要があるので、プログラムの部品化や再利用が困難になりますし、デッドロックのデバッグや品質保証は非常に高コストになりがちです。
一方トランザクションメモリは、原理的にデッドロックしない(らしい)ので、自由にプログラム片を組み合わせることが可能になるそうです。使い方によってはパフォーマンスの問題はありそうですが、安全に低コストでマルチスレッドプログラムが構築できるというのは良さそうな感じがします。

・実行環境とサンプルプログラム

GCC 4.7 以降で、対応プロセッサは x86、ARM、SH など、言語は C/C++ など、POSIX 環境でトランザクションメモリはサポートされているようです。
MinGW GCC は非 POSIX 環境なので、未サポートのようです。(未確認)
今回は手元の Ubuntu 12.04 64bit (VMWare 4 コア環境)で試してみました。
$ gcc-4.7 --version
gcc-4.7 (Ubuntu/Linaro 4.7.3-2ubuntu1~12.04) 4.7.3
マルチスレッドでカウントアップを行う、以下のようなサンプルを用意しました。
#include <pthread.h>
#include <stdio.h>

static int shared_sum = 0;
static pthread_mutex_t mutex;

#define MAX_THREAD 10
#define MAX_COUNT 10000

void* thread(void* pParam)
{
    int i;
    for (i = 0; i < MAX_COUNT; i++) {
#ifdef GUARD_MUTEX
        pthread_mutex_lock(&mutex);
#endif
#ifdef GUARD_TM
	__transaction_atomic
#endif
	{
	    shared_sum++;
	}
#ifdef GUARD_MUTEX
        pthread_mutex_unlock(&mutex);
#endif
    }
    return NULL;
}

int main(int argc, char *argv[])
{
    pthread_t tid[MAX_THREAD];
    int i;
    pthread_mutex_init(&mutex, NULL);
    for (i = 0; i < MAX_THREAD; i++) {
	pthread_create(&tid[i], NULL, thread, NULL);
    }
    for (i = 0; i < MAX_THREAD; i++) {
	pthread_join(tid[i], NULL);
    }
    pthread_mutex_destroy(&mutex);
    if (shared_sum == MAX_THREAD * MAX_COUNT) {
	fprintf(stderr, "OK(shared_sum=%d)\n", shared_sum);
    } else {
	fprintf(stderr, "NG(shared_sum=%d)\n", shared_sum);
    }

    return 0;
}
・実行結果

ロック無しの場合は、以下のように、何回やってもまず合計が正しくなりません。
$ gcc-4.7 -Wall tmtest.c -lpthread
$ ./a.out 
NG(shared_sum=80874)
$ ./a.out 
NG(shared_sum=76742)
$ ./a.out 
NG(shared_sum=85241)
mutex でロックすれば正しくなります。
$ gcc-4.7 -Wall -DGUARD_MUTEX tmtest.c -lpthread
$ ./a.out 
OK(shared_sum=100000)
トランザクションメモリで保護した場合も、期待通り正しい結果になりました。
$ gcc-4.7 -Wall -fgnu-tm -DGUARD_TM tmtest.c -lpthread
$ ./a.out 
OK(shared_sum=100000)
-S でアセンブリコードを出力させてみると、何やら _ITM_XXX という関数呼び出しが生成されています。
	call	_ITM_beginTransaction
	movl	$shared_sum, %edi
	call	_ITM_RU4
	addl	$1, %eax
	movl	%eax, %esi
	movl	$shared_sum, %edi
	call	_ITM_WU4
	call	_ITM_commitTransaction
なぜこれで上手く行くのか、上手く行かないパターンはあるのかなどイマイチ腑に落ちないので、もう少し詳細を調べて色々試してみる必要がありますが、とりあえず動作は確認できました。

・参考サイト

「Transactional Memory in GCC」
http://gcc.gnu.org/wiki/TransactionalMemory
「C++トランザクショナルメモリ拡張まとめ(ドラフト仕様v1.1)」
http://d.hatena.ne.jp/yohhoy/20120414
「GCC 4.7.0のTransactionalMemoryサポート」
http://d.hatena.ne.jp/yohhoy/20120603
「C++ Transactional Memory言語拡張の紹介」
http://www.slideshare.net/yohhoy/boostjp10-tm-20120728


トラックバックURL

コメントする

名前
 
  絵文字
 
 
記事検索
最新コメント
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード