Singleton速度比較 (1)

2chのマルチスレッドスレッドで興味深い議論があった。見ていただければわかるが、「C++でdouble checked locking(DCL)は安全か」という話題を、CPU毎に検討している。各CPUのmemory modelの話に立ち入った、楽しい議論だ。特に、リンクされている

Double-Checked Locking, Threads, Compiler Optimizations, and More
http://www.nwcpp.org/Downloads/2004/DCLP_notes.pdf

なるScott Mayersさんのペーパーがイケてると思う。最近書かれたばっかり。ここを読んでいなかったら当分知ることはなかっただろうな。ラッキー。


というわけで本題も興味深いのだが、とりあえずスレッドの中でいくつか示された「DCLに代わる高速なシングルトンの実装方法」が実際どの程度高速か興味がわいたので計測してみた。次の5種類を試した。

SynchronizedSingleton:
インスタンス取得メソッド全体をmutexで囲み、完全同期化を行ったシングルトン。
DCLSingleton:
インスタンス取得メソッドに Double Checked Locking Pattern を用いたシングルトン。
OnceSingleton:
インスタンス取得メソッド中でのインスタンスのnew処理を、pthread_onceで一度
だけ行うようにしたシングルトン。

> もしくは、pthread_once(3)のようなシステムが用意してくれてるAPIを使うとか。
> DCLが有効なアーキテクチャではpthread_once()の内部でDCLを使ってるかもしれ
> んが、使う側からはそういう内部実装を一切気にしなくて良い。
GccTSDSingleton:
インスタンス取得メソッドはSynchronizedSingletonと同一だが、それをTSDでキャッ
シュするように細工したシングルトン。

TSDは、__thread というGCC拡張のキーワードを用いて確保する。

> 正確に言うと、某CPUが数発のマシンでDCLを(バリアなしで)使ってる人に、バリア
> 使うか858のように書き直せと言いたいんです。そのための調査をしていまして。
> 自分はどちらかというと858派ですと付け加えておきます。基本的にはパフォーマンス
> より安全・確実な方を選びたいですね。
> # 完全synchronizeのペナルティがイヤならTLSにでもポインタいれとけばぁ?と思う。
TSDSingleton:
インスタンス取得メソッドはSynchronizedSingletonと同一だが、それをTSDでキャッ
シュするように細工したシングルトン。

TSDは、POSIXAPIを用いて確保する。

なお、DCLの実装はx86向けのもの*1である。各CPU向けにどのようなメモリバリアを記述すればよいのかは、glibc(LinuxThreads)のpthread_onceの実装*2を参考にした。より正確には、各アーキテクチャ向けに linuxthreads/sysdeps/アーキテクチャ/pt-machine.h や linuxthread/internals.h で定義される、MEMORY_BARRIER(), READ_MEMORY_BARRIER(), WRITE_MEMORY_BARRIER() マクロを参考にした。参考までに、このマクロのx86の場合の#define内容は次の通りである。

[internals.h]

/* If MEMORY_BARRIER isn't defined in pt-machine.h, assume the
   architecture doesn't need a memory barrier instruction (e.g. Intel
   x86).  Still we need the compiler to respect the barrier and emit
   all outstanding operations which modify memory.  Some architectures
   distinguish between full, read and write barriers.  */

#ifndef MEMORY_BARRIER
#define MEMORY_BARRIER() asm ("" : : : "memory")
#endif
#ifndef READ_MEMORY_BARRIER
#define READ_MEMORY_BARRIER() MEMORY_BARRIER()
#endif
#ifndef WRITE_MEMORY_BARRIER
#define WRITE_MEMORY_BARRIER() MEMORY_BARRIER()
#endif


また、TSDSingletoがOnceSingletonよりも低速になるのは下のほうに貼ったソースコードを見れば自明だが、一応実験しておいた。


測定環境は、Linux 2.6.8(FC3test2), glibc-2.3.3(NPTL), g++-3.4.2, boost-1.13.0, Pentium4-2.8GHz(HT) である。


→ 続き

→ 関連記事: C++でsynchronized methodを書くのは難しい (1) /  g++ の -fthreadsafe-statics ってオプション知ってます?

*1:コンパイラによるreorderだけを防止している

*2:glibc-2.3.3-200405070341/linuxthreads/mutex.c