event

스레드 수에 따라 갈린다 — 스레드 2에서는 CONDITION_VARIABLE 이, 4 이상에서는 Kernel Object의 HANDLE Event 가 앞선다

조사 코드

some_event.cpp

#include <iostream>
#include <atomic>
#include <mutex>
#include <vector>
#include <memory>
#include <Windows.h>

template<typename Out>
void print_env(Out& out) {

#define PRINT_MACRO(VAR) out << #VAR ": " << VAR << std::endl
   PRINT_MACRO(_MSC_FULL_VER);
   PRINT_MACRO(_MSVC_LANG);
   PRINT_MACRO(_WIN64);
   PRINT_MACRO(_MT);
#ifdef _DEBUG
   out << "with _DEBUG" << std::endl;
#else
   out << "without _DEBUG" << std::endl;
#endif
   PRINT_MACRO(_DLL);
#undef PRINT_MACRO
}

void exec_systeminfo() {
   system("systeminfo | findstr /r \"^OS.버전\"");
   system("systeminfo | findstr /r \"^시스템 종류\"");
}

using namespace std;

struct my_event_with_std {
   bool state;
   condition_variable cond;
   my_event_with_std() : state(false) {}
   my_event_with_std(const my_event_with_std&) = delete;
   my_event_with_std(my_event_with_std&&) = delete;
   my_event_with_std& operator=(const my_event_with_std&) = delete;
   my_event_with_std& operator=(my_event_with_std&&) = delete;
   template <typename T>
   void set(T& l) {
       this->cond.wait(l, [=] {return !this->state; });
       this->state = true;
       this->cond.notify_all();
   }
   template <typename T>
   void reset(T& l) {
       this->cond.wait(l, [=] {return this->state; });
       this->state = false;
       this->cond.notify_all();
   }
};

struct my_event_with_api {
   HANDLE handle;
   bool state;
   my_event_with_api(): handle(::CreateEvent(NULL, FALSE, FALSE, NULL)), state(false) {}
   ~my_event_with_api() {
       if (handle != NULL) ::CloseHandle(handle);
   }
   template<typename T>
   void set(T& l) {
       if (state) {
           while (state) {
               l.unlock();
               ::WaitForSingleObject(handle, INFINITE);
               l.lock();
           }
           state = true;
           ::ResetEvent(handle);
       }
       else {
           state = true;
           ::SetEvent(handle);
       }
   }
   template<typename T>
   void reset(T& l) {
       if (state) {
           state = false;
           ::SetEvent(handle);
       }
       else {
           while (state) {
               l.unlock();
               ::WaitForSingleObject(handle, INFINITE);
               l.lock();
           }
           state = false;
           ::ResetEvent(handle);
       }
   }
};

#include <queue>
#include <chrono>
using namespace std::chrono;
template<typename T>
size_t event_link_loop_mt(atomic_bool& stop, size_t C = 2) {
   vector<unique_ptr<thread>> threads;
   vector<unique_ptr<T>> events;
   struct log_data {
       high_resolution_clock::time_point tp;
       thread::id tid;
       size_t event_idx;
       bool is_set;
   };
   queue<log_data> event_log;
   auto log = [&](size_t event_idx, bool is_set) {
       //event_log.emplace(log_data{high_resolution_clock::now(), this_thread::get_id(), event_idx, is_set});
   };
   auto start = high_resolution_clock::now();
   for (size_t i = 0; i < C; ++i) {
       events.emplace_back(make_unique<T>());
   }
   mutex m;
   unique_lock<mutex> l(m);
   for (size_t i = 0; i < C; ++i) {
       events[i]->set(l);
       log(i, true);
   }
   for (size_t i = 0; i < C - 1; ++i) {
       threads.emplace_back(std::make_unique<thread>([&, i] {
           while (! stop) {
               unique_lock<mutex> l(m);
               events[i]->set(l);
               log(i, true);
               events[i+1]->reset(l);
               log(i+1, false);
           }
       }));
   }
   size_t count = 0;
   while (! stop) {
       events[0]->reset(l);
       log(0, false);
       events[C - 1]->set(l);
       log(C - 1, true);
       if (count + 1 == 0) throw runtime_error("count overflow");
       ++count;
   }
   events[0]->reset(l);
   log(0, false);
   l.unlock();
   for (auto& pt : threads) {
       pt->join();
   }
   while (!event_log.empty()) {
       const auto& line = event_log.front();
       cerr << "[" << duration_cast<nanoseconds>(line.tp - start).count() / 1000000000. << "]: " << "tid =" << line.tid << " event_idx=" << line.event_idx << " is_set=" << line.is_set << "\n";
       event_log.pop();
   }
   cerr.flush();
   return count;
}

template<typename T>
void test(const char* prefix) {
   SYSTEM_INFO info;
   ::GetSystemInfo(&info);
   for (int i = 1; i < static_cast<int>(info.dwNumberOfProcessors); ++i) {
       T m;
       atomic_bool stop = false;
       size_t result = 0;
       thread t([&] {
           result = event_link_loop_mt<T>(stop, i + 1);
       });
       this_thread::sleep_for(3s);
       stop = true;
       t.join();
       cout << prefix << i + 1 << "," << result << endl;
   }
}

int main()
{
   print_env(std::cout);
   exec_systeminfo();
   cout << "method,threads,count" << endl;
   test<my_event_with_std>("my_event_with_std,");
   test<my_event_with_api>("my_event_with_api,");
   return 0;
}

측정 내용

복수 thread를 사용하여, 수주 연결한 Event로 대기로부터 시그널을 3초간 반복하여, 합계 시그널 회수(처음의 발신만 카운트)를 계측한다, 라고 하는 것이다. 3초 sleep 하고 멈추는 로직이므로 오차는 나름대로라고 생각합니다. 동기화 객체는 다음 두 가지입니다.

실행 결과

VC++2019

some_event_2019.log

_MSC_FULL_VER: 192930154

_MSVC_LANG: 201402

_WIN64: 1