Singleton速度比較 (3) ソースコード

(1) SynchronizedSingleton: 完全同期型Singleton


特に説明は不要でしょう。non-PODでstaticなオブジェクトを使用するのは好きじゃありませんが(メンバ変数m)、今回は目を瞑ります。

template<typename T>
class SynchronizedSingleton : private boost::noncopyable
{
public:
  static T& getInstance(void) {
    boost::mutex::scoped_lock lk(m);
    if (!instance) {
      instance = new T;
    }
    return *instance;
  }
private:
  static T* instance;
  static boost::mutex m;
};

template<typename T>
T* SynchronizedSingleton<T>::instance = 0;

template<typename T>
boost::mutex SynchronizedSingleton<T>::m;

(2) DCLSingleton


先に述べた方法で、x86用のメモリバリアを挿入し、安全なdouble checked lockingを実現したバージョンです。結果として、メモリバリアとして余計なインストラクションは挿入されず、g++の最適化を抑制するだけ("memory")になっているので速そうですね。

template<typename T>
class DCLSingleton : private boost::noncopyable
{
public:
  static T& getInstance(void) {
    T* tmp = instance;
    __asm__ __volatile__ ( "" ::: "memory" ); // read memory barrier for x86
    if (!instance) {
      boost::mutex::scoped_lock lk(m);
      if (!instance) {
        tmp = new T;
        __asm__ __volatile__ ( "" ::: "memory" ); // write memory barrier for x86
        instance = tmp;
      }
    }
    return *instance;
  }
private:
  static T* instance;
  static boost::mutex m;
};

template<typename T>
T* DCLSingleton<T>::instance = 0;

template<typename T>
boost::mutex DCLSingleton<T>::m;

(3) OnceSingleton


pthread_once()で実装したシングルトンです。それ自体はちょっと使い方がインタフェースが煩雑なので、内部でpthread_once()を使っているboost::call_once()で実装しました。少々手抜きですね。

// Once
template<typename T>
class OnceSingleton : private boost::noncopyable
{
public:
  static T& getInstance(void) {
    static boost::once_flag flg = BOOST_ONCE_INIT;
    boost::call_once(init, flg);
    return *instance;
  }
private:
  static void init(void) {
    instance = new T;
  }
  static T* instance;
};

template<typename T>
T* OnceSingleton<T>::instance = 0;

(4) GccTSDSingleton


最近のGCCの__thread拡張を使ったシングルトンです。

// TSD (gcc拡張)
template<typename T>
class GccTSDSingleton : private boost::noncopyable
{
public:
  static T& getInstance(void) {
    static __thread T* tsd_instance = 0;
    if (!tsd_instance) {
      tsd_instance = getInstance_();
    }
    return *tsd_instance;
  }
private:
  static T* getInstance_(void) {
    boost::mutex::scoped_lock lk(m);
    if (!instance) {
      instance = new T;
    }
    return instance;
  }
  static T* instance;
  static boost::mutex m;
};

template<typename T>
T* GccTSDSingleton<T>::instance = 0;

template<typename T>
boost::mutex GccTSDSingleton<T>::m;

(5) TSDSingleton


POSIXで標準化されている、スレッド固有データを用いたシングルトンです。
こちらもboostで手抜きしたいところですが、Boost.ThreadにはTSD/TLSAPIは(まだ)ないので、直接pthread APIを叩いています。。

// TSD (POSIX)
template<typename T>
class TSDSingleton : private boost::noncopyable
{
public:
  static T& getInstance(void) {
    static boost::once_flag flg = BOOST_ONCE_INIT;
    boost::call_once(init, flg);

    T* tsd_instance = static_cast<T*>(pthread_getspecific(key));
    if (!tsd_instance) {
      tsd_instance = getInstance_();
      pthread_setspecific(key, tsd_instance);
    }
    return *tsd_instance;
  }
private:
  static void init(void) {
    pthread_key_create(&key, 0);
  }
  static T* getInstance_(void) {
    boost::mutex::scoped_lock lk(m);
    if (!instance) {
      instance = new T;
    }
    return instance;
  }
  static T* instance;
  static pthread_key_t key;
  static boost::mutex m;
};

template<typename T>
T* TSDSingleton<T>::instance = 0;

template<typename T>
pthread_key_t TSDSingleton<T>::key;

template<typename T>
boost::mutex TSDSingleton<T>::m;

次のコードでテストしました。

int main(void) {
  static const int TIMES = 500000000; // 5億回
  double s;
  boost::timer t;

  t.restart();
  for(int i = 0; i < TIMES; ++i) {}
  const double r = t.elapsed();

  t.restart();
  for(int i = 0; i < TIMES; ++i) {
    volatile X& x __attribute__((__unused__)) = DCLSingleton<X>::getInstance();
  }
  s = t.elapsed() - r;
  std::cout << "DCLSingleton: " << s << " [s]" << std::endl;

  (略)

続き