2014年01月23日
GCCのトランザクションメモリサポート
・トランザクションメモリとは?
トランザクションメモリは、マルチスレッドプログラムにおいて、複数のスレッドからアクセスされる共有変数(メモリ)を保護するためのしくみのようです。
共有変数の保護には、従来は mutex などのロックが使用されてきました。しかし、ロックを使用したプログラム片(関数など)は、ロックの取得と解放の順番が重要なので、基本的には入れ子にできないという問題があります。常にプログラム全体に散らばるロックを意識する必要があるので、プログラムの部品化や再利用が困難になりますし、デッドロックのデバッグや品質保証は非常に高コストになりがちです。
一方トランザクションメモリは、原理的にデッドロックしない(らしい)ので、自由にプログラム片を組み合わせることが可能になるそうです。使い方によってはパフォーマンスの問題はありそうですが、安全に低コストでマルチスレッドプログラムが構築できるというのは良さそうな感じがします。
・実行環境とサンプルプログラム
GCC 4.7 以降で、対応プロセッサは x86、ARM、SH など、言語は C/C++ など、POSIX 環境でトランザクションメモリはサポートされているようです。
MinGW GCC は非 POSIX 環境なので、未サポートのようです。(未確認)
今回は手元の Ubuntu 12.04 64bit (VMWare 4 コア環境)で試してみました。
ロック無しの場合は、以下のように、何回やってもまず合計が正しくなりません。
・参考サイト
「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
トランザクションメモリは、マルチスレッドプログラムにおいて、複数のスレッドからアクセスされる共有変数(メモリ)を保護するためのしくみのようです。
共有変数の保護には、従来は 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