Chris_M_Thomasson

Per-Thread Mutex Proxy GC Example...

Dec 18th, 2018
380
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 11.83 KB | None | 0 0
  1. // Simple Per-Thread Mutex Based Proxy Collector
  2. // For Academic and Experimental things...
  3. // Beginner, Moderate?
  4. // In Good Ol' Relacy! Nice as always.
  5. //_______________________________________________
  6.  
  7.  
  8. //#define RL_DEBUGBREAK_ON_ASSERT
  9. //#define RL_MSVC_OUTPUT
  10. //#define RL_FORCE_SEQ_CST
  11. //#define RL_GC
  12.  
  13.  
  14. #include <relacy/relacy_std.hpp>
  15. #include <vector>
  16. #include <algorithm>
  17. #include <iostream>
  18.  
  19.  
  20. // Simple macro based redirection of the verobsse std membars.
  21. #define CT_MB_ACQ std::memory_order_acquire
  22. #define CT_MB_REL std::memory_order_release
  23. #define CT_MB_RLX std::memory_order_relaxed
  24. #define CT_MB_ACQ_REL std::memory_order_acq_rel
  25. #define CT_MB(mp_0) std::atomic_thread_fence(mp_0)
  26.  
  27.  
  28. // Some global vars directing the show...
  29. #define WORKERS 4
  30. #define REAPERS 2 // set it to one for now...
  31. #define ITERS 5
  32. #define THREADS (WORKERS + REAPERS)
  33.  
  34.  
  35. // A User Node
  36. struct unode
  37. {
  38.     std::atomic<unode*> m_next;
  39.     VAR_T(unsigned long) m_data;
  40.     unode(unsigned long data, unode* next = nullptr) : m_data(data), m_next(next) {}
  41. };
  42.  
  43.  
  44. // The global ptex state
  45. template<std::size_t T_threads>
  46. struct ptex
  47. {
  48.  
  49.     // Per-thread state
  50.     struct per_thread
  51.     {
  52.         std::mutex m_quiesce; // essential for this scheme
  53.         VAR_T(unsigned long) m_ver_local; // for future use
  54.         std::atomic<unsigned long> m_ver_reap; // for future use
  55.  
  56.  
  57.         per_thread() : m_ver_local(0), m_ver_reap(0)
  58.         {
  59.             //std::cout << "ptex::per_thread::per_thread()\n";
  60.         }
  61.  
  62.         ~per_thread() throw()
  63.         {
  64.             //std::cout << "ptex::per_thread::~per_thread()\n";
  65.         }
  66.  
  67.  
  68.         // A thread enters a region
  69.         void acquire()
  70.         {
  71.             unsigned long nver = VAR(m_ver_local)++;
  72.             m_quiesce.lock($);
  73.             m_ver_reap.store(nver, CT_MB_RLX);
  74.             CT_MB(CT_MB_ACQ);
  75.         }
  76.  
  77.  
  78.         // A thread exits a region
  79.         void release()
  80.         {
  81.             CT_MB(CT_MB_REL);
  82.             unsigned long ver = VAR(m_ver_local);
  83.             m_ver_reap.store(ver + 1, CT_MB_RLX);
  84.             m_quiesce.unlock($);
  85.             VAR(m_ver_local) = ver + 1;
  86.         }
  87.  
  88.  
  89.         void quiesce_brute()
  90.         {
  91.             // Ensure quiescence
  92.             m_quiesce.lock($);
  93.             m_quiesce.unlock($);
  94.         }
  95.     };
  96.  
  97.  
  98.     per_thread m_threads[T_threads];
  99.     std::atomic<unode*> m_collected;
  100.  
  101.  
  102.     ptex() : m_collected(nullptr)
  103.     {
  104.         //std::cout << "ptex::ptex()\n";
  105.     }
  106.  
  107.  
  108.     ~ptex() throw()
  109.     {
  110.         RL_ASSERT(!m_collected.load(CT_MB_RLX));
  111.         //std::cout << "ptex::~ptex()\n";
  112.     }
  113.  
  114.     // Gain a ref to our threads per-thread struct
  115.     per_thread& get_ref() throw()
  116.     {
  117.         return m_threads[rl::thread_index()];
  118.     }
  119.  
  120.     // delete everything...
  121.     void dump(unode* n)
  122.     {
  123.         while (n)
  124.         {
  125.             unode* next = n->m_next.load(CT_MB_RLX);
  126.             delete n;
  127.             n = next;
  128.         }
  129.     }
  130.  
  131.  
  132.     // We are finished, collect the node...
  133.     void collect(unode* n) throw()
  134.     {
  135.         unode* head = m_collected.load(CT_MB_RLX);
  136.  
  137.         do
  138.         {
  139.             n->m_next.store(head, CT_MB_RLX);
  140.             CT_MB(CT_MB_REL); // release
  141.  
  142.         } while (!m_collected.compare_exchange_weak(
  143.             head, n, CT_MB_RLX));
  144.     }
  145.  
  146.  
  147.     // Flush the stack
  148.     unode* reap() throw()
  149.     {
  150.         unode* head = m_collected.exchange(nullptr, CT_MB_RLX);
  151.         if (head) CT_MB(CT_MB_ACQ); // acquire
  152.         return head;
  153.     }
  154.  
  155.  
  156.     // Wait for a quiesce
  157.     void quiesce_brute() throw()
  158.     {
  159.         for (std::size_t i = 0; i < T_threads; ++i)
  160.         {
  161.             per_thread& pt = m_threads[i];
  162.             //pt.quiesce();
  163.             pt.quiesce_brute();
  164.         }
  165.     }
  166. };
  167.  
  168.  
  169. // Relacy Multex Test...
  170. struct ct_multex_test
  171.     : rl::test_suite<ct_multex_test, THREADS>
  172. {
  173.     typedef ptex<THREADS> ptex_t;
  174.     ptex_t g_ptex; // Global ptex
  175.     std::atomic<unode*> g_worker_stack; // Global User Stack
  176.     unsigned long g_contrived_shutdown; // Just for shutdown
  177.  
  178.  
  179.     void before()
  180.     {
  181.         g_worker_stack.store(nullptr, CT_MB_RLX);
  182.         g_contrived_shutdown = WORKERS;
  183.     }
  184.  
  185.  
  186.     void after()
  187.     {
  188.  
  189.     }
  190.  
  191.  
  192.     // Add a user item to the stack
  193.     void worker_push(unode* n)
  194.     {
  195.         unode* head = g_worker_stack.load(CT_MB_RLX);
  196.  
  197.         do
  198.         {
  199.             n->m_next.store(head, CT_MB_RLX);
  200.             CT_MB(CT_MB_REL); // release
  201.         } while (!g_worker_stack.compare_exchange_weak(head, n, CT_MB_RLX));
  202.     }
  203.  
  204.     // Remove a user item from the stack
  205.     unode* worker_pop_try()
  206.     {
  207.         unode* head = g_worker_stack.load(CT_MB_RLX);
  208.         unode* next = nullptr;
  209.  
  210.         do
  211.         {
  212.             if (!head) break;
  213.             CT_MB(CT_MB_ACQ); // acquire
  214.             next = head->m_next.load(CT_MB_RLX); // critical load! ;^o
  215.  
  216.         } while (!g_worker_stack.compare_exchange_weak(head, next, CT_MB_RLX));
  217.  
  218.         return head;
  219.     }
  220.  
  221.  
  222.     // A User Worker Example. The main point!
  223.     void worker()
  224.     {
  225.         ptex_t::per_thread& self = g_ptex.get_ref();
  226.         //std::cout << "worker[" << rl::thread_index() << "]\n";
  227.  
  228.         // perform some work
  229.         for (unsigned long i = 0; i < ITERS; ++i)
  230.         {
  231.             // Push a node
  232.             unode* n = new unode(rl::thread_index());
  233.  
  234.             worker_push(n);
  235.  
  236.  
  237.             // Pop a node
  238.             unode* nx = nullptr;
  239.  
  240.             for (;;)
  241.             {
  242.                 self.acquire(); // Per thread acquire
  243.  
  244.                 nx = worker_pop_try(); // In critical region
  245.  
  246.                 if (nx)
  247.                 {
  248.                     unsigned long data = VAR(nx->m_data);
  249.                     //std::cout << data << "\n";
  250.                     RL_ASSERT(data < THREADS);
  251.                 }
  252.  
  253.                 self.release(); // Per thread acquire
  254.  
  255.                 if (nx) break;
  256.  
  257.                 rl::yield(1, $);
  258.             }
  259.  
  260.             // Collect a node
  261.             g_ptex.collect(nx); // We are fin with nx
  262.  
  263.  
  264.             // Quiesce a couple of times per iteration.
  265.             if ((i % 2) == 0)
  266.             {
  267.                 //self.quiesce(); // without brute force
  268.             }
  269.         }
  270.  
  271.         // remove our worker
  272.         --g_contrived_shutdown;
  273.     }
  274.  
  275.  
  276.  
  277.     // Holds user items in a stack
  278.     struct pending_reap
  279.     {
  280.         unode* m_head;
  281.         unode* m_tail;
  282.  
  283.         pending_reap() : m_head(nullptr), m_tail(nullptr) {}
  284.  
  285.         // push items
  286.         void push(unode* head, unode* tail)
  287.         {
  288.             if (!m_head)
  289.             {
  290.                 m_head = head;
  291.                 m_tail = tail;
  292.             }
  293.  
  294.             else
  295.             {
  296.                 tail->m_next.store(m_head, CT_MB_RLX);
  297.                 m_head = head;
  298.             }
  299.         }
  300.  
  301.         // dump all
  302.         void dump()
  303.         {
  304.             unode* n = m_head;
  305.             m_head = m_tail = nullptr;
  306.  
  307.             while (n)
  308.             {
  309.                 unode* next = n->m_next.load(CT_MB_RLX);
  310.                 delete n;
  311.                 n = next;
  312.             }
  313.         }
  314.     };
  315.  
  316.  
  317.     // Tracks a per_thread quiescence
  318.     struct thread_quiesce_node
  319.     {
  320.         thread_quiesce_node* m_next;
  321.         ptex_t::per_thread* m_thread;
  322.  
  323.         thread_quiesce_node(ptex_t::per_thread* thread) :
  324.             m_next(nullptr), m_thread(thread) {}
  325.     };
  326.  
  327.  
  328.     // tracks a reapers control of its view of quiescence
  329.     struct thread_quiesce
  330.     {
  331.         thread_quiesce_node* m_head;
  332.         thread_quiesce_node* m_ready;
  333.         ptex_t& m_ptex;
  334.  
  335.         thread_quiesce(ptex_t& ptex) : m_head(nullptr), m_ready(nullptr), m_ptex(ptex)
  336.         {
  337.             for (std::size_t i = 0; i < THREADS; ++i)
  338.             {
  339.                 thread_quiesce_node* n =
  340.                     new thread_quiesce_node(&ptex.m_threads[i]);
  341.  
  342.                 n->m_next = m_head;
  343.                 m_head = n;
  344.             }
  345.         }
  346.  
  347.         ~thread_quiesce()
  348.         {
  349.             thread_quiesce_node* n = m_head;
  350.  
  351.             // Dump the head
  352.             while (n)
  353.             {
  354.                 thread_quiesce_node* next = n->m_next;
  355.                 delete n;
  356.                 n = next;
  357.             }
  358.  
  359.             // Dump ready... We are at dtor anyway.
  360.             n = m_ready;
  361.  
  362.             while (n)
  363.             {
  364.                 thread_quiesce_node* next = n->m_next;
  365.                 delete n;
  366.                 n = next;
  367.             }
  368.         }
  369.  
  370.  
  371.         // Run a quiescence detection cycle
  372.         bool quiesce()
  373.         {
  374.             // Split sort...
  375.             thread_quiesce_node* not_ready = nullptr;
  376.             thread_quiesce_node* n = m_head;
  377.  
  378.             // Iterate...
  379.             while (n)
  380.             {
  381.                 thread_quiesce_node* next = n->m_next;
  382.  
  383.                 if (!n->m_thread->m_quiesce.try_lock($))
  384.                 {
  385.                     // NOT ready for the cycle...
  386.                     n->m_next = not_ready;
  387.                     not_ready = n;
  388.                 }
  389.  
  390.                 else
  391.                 {
  392.                     // PERFECT! We can run full steam ahead. :^)
  393.                     n->m_thread->m_quiesce.unlock($);
  394.                     n->m_next = m_ready;
  395.                     m_ready = n;
  396.                 }
  397.  
  398.                 n = next;
  399.             }
  400.  
  401.             m_head = not_ready;
  402.  
  403.             if (m_head) return false;
  404.  
  405.             // BINGO: TARGET ACQUIRED!
  406.             m_head = m_ready;
  407.             m_ready = nullptr;
  408.  
  409.             return true;
  410.         }
  411.     };
  412.  
  413.     // Add a reap to a reap...
  414.     unode* reap_penders(pending_reap& reap)
  415.     {
  416.         unode* fhead = g_ptex.reap();
  417.  
  418.         if (fhead)
  419.         {
  420.             // Find tail
  421.             unode* ftail = fhead;
  422.             unode* next = ftail->m_next.load(CT_MB_RLX);
  423.  
  424.             while (next)
  425.             {
  426.                 ftail = next;
  427.                 next = next->m_next.load(CT_MB_RLX);
  428.             }
  429.  
  430.             // Push onto fresh queue
  431.             reap.push(fhead, ftail);
  432.         }
  433.  
  434.         return fhead;
  435.     }
  436.  
  437.  
  438.     // Actually deletes user items
  439.     void reaper()
  440.     {
  441.         ptex_t::per_thread& self = g_ptex.get_ref();
  442.         //std::cout << "reaper[" << rl::thread_index() << "]\n";
  443.  
  444.         pending_reap fresh;
  445.         pending_reap old;
  446.         thread_quiesce tquiesce(g_ptex);
  447.  
  448.         unsigned long qcount = 0;
  449.  
  450.         // Reap Loop
  451.         {
  452.             unode* fhead = nullptr;
  453.  
  454.             while (g_contrived_shutdown != 0)
  455.             {
  456.                 reap_penders(fresh);
  457.  
  458.                 if (tquiesce.quiesce())
  459.                 {
  460.                     // The good ol' two step...
  461.                     pending_reap dumpster = old;
  462.                     old = fresh;
  463.                     fresh.m_head = fresh.m_tail = nullptr;
  464.  
  465.                     dumpster.dump();
  466.                 }
  467.  
  468.                 rl::yield(1, $);
  469.             }
  470.         }
  471.  
  472.         // Clean up, for we are fin.
  473.         reap_penders(fresh);
  474.  
  475.         g_ptex.quiesce_brute(); // wait for a quiescence point
  476.  
  477.         // Flush... Drain sound... ;^)
  478.         fresh.dump();
  479.         old.dump();
  480.     }
  481.  
  482.  
  483.     // Direct the work to the correct threads... ;^)
  484.     void thread(unsigned int tidx)
  485.     {
  486.         if (tidx < REAPERS)
  487.         {
  488.             reaper();
  489.         }
  490.  
  491.         else
  492.         {
  493.             worker();
  494.         }
  495.     }
  496. };
  497.  
  498.  
  499.  
  500. // Test away... Or fly? Humm...
  501. int main()
  502. {
  503.     {
  504.         rl::test_params p;
  505.  
  506.         p.iteration_count = 5000000;
  507.         //p.execution_depth_limit = 33333;
  508.        // p.search_type = rl::sched_bound;
  509.         //p.search_type = rl::fair_full_search_scheduler_type;
  510.         //p.search_type = rl::fair_context_bound_scheduler_type;
  511.  
  512.         rl::simulate<ct_multex_test>(p);
  513.     }
  514.  
  515.     return 0;
  516. }
Advertisement
Add Comment
Please, Sign In to add comment