spot  2.16
bloemen.hh
1 // -*- coding: utf-8 -*-
2 // Copyright (C) by the Spot authors, see the AUTHORS file for details.
3 //
4 // This file is part of Spot, a model checking library.
5 //
6 // Spot is free software; you can redistribute it and/or modify it
7 // under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // Spot is distributed in the hope that it will be useful, but WITHOUT
12 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
14 // License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 
19 #pragma once
20 
21 #include <atomic>
22 #include <chrono>
23 #include <stdlib.h>
24 #include <thread>
25 #include <vector>
26 #include <utility>
27 #include <spot/bricks/brick-hashset>
28 #include <spot/kripke/kripke.hh>
29 #include <spot/misc/common.hh>
30 #include <spot/misc/fixpool.hh>
31 #include <spot/misc/timer.hh>
32 #include <spot/twacube/twacube.hh>
33 #include <spot/twacube/fwd.hh>
34 #include <spot/mc/mc.hh>
35 
36 namespace spot
37 {
40  template<typename State,
41  typename StateHash,
42  typename StateEqual>
44  {
45 
46  public:
48  enum class uf_status { LIVE, LOCK, DEAD };
50  enum class list_status { BUSY, LOCK, DONE };
52  enum class claim_status { CLAIM_FOUND, CLAIM_NEW, CLAIM_DEAD };
53 
55  struct uf_element
56  {
58  State st_;
60  std::atomic<uf_element*> parent;
62  std::atomic<unsigned> worker_;
64  std::atomic<uf_element*> next_;
66  std::atomic<uf_status> uf_status_;
68  std::atomic<list_status> list_status_;
69  };
70 
73  {
76  { }
77 
79  uf_element_hasher() = default;
80 
82  brick::hash::hash128_t
83  hash(const uf_element* lhs) const
84  {
85  StateHash hash;
86  // Not modulo 31 according to brick::hashset specifications.
87  unsigned u = hash(lhs->st_) % (1<<30);
88  return {u, u};
89  }
90 
92  bool equal(const uf_element* lhs,
93  const uf_element* rhs) const
94  {
95  StateEqual equal;
96  return equal(lhs->st_, rhs->st_);
97  }
98  };
99 
101  using shared_map = brick::hashset::FastConcurrent <uf_element*,
103 
106  map_(uf.map_), tid_(uf.tid_), size_(std::thread::hardware_concurrency()),
107  nb_th_(std::thread::hardware_concurrency()), inserted_(0),
108  p_(sizeof(uf_element))
109  { }
110 
111 
113  iterable_uf(shared_map& map, unsigned tid):
114  map_(map), tid_(tid), size_(std::thread::hardware_concurrency()),
115  nb_th_(std::thread::hardware_concurrency()), inserted_(0),
116  p_(sizeof(uf_element))
117  {
118  }
119 
122 
124  std::pair<claim_status, uf_element*>
125  make_claim(State a)
126  {
127  unsigned w_id = (1U << tid_);
128 
129  // Setup and try to insert the new state in the shared map.
130  uf_element* v = (uf_element*) p_.allocate();
131  v->st_ = a;
132  v->parent = v;
133  v->next_ = v;
134  v->worker_ = 0;
135  v->uf_status_ = uf_status::LIVE;
136  v->list_status_ = list_status::BUSY;
137 
138  auto it = map_.insert({v});
139  bool b = it.isnew();
140 
141  // Insertion failed, delete element
142  // FIXME: Should we add a local cache to avoid useless allocations?
143  if (!b)
144  p_.deallocate(v);
145  else
146  ++inserted_;
147 
148  uf_element* a_root = find(*it);
149  if (a_root->uf_status_.load() == uf_status::DEAD)
150  return {claim_status::CLAIM_DEAD, *it};
151 
152  if ((a_root->worker_.load() & w_id) != 0)
153  return {claim_status::CLAIM_FOUND, *it};
154 
155  atomic_fetch_or(&(a_root->worker_), w_id);
156  while (a_root->parent.load() != a_root)
157  {
158  a_root = find(a_root);
159  atomic_fetch_or(&(a_root->worker_), w_id);
160  }
161 
162  return {claim_status::CLAIM_NEW, *it};
163  }
164 
167  {
168  uf_element* parent = a->parent.load();
169  uf_element* x = a;
170  uf_element* y;
171 
172  while (x != parent)
173  {
174  y = parent;
175  parent = y->parent.load();
176  if (parent == y)
177  return y;
178  x->parent.store(parent);
179  x = parent;
180  parent = x->parent.load();
181  }
182  return x;
183  }
184 
187  {
188  while (true)
189  {
190  uf_element* a_root = find(a);
191  uf_element* b_root = find(b);
192  if (a_root == b_root)
193  return true;
194 
195  if (a_root->parent.load() == a_root)
196  return false;
197  }
198  }
199 
202  {
203  uf_status expected = uf_status::LIVE;
204  if (a->uf_status_.load() == expected)
205  {
206  if (std::atomic_compare_exchange_strong
207  (&(a->uf_status_), &expected, uf_status::LOCK))
208  {
209  if (a->parent.load() == a)
210  return true;
211  unlock_root(a);
212  }
213  }
214  return false;
215  }
216 
218  inline void unlock_root(uf_element* a)
219  {
220  a->uf_status_.store(uf_status::LIVE);
221  }
222 
225  {
226  uf_element* a_list = a;
227  while (true)
228  {
229  bool dontcare = false;
230  a_list = pick_from_list(a_list, &dontcare);
231  if (a_list == nullptr)
232  {
233  return nullptr;
234  }
235 
236  auto expected = list_status::BUSY;
237  bool b = std::atomic_compare_exchange_strong
238  (&(a_list->list_status_), &expected, list_status::LOCK);
239 
240  if (b)
241  return a_list;
242 
243  a_list = a_list->next_.load();
244  }
245  }
246 
249  {
250  a->list_status_.store(list_status::BUSY);
251  }
252 
255  {
256  uf_element* a_root;
257  uf_element* b_root;
258  uf_element* q;
259  uf_element* r;
260 
261  while (true)
262  {
263  a_root = find(a);
264  b_root = find(b);
265 
266  if (a_root == b_root)
267  return;
268 
269  r = std::max(a_root, b_root);
270  q = std::min(a_root, b_root);
271 
272  if (!lock_root(q))
273  continue;
274 
275  break;
276  }
277 
278  uf_element* a_list = lock_list(a);
279  if (a_list == nullptr)
280  {
281  unlock_root(q);
282  return;
283  }
284 
285  uf_element* b_list = lock_list(b);
286  if (b_list == nullptr)
287  {
288  unlock_list(a_list);
289  unlock_root(q);
290  return;
291  }
292 
293  SPOT_ASSERT(a_list->list_status_.load() == list_status::LOCK);
294  SPOT_ASSERT(b_list->list_status_.load() == list_status::LOCK);
295 
296  // Swapping
297  uf_element* a_next = a_list->next_.load();
298  uf_element* b_next = b_list->next_.load();
299  SPOT_ASSERT(a_next != nullptr);
300  SPOT_ASSERT(b_next != nullptr);
301 
302  a_list->next_.store(b_next);
303  b_list->next_.store(a_next);
304  q->parent.store(r);
305 
306  // Update workers
307  unsigned q_worker = q->worker_.load();
308  unsigned r_worker = r->worker_.load();
309  if ((q_worker|r_worker) != r_worker)
310  {
311  atomic_fetch_or(&(r->worker_), q_worker);
312  while (r->parent.load() != r)
313  {
314  r = find(r);
315  atomic_fetch_or(&(r->worker_), q_worker);
316  }
317  }
318 
319  unlock_list(a_list);
320  unlock_list(b_list);
321  unlock_root(q);
322  }
323 
325  uf_element* pick_from_list(uf_element* u, bool* sccfound)
326  {
327  uf_element* a = u;
328  while (true)
329  {
330  list_status a_status;
331  while (true)
332  {
333  a_status = a->list_status_.load();
334 
335  if (a_status == list_status::BUSY)
336  {
337  return a;
338  }
339 
340  if (a_status == list_status::DONE)
341  break;
342  }
343 
344  uf_element* b = a->next_.load();
345 
346  // ------------------------------ NO LAZY : start
347  // if (b == u)
348  // {
349  // uf_element* a_root = find(a);
350  // uf_status status = a_root->uf_status_.load();
351  // while (status != uf_status::DEAD)
352  // {
353  // if (status == uf_status::LIVE)
354  // *sccfound = std::atomic_compare_exchange_strong
355  // (&(a_root->uf_status_), &status, uf_status::DEAD);
356  // status = a_root->uf_status_.load();
357  // }
358  // return nullptr;
359  // }
360  // a = b;
361  // ------------------------------ NO LAZY : end
362 
363  if (a == b)
364  {
365  uf_element* a_root = find(u);
366  uf_status status = a_root->uf_status_.load();
367  while (status != uf_status::DEAD)
368  {
369  if (status == uf_status::LIVE)
370  *sccfound = std::atomic_compare_exchange_strong
371  (&(a_root->uf_status_), &status, uf_status::DEAD);
372  status = a_root->uf_status_.load();
373  }
374  return nullptr;
375  }
376 
377  list_status b_status;
378  while (true)
379  {
380  b_status = b->list_status_.load();
381 
382  if (b_status == list_status::BUSY)
383  {
384  return b;
385  }
386 
387  if (b_status == list_status::DONE)
388  break;
389  }
390 
391  SPOT_ASSERT(b_status == list_status::DONE);
392  SPOT_ASSERT(a_status == list_status::DONE);
393 
394  uf_element* c = b->next_.load();
395  a->next_.store(c);
396  a = c;
397  }
398  }
399 
402  {
403  while (true)
404  {
405  list_status a_status = a->list_status_.load();
406 
407  if (a_status == list_status::DONE)
408  break;
409 
410  if (a_status == list_status::BUSY)
411  std::atomic_compare_exchange_strong
412  (&(a->list_status_), &a_status, list_status::DONE);
413  }
414  }
415 
417  unsigned inserted()
418  {
419  return inserted_;
420  }
421 
422  private:
423  iterable_uf() = default;
424 
425  shared_map map_;
426  unsigned tid_;
427  unsigned size_;
428  unsigned nb_th_;
429  unsigned inserted_;
431  };
432 
439  template<typename State, typename SuccIterator,
440  typename StateHash, typename StateEqual>
442  {
443  private:
444  swarmed_bloemen() = delete;
445 
446  public:
447 
451  using uf_element = typename uf::uf_element;
452 
454  using shared_struct = uf;
456  using shared_map = typename uf::shared_map;
457 
460  {
461  return new uf(m, i);
462  }
463 
466  twacube_ptr, /* useless here */
467  shared_map& map, /* useless here */
469  unsigned tid,
470  std::atomic<bool>& stop):
471  sys_(sys), uf_(*uf), tid_(tid),
472  nb_th_(std::thread::hardware_concurrency()),
473  stop_(stop)
474  {
475  static_assert(spot::is_a_kripkecube_ptr<decltype(&sys),
476  State, SuccIterator>::value,
477  "error: does not match the kripkecube requirements");
478  }
479 
481  void run()
482  {
483  setup();
484  State init = sys_.initial(tid_);
485  auto pair = uf_.make_claim(init);
486  todo_.push_back(pair.second);
487  Rp_.push_back(pair.second);
488  ++states_;
489 
490  while (!todo_.empty())
491  {
492  bloemen_recursive_start:
493  while (!stop_.load(std::memory_order_relaxed))
494  {
495  bool sccfound = false;
496  uf_element* v_prime = uf_.pick_from_list(todo_.back(), &sccfound);
497  if (v_prime == nullptr)
498  {
499  // The SCC has been explored!
500  sccs_ += sccfound;
501  break;
502  }
503 
504  auto it = sys_.succ(v_prime->st_, tid_);
505  while (!it->done())
506  {
507  auto w = uf_.make_claim(it->state());
508  it->next();
509  ++transitions_;
510  if (w.first == uf::claim_status::CLAIM_NEW)
511  {
512  todo_.push_back(w.second);
513  Rp_.push_back(w.second);
514  ++states_;
515  sys_.recycle(it, tid_);
516  goto bloemen_recursive_start;
517  }
518  else if (w.first == uf::claim_status::CLAIM_FOUND)
519  {
520  while (!uf_.sameset(todo_.back(), w.second))
521  {
522  uf_element* r = Rp_.back();
523  Rp_.pop_back();
524  uf_.unite(r, Rp_.back());
525  }
526  }
527  }
528  uf_.remove_from_list(v_prime);
529  sys_.recycle(it, tid_);
530  }
531 
532  if (todo_.back() == Rp_.back())
533  Rp_.pop_back();
534  todo_.pop_back();
535  }
536  finalize();
537  }
538 
540  void setup()
541  {
542  tm_.start("DFS thread " + std::to_string(tid_));
543  }
544 
546  void finalize()
547  {
548  bool tst_val = false;
549  bool new_val = true;
550  bool exchanged = stop_.compare_exchange_strong(tst_val, new_val);
551  if (exchanged)
552  finisher_ = true;
553  tm_.stop("DFS thread " + std::to_string(tid_));
554  }
555 
557  bool finisher()
558  {
559  return finisher_;
560  }
561 
563  unsigned states()
564  {
565  return states_;
566  }
567 
569  unsigned transitions()
570  {
571  return transitions_;
572  }
573 
575  unsigned walltime()
576  {
577  return tm_.timer("DFS thread " + std::to_string(tid_)).walltime();
578  }
579 
581  std::string name()
582  {
583  return "bloemen_scc";
584  }
585 
587  int sccs()
588  {
589  return sccs_;
590  }
591 
594  {
595  return mc_rvalue::SUCCESS;
596  }
597 
599  std::string trace()
600  {
601  // Returning a trace makes no sense in this algorithm
602  return "";
603  }
604 
605  private:
607  std::vector<uf_element*> todo_;
608  std::vector<uf_element*> Rp_;
610  unsigned tid_;
611  unsigned nb_th_;
612  unsigned inserted_ = 0;
613  unsigned states_ = 0;
614  unsigned transitions_ = 0;
615  unsigned sccs_ = 0;
616  spot::timer_map tm_;
617  std::atomic<bool>& stop_;
618  bool finisher_ = false;
619  };
620 }
void * allocate()
Allocate size bytes of memory.
Definition: fixpool.hh:88
This class allows to ensure (at compile time) if a given parameter is of type kripkecube....
Definition: kripke.hh:71
Iterable Union-Find for parallel reachability algorithms.
Definition: bloemen.hh:44
uf_status
Status values for union-find elements.
Definition: bloemen.hh:48
bool sameset(uf_element *a, uf_element *b)
Check if elements are in same set.
Definition: bloemen.hh:186
std::pair< claim_status, uf_element * > make_claim(State a)
Try to claim a state; returns status and element pointer.
Definition: bloemen.hh:125
iterable_uf(const iterable_uf< State, StateHash, StateEqual > &uf)
Copy constructor.
Definition: bloemen.hh:105
void remove_from_list(uf_element *a)
Mark element as removed from list.
Definition: bloemen.hh:401
iterable_uf(shared_map &map, unsigned tid)
Constructor from shared map and thread ID.
Definition: bloemen.hh:113
claim_status
Status values for claim operations.
Definition: bloemen.hh:52
void unlock_root(uf_element *a)
Unlock root element.
Definition: bloemen.hh:218
brick::hashset::FastConcurrent< uf_element *, uf_element_hasher > shared_map
Concurrent hashset for shared state storage.
Definition: bloemen.hh:102
void unlock_list(uf_element *a)
Unlock list element.
Definition: bloemen.hh:248
uf_element * find(uf_element *a)
Find root of element using path compression.
Definition: bloemen.hh:166
uf_element * pick_from_list(uf_element *u, bool *sccfound)
Pick element from list; mark SCC as dead if complete.
Definition: bloemen.hh:325
unsigned inserted()
Return number of successfully inserted states.
Definition: bloemen.hh:417
bool lock_root(uf_element *a)
Lock root element if live; return true if successful.
Definition: bloemen.hh:201
list_status
Status values for list operations.
Definition: bloemen.hh:50
~iterable_uf()
Destructor.
Definition: bloemen.hh:121
void unite(uf_element *a, uf_element *b)
Unite two sets.
Definition: bloemen.hh:254
uf_element * lock_list(uf_element *a)
Lock next element in list.
Definition: bloemen.hh:224
This class is a template representation of a Kripke structure. It is composed of two template paramet...
Definition: kripke.hh:40
Bloemen parallel SCC decomposition algorithm for reachability.
Definition: bloemen.hh:442
typename uf::shared_map shared_map
Type alias for shared map.
Definition: bloemen.hh:456
unsigned walltime()
Return wall time in milliseconds.
Definition: bloemen.hh:575
int sccs()
Return number of SCCs found.
Definition: bloemen.hh:587
mc_rvalue result()
Return emptiness check result.
Definition: bloemen.hh:593
unsigned transitions()
Return number of transitions traversed.
Definition: bloemen.hh:569
static shared_struct * make_shared_structure(shared_map m, unsigned i)
Create shared structure for thread tid.
Definition: bloemen.hh:459
std::string name()
Return algorithm name.
Definition: bloemen.hh:581
bool finisher()
Check if this thread finished the search.
Definition: bloemen.hh:557
unsigned states()
Return number of states visited.
Definition: bloemen.hh:563
void finalize()
Finalize thread resources.
Definition: bloemen.hh:546
void run()
Run the algorithm.
Definition: bloemen.hh:481
std::string trace()
Return trace (returns empty string)
Definition: bloemen.hh:599
typename uf::uf_element uf_element
Type alias for union-find element.
Definition: bloemen.hh:451
iterable_uf< State, StateHash, StateEqual > uf
Type alias for iterable union-find.
Definition: bloemen.hh:449
swarmed_bloemen(kripkecube< State, SuccIterator > &sys, twacube_ptr, shared_map &map, iterable_uf< State, StateHash, StateEqual > *uf, unsigned tid, std::atomic< bool > &stop)
Constructor for parallel Bloemen algorithm.
Definition: bloemen.hh:465
void setup()
Setup thread resources.
Definition: bloemen.hh:540
A map of timer, where each timer has a name.
Definition: timer.hh:231
void stop(const std::string &name)
Stop timer name.
Definition: timer.hh:251
void start(const std::string &name)
Start a timer with name name.
Definition: timer.hh:240
const spot::timer & timer(const std::string &name) const
Return the timer name.
Definition: timer.hh:276
std::chrono::milliseconds::rep walltime() const
Return cumulative wall time.
Definition: timer.hh:208
void deallocate(void *ptr)
Recycle size bytes of memory.
Definition: fixpool.hh:133
std::shared_ptr< twacube > twacube_ptr
Definition: fwd.hh:25
Definition: automata.hh:26
mc_rvalue
Return value of a parallel model-checking algorithm.
Definition: mc.hh:50
@ SUCCESS
The algorithm finished normally.
Hasher for union-find elements.
Definition: bloemen.hh:73
uf_element_hasher(const uf_element *)
Constructor from element pointer.
Definition: bloemen.hh:75
brick::hash::hash128_t hash(const uf_element *lhs) const
Compute hash of element.
Definition: bloemen.hh:83
uf_element_hasher()=default
Default constructor.
bool equal(const uf_element *lhs, const uf_element *rhs) const
Check equality of elements.
Definition: bloemen.hh:92
Represents a Union-Find element.
Definition: bloemen.hh:56
std::atomic< list_status > list_status_
current status for the list
Definition: bloemen.hh:68
std::atomic< unsigned > worker_
The set of worker for a given state.
Definition: bloemen.hh:62
State st_
the state handled by the element
Definition: bloemen.hh:58
std::atomic< uf_element * > parent
reference to the pointer
Definition: bloemen.hh:60
std::atomic< uf_element * > next_
next element for work stealing
Definition: bloemen.hh:64
std::atomic< uf_status > uf_status_
current status for the element
Definition: bloemen.hh:66

Please direct any question, comment, or bug report to the Spot mailing list at spot@lrde.epita.fr.
Generated on Fri Feb 27 2015 10:00:07 for spot by doxygen 1.9.1