Chris_M_Thomasson

Experimental SeqLock

Mar 11th, 2022
1,226
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 6.70 KB | None | 0 0
  1. #include <iostream>
  2. #include <thread>
  3. #include <atomic>
  4. #include <mutex>
  5. #include <shared_mutex> // for a benchmark
  6. #include <algorithm>
  7. #include <chrono>
  8. #include <ratio>
  9. #include <cstdlib>
  10.  
  11.  
  12.  
  13. // Sorry about the hardcoded macros! ;^(
  14. #define CT_THREADS 32
  15. #define CT_ITERS 20000000
  16. #define CT_SEQLOCK_CELL 64
  17.  
  18. // uncomment for to log read spins. SeqLock only...
  19. //#define CT_LOG
  20.  
  21. // uncomment to test shared mutex
  22. //#define CT_TEST_SHARED_MUTEX
  23.  
  24.  
  25. #if defined CT_LOG
  26. static std::atomic<unsigned long> g_log_read_spins(0);
  27. #endif
  28.  
  29.  
  30. // User data anyone? ;^)
  31. struct ct_data
  32. {
  33.     int m_a;
  34.     int m_b;
  35.     int m_c;
  36.  
  37.     ct_data() : m_a(0), m_b(1), m_c(2) {}
  38.  
  39.     void inc(ct_data const& data)
  40.     {
  41.         m_a = data.m_a + 1;
  42.         m_b = data.m_b + 1;
  43.         m_c = data.m_c + 1;
  44.     }
  45.  
  46.     bool validate()
  47.     {
  48.         return (m_a + 1 == m_b && m_b + 1 == m_c);
  49.     }
  50. };
  51.  
  52.  
  53. // A seqlock data cell
  54. struct ct_cell
  55. {
  56.     std::atomic<unsigned long> m_ver;
  57.     ct_data m_data;
  58.  
  59.     ct_cell() : m_ver(0) {}
  60. };
  61.  
  62.  
  63. // Wrapper for the user
  64. struct ct_cell_ref
  65. {
  66.     unsigned long m_ver;
  67.     ct_data m_data;
  68.     ct_cell* m_next;
  69. };
  70.  
  71.  
  72. // The main impl...
  73. struct ct_seqlock
  74. {
  75.     std::mutex m_mutex;
  76.     std::atomic<ct_cell*> m_cur;
  77.     ct_cell m_cells[CT_SEQLOCK_CELL];
  78.  
  79.     ct_seqlock() : m_cur(m_cells) {}
  80.  
  81.  
  82.     bool validate()
  83.     {
  84.         for (std::size_t i = 0; i < CT_SEQLOCK_CELL; ++i)
  85.         {
  86.             if (! m_cells[i].m_data.validate()) return false;
  87.         }
  88.  
  89.         return true;
  90.     }
  91.  
  92.     ct_cell_ref write_lock()
  93.     {
  94.         m_mutex.lock();
  95.  
  96.         ct_cell* cur = m_cur.load(std::memory_order_relaxed);
  97.         ct_cell* next = m_cells + ((cur - m_cells + 1) % CT_SEQLOCK_CELL);
  98.  
  99.         ct_cell_ref cell_ref = { next->m_ver, cur->m_data, next };
  100.  
  101.         next->m_ver.store(cell_ref.m_ver + 1, std::memory_order_relaxed);
  102.  
  103.         std::atomic_thread_fence(std::memory_order_acq_rel);
  104.  
  105.         return cell_ref;
  106.     }
  107.  
  108.     void write_unlock(ct_cell_ref const& cell_ref)
  109.     {
  110.         cell_ref.m_next->m_ver.store(cell_ref.m_ver + 2, std::memory_order_release);
  111.  
  112.         // make it visible to the reader...
  113.         m_cur.store(cell_ref.m_next, std::memory_order_release);
  114.  
  115.         m_mutex.unlock();
  116.     }
  117.  
  118.  
  119.     ct_data read()
  120.     {
  121.         unsigned long spins = 0;
  122.  
  123.         ct_data data;
  124.  
  125.         for (;;++spins)
  126.         {
  127.             ct_cell* cell = m_cur.load(std::memory_order_consume);
  128.  
  129.             unsigned long ver0 = cell->m_ver.load(std::memory_order_acquire);
  130.  
  131.             if (ver0 & 1)
  132.             {
  133.                 // spin on locked state
  134.                 // We can spin, backoff or do something else...
  135.                 // For now, we just yield and spin away! ;^)
  136.                 std::this_thread::yield();
  137.                 continue;
  138.             }
  139.  
  140.             data = cell->m_data;
  141.  
  142.             std::atomic_thread_fence(std::memory_order_acquire);
  143.  
  144.             unsigned long ver1 = cell->m_ver.load(std::memory_order_relaxed);
  145.  
  146.             if (ver0 == ver1) break;
  147.  
  148.             // spin on data changed
  149.         }
  150.  
  151. #if defined CT_LOG
  152.         g_log_read_spins.fetch_add(spins, std::memory_order_relaxed);
  153. #endif
  154.  
  155.         return data;
  156.     }
  157. };
  158.  
  159.  
  160.  
  161. // The test app
  162.  
  163. // a simple timer
  164. struct ct_time
  165. {
  166.     // Type aliases to make accessing nested type easier
  167.     using clock_type = std::chrono::steady_clock;
  168.     using second_type = std::chrono::duration<double, std::ratio<1> >;
  169.  
  170.     std::chrono::time_point<std::chrono::steady_clock> m_beg;
  171.  
  172.     ct_time() : m_beg(std::chrono::steady_clock::now()) {}
  173.  
  174.     void reset()
  175.     {
  176.         m_beg = std::chrono::steady_clock::now();
  177.     }
  178.  
  179.     double elapsed() const
  180.     {
  181.         return std::chrono::duration_cast<std::chrono::duration<double, std::ratio<1> >>
  182.             (clock_type::now() - m_beg).count();
  183.     }
  184. };
  185.  
  186.  
  187. struct ct_shared_state
  188. {
  189.     ct_seqlock m_seqlock;
  190.  
  191. #if defined (CT_TEST_SHARED_MUTEX)
  192.     std::shared_mutex m_std_shared_mutex;
  193. #endif
  194.  
  195.     void write()
  196.     {
  197. #if ! defined (CT_TEST_SHARED_MUTEX)
  198.         ct_cell_ref cell_ref = m_seqlock.write_lock();
  199.         cell_ref.m_next->m_data.inc(cell_ref.m_data);
  200.         m_seqlock.write_unlock(cell_ref);
  201. #else
  202.         m_std_shared_mutex.lock();
  203.         m_seqlock.m_cells[0].m_data.inc(m_seqlock.m_cells[0].m_data);
  204.         m_std_shared_mutex.unlock();
  205.  
  206. #endif
  207.     }
  208.  
  209.     bool read()
  210.     {
  211. #if ! defined (CT_TEST_SHARED_MUTEX)
  212.         ct_data data = m_seqlock.read();
  213.         return data.validate();
  214. #else
  215.         m_std_shared_mutex.lock_shared();
  216.         ct_data data = m_seqlock.m_cells[0].m_data;
  217.         m_std_shared_mutex.unlock_shared();
  218.         return data.validate();
  219. #endif
  220.     }
  221. };
  222.  
  223.  
  224.  
  225.  
  226.  
  227. void
  228. ct_thread(ct_shared_state& shared)
  229. {
  230.     for (unsigned long i = 0; i < CT_ITERS; ++i)
  231.     {
  232.         if (! shared.read()) break;
  233.         if (! shared.read()) break;
  234.         shared.write();
  235.         if (! shared.read()) break;
  236.         shared.write();
  237.         if (! shared.read()) break;
  238.         if (! shared.read()) break;
  239.     }
  240. }
  241.  
  242.  
  243.  
  244. int main()
  245. {
  246.     std::cout << "Quick and Dirty SeqLock Experiment ver:(0.0.0)\n";
  247.     std::cout << "By: Chris M. Thomasson\n";
  248.     std::cout << "________________________________________\n";
  249.     std::cout << "Threads = " << CT_THREADS << "\n";
  250.     std::cout << "Iterations = " << CT_ITERS << "\n";
  251.     std::cout << "SeqLock Cells = " << CT_SEQLOCK_CELL << "\n";
  252.  
  253. #if defined (CT_LOG)
  254.     std::cout << "Logging is on\n";
  255. #endif
  256.  
  257. #if defined (CT_TEST_SHARED_MUTEX)
  258.     std::cout << "Using a shared mutex\n";
  259. #endif
  260.  
  261.  
  262.     std::cout << "\nLaunching...\n";
  263.     std::cout.flush();
  264.  
  265.     ct_shared_state shared_state;
  266.  
  267.     ct_time timer;
  268.  
  269.     {
  270.         std::thread threads[CT_THREADS];
  271.  
  272.         for (unsigned int i = 0; i < CT_THREADS; ++i)
  273.         {
  274.             threads[i] = std::thread(ct_thread, std::ref(shared_state));
  275.         }
  276.  
  277.         std::cout << "Running...\n";
  278.         std::cout.flush();
  279.  
  280.         for (unsigned int i = 0; i < CT_THREADS; ++i)
  281.         {
  282.             threads[i].join();
  283.         }
  284.     }
  285.  
  286.     double time_elpased = timer.elapsed();
  287.  
  288.     std::cout << "Complete!\n";
  289.     std::cout << "Time = " << time_elpased << "\n";
  290.     std::cout << "________________________________________\n";
  291.  
  292.  
  293.     if (shared_state.m_seqlock.validate())
  294.     {
  295.         std::cout << "DATA IS COHERENT!!! :^D\n\n\n";
  296.     }
  297.  
  298.     else
  299.     {
  300.         std::cout << "DATA IS __FOOBAR__! God DAMN IT!!!! ;^o\n\n\n";
  301.     }
  302.  
  303. #if defined CT_LOG
  304.     unsigned long log_read_spins = g_log_read_spins.load(std::memory_order_relaxed);
  305.     std::cout << "CT_LOG: log_read_spins = " << log_read_spins << "\n";
  306. #endif
  307.  
  308.     return 0;
  309. }
Advertisement
Add Comment
Please, Sign In to add comment