Chris_M_Thomasson

read/write benchmark, reads/writes per-second...

Feb 20th, 2019
465
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.84 KB | None | 0 0
  1. /* Crude Read/Write Mutex Benchmark
  2.  
  3. This tests my algorithm ct_rwmutex vs. std::shared_mutex.
  4.  
  5. It shows how many reads and writes can be performed within
  6. a fixed amount of time.
  7.  
  8. by: Chris M. Thomasson
  9.  
  10. version 0.1
  11. __________________________________________*/
  12.  
  13.  
  14.  
  15. #include <thread>
  16. #include <atomic>
  17. #include <shared_mutex>
  18. #include <condition_variable>
  19. #include <iostream>
  20. #include <functional>
  21. #include <chrono>
  22. #include <cassert>
  23. #include <cstdlib>
  24. #include <ctime>
  25. #include <climits>
  26. #include <cstdint>
  27. #include <vector>
  28.  
  29.  
  30. // undefine to test std::shared_mutex
  31. #define CT_TEST_FAST_MUTEX 1
  32.  
  33.  
  34. #define THREADS 8 // multiplied by std::hardware_concurrency
  35. #define NODE_PRIME 1000000 // number of nodes
  36. #define CRUDE_CACHE_PAD 256 // crude cache pad
  37. #define TEST_DURATION_SEC 60 // number of seconds for the test
  38.  
  39.  
  40. // bare bones mutex/condvar based semaphore
  41. struct ct_slow_semaphore
  42. {
  43. unsigned long m_state;
  44. std::mutex m_mutex;
  45. std::condition_variable m_cond;
  46.  
  47. ct_slow_semaphore(unsigned long state) : m_state(state) {}
  48.  
  49. void inc()
  50. {
  51. m_mutex.lock();
  52. ++m_state;
  53. m_mutex.unlock();
  54. m_cond.notify_one();
  55. }
  56.  
  57. void add(unsigned long addend)
  58. {
  59. {
  60. std::unique_lock<std::mutex> lock(m_mutex);
  61. m_state += addend;
  62. }
  63.  
  64. m_cond.notify_all();
  65. }
  66.  
  67. void dec()
  68. {
  69. std::unique_lock<std::mutex> lock(m_mutex);
  70. while (m_state == 0) m_cond.wait(lock);
  71. --m_state;
  72. }
  73. };
  74.  
  75.  
  76.  
  77.  
  78. // bin-sema
  79. struct ct_auto_reset_event
  80. {
  81. bool m_state;
  82. std::mutex m_mutex;
  83. std::condition_variable m_cond;
  84.  
  85. ct_auto_reset_event() : m_state(false) {}
  86.  
  87. void signal()
  88. {
  89. std::unique_lock<std::mutex> lock(m_mutex);
  90. m_state = true;
  91. m_cond.notify_one();
  92. }
  93.  
  94. void wait()
  95. {
  96. std::unique_lock<std::mutex> lock(m_mutex);
  97. while (m_state == false) m_cond.wait(lock);
  98. m_state = false; // auto-reset
  99. }
  100. };
  101.  
  102.  
  103.  
  104.  
  105. // just a layer over an auto-reset event
  106. struct ct_fast_mutex
  107. {
  108. std::atomic<unsigned int> m_state;
  109. ct_auto_reset_event m_waitset;
  110.  
  111. ct_fast_mutex() : m_state(0) {}
  112.  
  113. void lock()
  114. {
  115. if (m_state.exchange(1, std::memory_order_acquire))
  116. {
  117. while (m_state.exchange(2, std::memory_order_acquire))
  118. {
  119. m_waitset.wait();
  120. }
  121. }
  122. }
  123.  
  124. void unlock()
  125. {
  126. if (m_state.exchange(0, std::memory_order_release) == 2)
  127. {
  128. m_waitset.signal();
  129. }
  130. }
  131. };
  132.  
  133.  
  134.  
  135. // Chris M. Thomassons Experimental Read/Write Mutex
  136. // Yeah, it is pretty damn fat wrt the state, however
  137. // it has some interesting properties...
  138. // The state can be compressed a bit...
  139. // btw, it has no loops...
  140. // Take a look at the lock_shared and unlock_shared functions
  141.  
  142. // The number of readers _must_ never exceed LONG_MAX!
  143.  
  144. #define RWMUTEX_COUNT_MAX LONG_MAX
  145.  
  146. struct ct_rwmutex
  147. {
  148. unsigned char m_crude_cache_pad_0[CRUDE_CACHE_PAD];
  149.  
  150. // shared state
  151. std::atomic<long> m_count;
  152. unsigned char m_crude_cache_pad_1[CRUDE_CACHE_PAD];
  153.  
  154. std::atomic<long> m_rdwake;
  155. unsigned char m_crude_cache_pad_2[CRUDE_CACHE_PAD];
  156.  
  157. ct_slow_semaphore m_rdwset;
  158. unsigned char m_crude_cache_pad_3[CRUDE_CACHE_PAD];
  159.  
  160. ct_slow_semaphore m_wrwset;
  161. unsigned char m_crude_cache_pad_4[CRUDE_CACHE_PAD];
  162.  
  163. ct_fast_mutex m_wrlock;
  164. unsigned char m_crude_cache_pad_5[CRUDE_CACHE_PAD];
  165.  
  166.  
  167. ct_rwmutex() :
  168. m_count(RWMUTEX_COUNT_MAX),
  169. m_rdwake(0),
  170. m_rdwset(0),
  171. m_wrwset(0) {
  172. }
  173.  
  174.  
  175. // READ, pretty slim...
  176. void lock_shared()
  177. {
  178. if (m_count.fetch_add(-1, std::memory_order_acquire) < 1)
  179. {
  180. // We need to wait for a writer.
  181. m_rdwset.dec();
  182. }
  183. }
  184.  
  185. void unlock_shared()
  186. {
  187. if (m_count.fetch_add(1, std::memory_order_release) < 0)
  188. {
  189. // There is a writer
  190. if (m_rdwake.fetch_add(-1, std::memory_order_acq_rel) == 1)
  191. {
  192. // We need to wake the writer up
  193. m_wrwset.inc();
  194. }
  195. }
  196. }
  197.  
  198.  
  199. // WRITE, more hefty
  200. void lock()
  201. {
  202. // Acquire exclusive access
  203. m_wrlock.lock();
  204.  
  205. // we are the only thread in here now.
  206.  
  207. // Gain write access wrt m_count
  208. long count = m_count.fetch_add(-RWMUTEX_COUNT_MAX, std::memory_order_acquire);
  209.  
  210. // count can never be negative.
  211. if (count < RWMUTEX_COUNT_MAX)
  212. {
  213. // We detected readers.
  214. long rdwake = m_rdwake.fetch_add(RWMUTEX_COUNT_MAX - count, std::memory_order_acquire);
  215.  
  216. if (rdwake + RWMUTEX_COUNT_MAX - count)
  217. {
  218. // Okay, we need to wait for all of the readers
  219. // The number of readers is actually
  220. // RWMUTEX_COUNT_MAX - count
  221. m_wrwset.dec();
  222. }
  223. }
  224. }
  225.  
  226. // write unlock
  227. void unlock()
  228. {
  229. // Release write access wrt m_count
  230. long count = m_count.fetch_add(RWMUTEX_COUNT_MAX, std::memory_order_release);
  231.  
  232. if (count < 0)
  233. {
  234. // We need to wake -count readers.
  235. m_rdwset.add(-count);
  236. }
  237.  
  238. // Release exclusive access
  239. m_wrlock.unlock();
  240. }
  241. };
  242.  
  243.  
  244. struct ct_simple_stack
  245. {
  246. struct node
  247. {
  248. node* volatile m_next; // to suppress optimization
  249. unsigned int m_tid;
  250.  
  251. node(unsigned int tid) : m_tid(tid) {}
  252. };
  253.  
  254. node* m_head;
  255.  
  256. ct_simple_stack() : m_head(nullptr) {}
  257.  
  258. ~ct_simple_stack()
  259. {
  260. ct_simple_stack::node* n = flush();
  261.  
  262. while (n)
  263. {
  264. ct_simple_stack::node* next = n->m_next;
  265. delete n;
  266. n = next;
  267. }
  268. }
  269.  
  270. void push(node* n)
  271. {
  272. n->m_next = m_head;
  273. m_head = n;
  274. }
  275.  
  276. node* pop()
  277. {
  278. node* n = m_head;
  279.  
  280. if (n)
  281. {
  282. m_head = n->m_next;
  283. }
  284.  
  285. return n;
  286. }
  287.  
  288.  
  289. node* flush()
  290. {
  291. node* n = m_head;
  292. m_head = nullptr;
  293. return n;
  294. }
  295.  
  296. };
  297.  
  298.  
  299. struct ct_shared
  300. {
  301. std::atomic<bool> m_run;
  302.  
  303. // protected by m_std_rwmutex
  304. std::uint64_t m_reads;
  305. std::uint64_t m_writes;
  306.  
  307. ct_simple_stack m_stack;
  308. ct_simple_stack m_stack_dtor;
  309.  
  310. #if defined (CT_TEST_FAST_MUTEX)
  311. ct_rwmutex m_std_rwmutex;
  312. #else
  313. std::shared_mutex m_std_rwmutex;
  314. #endif
  315.  
  316. ct_shared() : m_run(true), m_reads(0), m_writes(0)
  317. {
  318. // prime m_stack
  319. for (unsigned int i = 0; i < NODE_PRIME; ++i)
  320. {
  321. m_stack.push(new ct_simple_stack::node(i));
  322. }
  323. }
  324. };
  325.  
  326.  
  327. void ct_thread_reader(ct_shared& shared, std::size_t tidx)
  328. {
  329. std::uint64_t reads = 0;
  330.  
  331. while (shared.m_run.load(std::memory_order_relaxed))
  332. {
  333. shared.m_std_rwmutex.lock_shared();
  334.  
  335. ct_simple_stack::node* n = shared.m_stack.m_head;
  336.  
  337. while (n)
  338. {
  339. ct_simple_stack::node* next = n->m_next;
  340.  
  341. n = next;
  342. }
  343.  
  344. std::this_thread::yield();
  345.  
  346. shared.m_std_rwmutex.unlock_shared();
  347.  
  348. ++reads;
  349. }
  350.  
  351. shared.m_std_rwmutex.lock();
  352. shared.m_reads += reads;
  353. shared.m_std_rwmutex.unlock();
  354. }
  355.  
  356.  
  357. void ct_thread_writer(ct_shared& shared, std::size_t tidx)
  358. {
  359. std::uint64_t writes = 0;
  360.  
  361. while (shared.m_run.load(std::memory_order_relaxed))
  362. {
  363. shared.m_std_rwmutex.lock();
  364. ct_simple_stack::node* n = shared.m_stack.pop();
  365. shared.m_std_rwmutex.unlock();
  366.  
  367. std::this_thread::yield();
  368.  
  369. shared.m_std_rwmutex.lock();
  370.  
  371. if (n)
  372. {
  373. shared.m_stack.push(n);
  374. }
  375.  
  376. shared.m_std_rwmutex.unlock();
  377.  
  378. std::this_thread::yield();
  379.  
  380. ++writes;
  381. }
  382.  
  383. shared.m_std_rwmutex.lock();
  384. shared.m_writes += writes;
  385. shared.m_std_rwmutex.unlock();
  386. }
  387.  
  388.  
  389. int main()
  390. {
  391. ct_shared shared;
  392.  
  393. std::cout << "Testing Version 0.1: ";
  394.  
  395. #if defined (CT_TEST_FAST_MUTEX)
  396. std::cout << "Chris M. Thomasson's Experimental Read/Write Mutex\n\n";
  397. #else
  398. std::cout << "std::shared_mutex\n\n";
  399. #endif
  400.  
  401. std::cout.flush();
  402.  
  403. {
  404. // Setup our threads
  405. std::size_t cpu_threads_n = std::thread::hardware_concurrency();
  406. std::size_t threads_n = cpu_threads_n * THREADS;
  407. std::vector<std::thread> threads(threads_n);
  408.  
  409. std::size_t writers = threads_n / 2;
  410. std::size_t readers = threads_n - writers;
  411.  
  412. std::cout << "___________________________________\n";
  413. std::cout << "cpu_threads_n = " << cpu_threads_n << "\n";
  414. std::cout << "threads_n = " << threads_n << "\n";
  415. std::cout << "writers = " << writers << "\n";
  416. std::cout << "readers = " << readers << "\n";
  417. std::cout << "test duration = " << TEST_DURATION_SEC << " seconds\n";
  418. std::cout << "___________________________________\n\n\n";
  419.  
  420. auto time_start = std::chrono::high_resolution_clock::now();
  421.  
  422. // Create threads
  423. for (std::size_t i = 0; i < threads_n; ++i)
  424. {
  425. if (i < writers)
  426. {
  427. threads[i] = std::thread(ct_thread_writer, std::ref(shared), i);
  428. }
  429.  
  430. else
  431. {
  432. threads[i] = std::thread(ct_thread_reader, std::ref(shared), i);
  433. }
  434. }
  435.  
  436. // Give the test some time to run...
  437. std::chrono::seconds time_duration(TEST_DURATION_SEC);
  438. std::this_thread::sleep_for(time_duration);
  439. shared.m_run.store(false, std::memory_order_relaxed);
  440.  
  441. // Join threads
  442. for (std::size_t i = 0; i < threads_n; ++i)
  443. {
  444. threads[i].join();
  445. }
  446.  
  447. // Grab our timing.
  448. auto time_stop = std::chrono::high_resolution_clock::now();
  449.  
  450. std::chrono::duration<double> time_diff = time_stop - time_start;
  451.  
  452. std::uint64_t raw_reads = shared.m_reads;
  453. std::uint64_t raw_writes = shared.m_writes;
  454.  
  455. std::cout << "___________________________________\n";
  456. std::cout << "Raw Reads: " << raw_reads << "\n";
  457. std::cout << "Raw Writes: " << raw_writes << "\n";
  458.  
  459. std::uint64_t reads_per_tick = raw_reads / time_diff.count();
  460. std::uint64_t writes_per_tick = raw_writes / time_diff.count();
  461.  
  462. std::cout << "reads_per_tick = " << reads_per_tick << "\n";
  463. std::cout << "writes_per_tick = " << writes_per_tick << "\n";
  464.  
  465. std::cout << "Ticks = " << time_diff.count() << "\n";
  466. std::cout << "___________________________________\n\n";
  467. }
  468.  
  469. std::cout << "\n\nFin!\n";
  470.  
  471. return 0;
  472. }
Advertisement
Add Comment
Please, Sign In to add comment