SetEventは早いのか遅いのか

コメント欄でイベントを使ったほうが安定して早いのではないかという指摘を頂戴しております。

NW
WaitForSingleObjectからの復帰のほうが、安定して早いほうに100カノッサ

私はSleep(0)からの復帰のほうが絶対早いと信じているのでここは私も100カノッサを賭けて勝負です!!

#include "stdafx.h"
#include <windows.h>

#include "timer.h"
using namespace hiyoko_shogi;

#define EVENT_NAME TEXT("Event Object Test")

volatile bool quit = false;
DWORD WINAPI DoThread(DWORD ThreadCount)
{
  HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME);

  int i = 0;
  while(true)
  {
    /* 所有権獲得まで待機 */
    WaitForSingleObject(hEvent, INFINITE);

    ++i;

    if (quit)
      break;
  }
  printf("times = %d\n",i);
	
  CloseHandle(hEvent);
  return 0;
}

void* work = NULL;
DWORD WINAPI DoThread2(DWORD ThreadCount)
{
  int i = 0;
  while(true)
  {
    /* 所有権獲得まで待機 */
    if (work == NULL)
    {
      Sleep(0);
      continue;
    }
    ++i;
    work = NULL;

    if (quit)
      break;
  }
  printf("times = %d\n",i);
  return 0;
}


/* メイン関数 */
int main_(int argc, char **argv)
{
  HANDLE hThread;

#ifdef USE_EVENT
  HANDLE hEvent;
  hEvent = CreateEvent(NULL, FALSE, TRUE, EVENT_NAME);
	ResetEvent(hEvent);
  hThread = CreateThread(
		NULL, 0, (LPTHREAD_START_ROUTINE)DoThread,
		/*(LPVOID)i*/ 0, 0, NULL);
#else
	hThread = CreateThread(
		NULL, 0, (LPTHREAD_START_ROUTINE)DoThread2,
		/*(LPVOID)i*/ 0, 0, NULL);
#endif

  // スレッド起動待ち
  Sleep(1000);

  Timer time;

  time.reset();
  int c = 0;

#ifdef USE_EVENT
  // イベントを用いる場合
  for(;;)
  {
    SetEvent(hEvent);
    if ((c++ & 0xfff) == 0xfff
      && time.elapsed() >= 1000)
      break;
  }
  quit = true;
  SetEvent(hEvent);

  /* 後処理 */
  CloseHandle(hEvent);
  CloseHandle(hThread);
#else
  // イベントを用いない場合
  for(;;)
  {
    work = (void*)1;
    if ((c++ & 0xfff) == 0xfff
      && time.elapsed() >= 1000)
      break;
  }
  quit = true;
  work = (void*)1;
#endif

  // 結果が表示されるのを待つ。
  Sleep(5000);

  return(0);
}

(c++ & 0xfff) == 0xfff のくだりは、timerの取得に要する時間を希釈したかったので一定回数ごとにしかelapsedを呼び出さないようにするためのhackです。

■ 実験環境

Windows7 x64にて測定
・コアは2つ(Corei5)

さて、結果はと言いますと…

SetEventを用いた場合 → 100万〜210万回

そこそこいいスコアですね。1回当たり1us(1マイクロ秒)以下です。
しかしよく考えると、メインスレッド側で連続してSetEventした場合、worker側でWaitForSingleObjectするタイミングですでにシグナル状態になっていて、待機状態にならずそのまま続行している可能性はありそうです。そういう意味では本当はもう少し悪いスコアなのかも知れません。

まあ、そのへんはあとあと考えるとしまして、Sleepを用いたほうも計測してみます。

Sleepを用いた場合 → 1000万〜1200万回!!

圧倒的です。一桁違います。

ですが、上で書きましたように、メインスレッドでwork=(void*)1;し続けているのでworkerがSleep(0)せずにぐるぐる回っているだけかも知れません。そりゃそうですね。

そこで、メインスレッド側のループでSleep(0)を入れることにしました。いくらSleep(0)が速いとは言え、戻ってくるまでにサブスレッド側はSleep(0)での待機状態になっているでしょうからね。

この場合を計測しますと…600万回!!

これでもまだ圧倒的です。Sleep(0) 一回で足りない可能性もあるので、Sleep(0)を2つ書いてみることにしました。

この場合を計測しますと..400万回!!

裏を返せば、メインスレッドのみに着目すればSleep(0)は(他のプロセスがほとんど動いていないなら)秒間800万回は実行出来るとも解釈できるかも知れません。すなわち、1回の実行が100ns(ナノ秒)程度。

気になったので念のためSleep(0)を1秒間に何回できるのかを同環境にて計測してみましたところ…1300万回程度でした。

つまり、WaitForSingleObjectで待つ1/5ぐらいの時間でSleep(0)から復帰できるということですね。


以上のような結果が出ました。
100カノッサはひよこがありがたく頂戴いたしました。