spot  2.16
bloemen_ec.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 <spot/bricks/brick-hashset>
24 #include <stdlib.h>
25 #include <thread>
26 #include <vector>
27 #include <utility>
28 #include <spot/misc/common.hh>
29 #include <spot/kripke/kripke.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/intersect.hh>
35 #include <spot/mc/mc.hh>
36 
37 namespace spot
38 {
41  template<typename State,
42  typename StateHash,
43  typename StateEqual>
45  {
46 
47  public:
49  enum class uf_status { LIVE, LOCK, DEAD };
51  enum class list_status { BUSY, LOCK, DONE };
53  enum class claim_status { CLAIM_FOUND, CLAIM_NEW, CLAIM_DEAD };
54 
56  struct uf_element
57  {
59  State st_kripke;
61  unsigned st_prop;
65  std::mutex acc_mutex_;
67  std::atomic<uf_element*> parent;
69  std::atomic<unsigned> worker_;
71  std::atomic<uf_element*> next_;
73  std::atomic<uf_status> uf_status_;
75  std::atomic<list_status> list_status_;
76  };
77 
80  {
83  { }
84 
86  uf_element_hasher() = default;
87 
89  brick::hash::hash128_t
90  hash(const uf_element* lhs) const
91  {
92  StateHash hash;
93  // Not modulo 31 according to brick::hashset specifications.
94  unsigned u = hash(lhs->st_kripke) % (1<<30);
95  u = wang32_hash(lhs->st_prop) ^ u;
96  u = u % (1<<30);
97  return {u, u};
98  }
99 
101  bool equal(const uf_element* lhs,
102  const uf_element* rhs) const
103  {
104  StateEqual equal;
105  return (lhs->st_prop == rhs->st_prop)
106  && equal(lhs->st_kripke, rhs->st_kripke);
107  }
108  };
109 
111  using shared_map = brick::hashset::FastConcurrent <uf_element*,
113 
116  map_(uf.map_), tid_(uf.tid_), size_(std::thread::hardware_concurrency()),
117  nb_th_(std::thread::hardware_concurrency()), inserted_(0),
118  p_(sizeof(uf_element))
119  { }
120 
122  iterable_uf_ec(shared_map& map, unsigned tid):
123  map_(map), tid_(tid), size_(std::thread::hardware_concurrency()),
124  nb_th_(std::thread::hardware_concurrency()), inserted_(0),
125  p_(sizeof(uf_element))
126  { }
127 
130 
132  std::pair<claim_status, uf_element*>
133  make_claim(State kripke, unsigned prop)
134  {
135  unsigned w_id = (1U << tid_);
136 
137  // Setup and try to insert the new state in the shared map.
138  uf_element* v = (uf_element*) p_.allocate();
139  new (v) (uf_element); // required, otherwise the mutex is uninitialized
140  v->st_kripke = kripke;
141  v->st_prop = prop;
142  v->acc = {};
143  v->parent = v;
144  v->next_ = v;
145  v->worker_ = 0;
146  v->uf_status_ = uf_status::LIVE;
147  v->list_status_ = list_status::BUSY;
148 
149  auto it = map_.insert({v});
150  bool b = it.isnew();
151 
152  // Insertion failed, delete element
153  // FIXME Should we add a local cache to avoid useless allocations?
154  if (!b)
155  p_.deallocate(v);
156  else
157  ++inserted_;
158 
159  uf_element* a_root = find(*it);
160  if (a_root->uf_status_.load() == uf_status::DEAD)
161  return {claim_status::CLAIM_DEAD, *it};
162 
163  if ((a_root->worker_.load() & w_id) != 0)
164  return {claim_status::CLAIM_FOUND, *it};
165 
166  atomic_fetch_or(&(a_root->worker_), w_id);
167  while (a_root->parent.load() != a_root)
168  {
169  a_root = find(a_root);
170  atomic_fetch_or(&(a_root->worker_), w_id);
171  }
172 
173  return {claim_status::CLAIM_NEW, *it};
174  }
175 
178  {
179  uf_element* parent = a->parent.load();
180  uf_element* x = a;
181  uf_element* y;
182 
183  while (x != parent)
184  {
185  y = parent;
186  parent = y->parent.load();
187  if (parent == y)
188  return y;
189  x->parent.store(parent);
190  x = parent;
191  parent = x->parent.load();
192  }
193  return x;
194  }
195 
198  {
199  while (true)
200  {
201  uf_element* a_root = find(a);
202  uf_element* b_root = find(b);
203  if (a_root == b_root)
204  return true;
205 
206  if (a_root->parent.load() == a_root)
207  return false;
208  }
209  }
210 
213  {
214  uf_status expected = uf_status::LIVE;
215  if (a->uf_status_.load() == expected)
216  {
217  if (std::atomic_compare_exchange_strong
218  (&(a->uf_status_), &expected, uf_status::LOCK))
219  {
220  if (a->parent.load() == a)
221  return true;
222  unlock_root(a);
223  }
224  }
225  return false;
226  }
227 
229  inline void unlock_root(uf_element* a)
230  {
231  a->uf_status_.store(uf_status::LIVE);
232  }
233 
236  {
237  uf_element* a_list = a;
238  while (true)
239  {
240  bool dontcare = false;
241  a_list = pick_from_list(a_list, &dontcare);
242  if (a_list == nullptr)
243  {
244  return nullptr;
245  }
246 
247  auto expected = list_status::BUSY;
248  bool b = std::atomic_compare_exchange_strong
249  (&(a_list->list_status_), &expected, list_status::LOCK);
250 
251  if (b)
252  return a_list;
253 
254  a_list = a_list->next_.load();
255  }
256  }
257 
260  {
261  a->list_status_.store(list_status::BUSY);
262  }
263 
267  {
268  uf_element* a_root;
269  uf_element* b_root;
270  uf_element* q;
271  uf_element* r;
272 
273  do
274  {
275  a_root = find(a);
276  b_root = find(b);
277 
278  if (a_root == b_root)
279  {
280  // Update acceptance condition
281  {
282  std::lock_guard<std::mutex> rlock(a_root->acc_mutex_);
283  acc |= a_root->acc;
284  a_root->acc = acc;
285  }
286 
287  while (a_root->parent.load() != a_root)
288  {
289  a_root = find(a_root);
290  std::lock_guard<std::mutex> rlock(a_root->acc_mutex_);
291  acc |= a_root->acc;
292  a_root->acc = acc;
293  }
294  return acc;
295  }
296 
297  r = std::max(a_root, b_root);
298  q = std::min(a_root, b_root);
299  }
300  while (!lock_root(q));
301 
302  uf_element* a_list = lock_list(a);
303  if (a_list == nullptr)
304  {
305  unlock_root(q);
306  return acc;
307  }
308 
309  uf_element* b_list = lock_list(b);
310  if (b_list == nullptr)
311  {
312  unlock_list(a_list);
313  unlock_root(q);
314  return acc;
315  }
316 
317  SPOT_ASSERT(a_list->list_status_.load() == list_status::LOCK);
318  SPOT_ASSERT(b_list->list_status_.load() == list_status::LOCK);
319 
320  // Swapping
321  uf_element* a_next = a_list->next_.load();
322  uf_element* b_next = b_list->next_.load();
323  SPOT_ASSERT(a_next != nullptr);
324  SPOT_ASSERT(b_next != nullptr);
325 
326  a_list->next_.store(b_next);
327  b_list->next_.store(a_next);
328  q->parent.store(r);
329 
330  // Update workers
331  unsigned q_worker = q->worker_.load();
332  unsigned r_worker = r->worker_.load();
333  if ((q_worker|r_worker) != r_worker)
334  {
335  atomic_fetch_or(&(r->worker_), q_worker);
336  while (r->parent.load() != r)
337  {
338  r = find(r);
339  atomic_fetch_or(&(r->worker_), q_worker);
340  }
341  }
342 
343  // Update acceptance condition
344  {
345  std::lock_guard<std::mutex> rlock(r->acc_mutex_);
346  std::lock_guard<std::mutex> qlock(q->acc_mutex_);
347  acc |= r->acc | q->acc;
348  r->acc = q->acc = acc;
349  }
350 
351  while (r->parent.load() != r)
352  {
353  r = find(r);
354  std::lock_guard<std::mutex> rlock(r->acc_mutex_);
355  std::lock_guard<std::mutex> qlock(q->acc_mutex_);
356  acc |= r->acc | q->acc;
357  r->acc = acc;
358  }
359 
360  unlock_list(a_list);
361  unlock_list(b_list);
362  unlock_root(q);
363  return acc;
364  }
365 
367  uf_element* pick_from_list(uf_element* u, bool* sccfound)
368  {
369  uf_element* a = u;
370  while (true)
371  {
372  list_status a_status;
373  while (true)
374  {
375  a_status = a->list_status_.load();
376 
377  if (a_status == list_status::BUSY)
378  return a;
379 
380  if (a_status == list_status::DONE)
381  break;
382  }
383 
384  uf_element* b = a->next_.load();
385 
386  // ------------------------------ NO LAZY : start
387  // if (b == u)
388  // {
389  // uf_element* a_root = find(a);
390  // uf_status status = a_root->uf_status_.load();
391  // while (status != uf_status::DEAD)
392  // {
393  // if (status == uf_status::LIVE)
394  // *sccfound = std::atomic_compare_exchange_strong
395  // (&(a_root->uf_status_), &status, uf_status::DEAD);
396  // status = a_root->uf_status_.load();
397  // }
398  // return nullptr;
399  // }
400  // a = b;
401  // ------------------------------ NO LAZY : end
402 
403  if (a == b)
404  {
405  uf_element* a_root = find(u);
406  uf_status status = a_root->uf_status_.load();
407  while (status != uf_status::DEAD)
408  {
409  if (status == uf_status::LIVE)
410  *sccfound = std::atomic_compare_exchange_strong
411  (&(a_root->uf_status_), &status, uf_status::DEAD);
412  status = a_root->uf_status_.load();
413  }
414  return nullptr;
415  }
416 
417  list_status b_status;
418  while (true)
419  {
420  b_status = b->list_status_.load();
421 
422  if (b_status == list_status::BUSY)
423  return b;
424 
425  if (b_status == list_status::DONE)
426  break;
427  }
428 
429  SPOT_ASSERT(b_status == list_status::DONE);
430  SPOT_ASSERT(a_status == list_status::DONE);
431 
432  uf_element* c = b->next_.load();
433  a->next_.store(c);
434  a = c;
435  }
436  }
437 
440  {
441  while (true)
442  {
443  list_status a_status = a->list_status_.load();
444 
445  if (a_status == list_status::DONE)
446  break;
447 
448  if (a_status == list_status::BUSY)
449  std::atomic_compare_exchange_strong
450  (&(a->list_status_), &a_status, list_status::DONE);
451  }
452  }
453 
455  unsigned inserted()
456  {
457  return inserted_;
458  }
459 
460  private:
461  iterable_uf_ec() = default;
462 
463  shared_map map_;
464  unsigned tid_;
465  unsigned size_;
466  unsigned nb_th_;
467  unsigned inserted_;
469  };
470 
477  template<typename State, typename SuccIterator,
478  typename StateHash, typename StateEqual>
480  {
481  private:
482  swarmed_bloemen_ec() = delete;
483  public:
484 
488  using uf_element = typename uf::uf_element;
489 
491  using shared_struct = uf;
493  using shared_map = typename uf::shared_map;
494 
497  {
498  return new uf(m, i);
499  }
500 
504  shared_map& map, /* useless here */
506  unsigned tid,
507  std::atomic<bool>& stop):
508  sys_(sys), twa_(twa), uf_(*uf), tid_(tid),
509  nb_th_(std::thread::hardware_concurrency()),
510  stop_(stop)
511  {
512  static_assert(spot::is_a_kripkecube_ptr<decltype(&sys),
513  State, SuccIterator>::value,
514  "error: does not match the kripkecube requirements");
515  }
516 
518  ~swarmed_bloemen_ec() = default;
519 
521  void run()
522  {
523  setup();
524  State init_kripke = sys_.initial(tid_);
525  unsigned init_twa = twa_->get_initial();
526  auto pair = uf_.make_claim(init_kripke, init_twa);
527  todo_.push_back(pair.second);
528  Rp_.push_back(pair.second);
529  ++states_;
530 
531  while (!todo_.empty())
532  {
533  bloemen_recursive_start:
534  while (!stop_.load(std::memory_order_relaxed))
535  {
536  bool sccfound = false;
537  uf_element* v_prime = uf_.pick_from_list(todo_.back(), &sccfound);
538  if (v_prime == nullptr)
539  {
540  // The SCC has been explored!
541  sccs_ += sccfound;
542  break;
543  }
544 
545  auto it_kripke = sys_.succ(v_prime->st_kripke, tid_);
546  auto it_prop = twa_->succ(v_prime->st_prop);
547  forward_iterators(sys_, twa_, it_kripke, it_prop, true, tid_);
548  while (!it_kripke->done())
549  {
550  auto w = uf_.make_claim(it_kripke->state(),
551  twa_->trans_storage(it_prop, tid_)
552  .dst);
553  auto trans_acc = twa_->trans_storage(it_prop, tid_).acc_;
554  ++transitions_;
555  if (w.first == uf::claim_status::CLAIM_NEW)
556  {
557  todo_.push_back(w.second);
558  Rp_.push_back(w.second);
559  ++states_;
560  sys_.recycle(it_kripke, tid_);
561  goto bloemen_recursive_start;
562  }
563  else if (w.first == uf::claim_status::CLAIM_FOUND)
564  {
565  acc_cond::mark_t scc_acc = trans_acc;
566 
567  // This operation is mandatory to update acceptance marks.
568  // Otherwise, when w.second and todo.back() are
569  // already in the same set, the acceptance condition will
570  // not be added.
571  scc_acc |= uf_.unite(w.second, w.second, scc_acc);
572 
573  while (!uf_.sameset(todo_.back(), w.second))
574  {
575  uf_element* r = Rp_.back();
576  Rp_.pop_back();
577  uf_.unite(r, Rp_.back(), scc_acc);
578  }
579 
580 
581  {
582  auto root = uf_.find(w.second);
583  std::lock_guard<std::mutex> lock(root->acc_mutex_);
584  scc_acc = root->acc;
585  }
586 
587  // cycle found in SCC and it contains acceptance condition
588  if (twa_->acc().accepting(scc_acc))
589  {
590  sys_.recycle(it_kripke, tid_);
591  stop_ = true;
592  is_empty_ = false;
593  tm_.stop("DFS thread " + std::to_string(tid_));
594  return;
595  }
596  }
597  forward_iterators(sys_, twa_, it_kripke, it_prop,
598  false, tid_);
599  }
600  uf_.remove_from_list(v_prime);
601  sys_.recycle(it_kripke, tid_);
602  }
603 
604  if (todo_.back() == Rp_.back())
605  Rp_.pop_back();
606  todo_.pop_back();
607  }
608  finalize();
609  }
610 
612  void setup()
613  {
614  tm_.start("DFS thread " + std::to_string(tid_));
615  }
616 
618  void finalize()
619  {
620  bool tst_val = false;
621  bool new_val = true;
622  bool exchanged = stop_.compare_exchange_strong(tst_val, new_val);
623  if (exchanged)
624  finisher_ = true;
625  tm_.stop("DFS thread " + std::to_string(tid_));
626  }
627 
629  bool finisher()
630  {
631  return finisher_;
632  }
633 
635  unsigned states()
636  {
637  return states_;
638  }
639 
641  unsigned transitions()
642  {
643  return transitions_;
644  }
645 
647  unsigned walltime()
648  {
649  return tm_.timer("DFS thread " + std::to_string(tid_)).walltime();
650  }
651 
653  std::string name()
654  {
655  return "bloemen_ec";
656  }
657 
659  int sccs()
660  {
661  return sccs_;
662  }
663 
666  {
667  return is_empty_ ? mc_rvalue::EMPTY : mc_rvalue::NOT_EMPTY;
668  }
669 
671  std::string trace()
672  {
673  return "Not implemented";
674  }
675 
676  private:
678  twacube_ptr twa_;
679  std::vector<uf_element*> todo_;
680  std::vector<uf_element*> Rp_;
682  unsigned tid_;
683  unsigned nb_th_;
684  unsigned inserted_ = 0;
685  unsigned states_ = 0;
686  unsigned transitions_ = 0;
687  unsigned sccs_ = 0;
688  bool is_empty_ = true;
689  spot::timer_map tm_;
690  std::atomic<bool>& stop_;
691  bool finisher_ = false;
692  };
693 }
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 emptiness-check algorithms.
Definition: bloemen_ec.hh:45
std::pair< claim_status, uf_element * > make_claim(State kripke, unsigned prop)
Try to claim a state; returns status and element pointer.
Definition: bloemen_ec.hh:133
claim_status
Status values for claim operations.
Definition: bloemen_ec.hh:53
~iterable_uf_ec()
Destructor.
Definition: bloemen_ec.hh:129
uf_element * find(uf_element *a)
Find root of element using path compression.
Definition: bloemen_ec.hh:177
void unlock_root(uf_element *a)
Unlock root element.
Definition: bloemen_ec.hh:229
list_status
Status values for list operations.
Definition: bloemen_ec.hh:51
acc_cond::mark_t unite(uf_element *a, uf_element *b, acc_cond::mark_t acc)
Unite two sets with acceptance condition; return new acc.
Definition: bloemen_ec.hh:266
brick::hashset::FastConcurrent< uf_element *, uf_element_hasher > shared_map
Concurrent hashset for shared state storage.
Definition: bloemen_ec.hh:112
unsigned inserted()
Return number of successfully inserted states.
Definition: bloemen_ec.hh:455
void remove_from_list(uf_element *a)
Mark element as removed from list.
Definition: bloemen_ec.hh:439
iterable_uf_ec(shared_map &map, unsigned tid)
Constructor from shared map and thread ID.
Definition: bloemen_ec.hh:122
uf_element * pick_from_list(uf_element *u, bool *sccfound)
Pick element from list; mark SCC as dead if complete.
Definition: bloemen_ec.hh:367
iterable_uf_ec(const iterable_uf_ec< State, StateHash, StateEqual > &uf)
Copy constructor.
Definition: bloemen_ec.hh:115
void unlock_list(uf_element *a)
Unlock list element.
Definition: bloemen_ec.hh:259
uf_status
Status values for union-find elements.
Definition: bloemen_ec.hh:49
bool sameset(uf_element *a, uf_element *b)
Check if elements are in same set.
Definition: bloemen_ec.hh:197
bool lock_root(uf_element *a)
Lock root element if live; return true if successful.
Definition: bloemen_ec.hh:212
uf_element * lock_list(uf_element *a)
Lock next element in list.
Definition: bloemen_ec.hh:235
Interface for a Kripke structure.
Definition: kripke.hh:178
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 emptiness check.
Definition: bloemen_ec.hh:480
std::string trace()
Return trace (not implemented)
Definition: bloemen_ec.hh:671
mc_rvalue result()
Return emptiness check result.
Definition: bloemen_ec.hh:665
unsigned transitions()
Return number of transitions traversed.
Definition: bloemen_ec.hh:641
unsigned states()
Return number of states visited.
Definition: bloemen_ec.hh:635
static shared_struct * make_shared_structure(shared_map m, unsigned i)
Create shared structure for thread tid.
Definition: bloemen_ec.hh:496
void run()
Run the algorithm.
Definition: bloemen_ec.hh:521
typename uf::shared_map shared_map
Type alias for shared map.
Definition: bloemen_ec.hh:493
typename uf::uf_element uf_element
Type alias for union-find element.
Definition: bloemen_ec.hh:488
int sccs()
Return number of SCCs found.
Definition: bloemen_ec.hh:659
~swarmed_bloemen_ec()=default
Destructor.
std::string name()
Return algorithm name.
Definition: bloemen_ec.hh:653
void setup()
Setup thread resources.
Definition: bloemen_ec.hh:612
void finalize()
Finalize thread resources.
Definition: bloemen_ec.hh:618
iterable_uf_ec< State, StateHash, StateEqual > uf
Type alias for iterable union-find.
Definition: bloemen_ec.hh:486
unsigned walltime()
Return wall time in milliseconds.
Definition: bloemen_ec.hh:647
bool finisher()
Check if this thread finished the search.
Definition: bloemen_ec.hh:629
swarmed_bloemen_ec(kripkecube< State, SuccIterator > &sys, twacube_ptr twa, shared_map &map, iterable_uf_ec< State, StateHash, StateEqual > *uf, unsigned tid, std::atomic< bool > &stop)
Constructor for parallel Bloemen algorithm.
Definition: bloemen_ec.hh:502
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
A Transition-based ω-Automaton.
Definition: twa.hh:648
size_t wang32_hash(size_t key)
Thomas Wang's 32 bit hash function.
Definition: hashfunc.hh:37
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
@ NOT_EMPTY
The product is not empty.
@ EMPTY
The product is empty.
An acceptance mark.
Definition: acc.hh:76
Hasher for union-find elements.
Definition: bloemen_ec.hh:80
bool equal(const uf_element *lhs, const uf_element *rhs) const
Check equality of elements.
Definition: bloemen_ec.hh:101
uf_element_hasher()=default
Default constructor.
uf_element_hasher(const uf_element *)
Constructor from element pointer.
Definition: bloemen_ec.hh:82
brick::hash::hash128_t hash(const uf_element *lhs) const
Compute hash of element.
Definition: bloemen_ec.hh:90
Represents a Union-Find element.
Definition: bloemen_ec.hh:57
State st_kripke
the kripke state handled by the element
Definition: bloemen_ec.hh:59
std::mutex acc_mutex_
mutex for acceptance condition
Definition: bloemen_ec.hh:65
std::atomic< uf_element * > parent
reference to the pointer
Definition: bloemen_ec.hh:67
std::atomic< unsigned > worker_
The set of worker for a given state.
Definition: bloemen_ec.hh:69
std::atomic< list_status > list_status_
current status for the list
Definition: bloemen_ec.hh:75
unsigned st_prop
the prop state handled by the element
Definition: bloemen_ec.hh:61
acc_cond::mark_t acc
acceptance conditions of the union
Definition: bloemen_ec.hh:63
std::atomic< uf_status > uf_status_
current status for the element
Definition: bloemen_ec.hh:73
std::atomic< uf_element * > next_
next element for work stealing
Definition: bloemen_ec.hh:71

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