souffle  2.0.2-371-g6315b36
PiggyList.h
Go to the documentation of this file.
1 #pragma once
2 
4 #include <array>
5 #include <atomic>
6 #include <cassert>
7 #include <cstring>
8 #include <iostream>
9 #include <iterator>
10 
11 #ifdef _WIN32
12 /**
13  * Some versions of MSVC do not provide a builtin for counting leading zeroes
14  * like gcc, so we have to implement it ourselves.
15  */
16 #if defined(_MSC_VER)
17 unsigned long __inline __builtin_clzll(unsigned long long value) {
18  unsigned long msb = 0;
19 
20  if (_BitScanReverse64(&msb, value))
21  return 63 - msb;
22  else
23  return 64;
24 }
25 #endif // _MSC_VER
26 #endif // _WIN32
27 
28 using std::size_t;
29 namespace souffle {
30 
31 /**
32  * A PiggyList that allows insertAt functionality.
33  * This means we can't append, as we don't know the next available element.
34  * insertAt is dangerous. You must be careful not to call it for the same index twice!
35  */
36 template <class T>
38 public:
39  RandomInsertPiggyList() = default;
40  // an instance where the initial size is not 65k, and instead is user settable (to a power of
41  // initialbitsize)
42  RandomInsertPiggyList(size_t initialbitsize) : BLOCKBITS(initialbitsize) {}
43 
44  /** copy constructor */
46  this->numElements.store(other.numElements.load());
47 
48  // copy blocks from the old lookup table to this one
49  for (size_t i = 0; i < maxContainers; ++i) {
50  if (other.blockLookupTable[i].load() != nullptr) {
51  // calculate the size of that block
52  const size_t blockSize = INITIALBLOCKSIZE << i;
53 
54  // allocate that in the new container
55  this->blockLookupTable[i].store(new T[blockSize]);
56 
57  // then copy the stuff over
58  std::memcpy(this->blockLookupTable[i].load(), other.blockLookupTable[i].load(),
59  blockSize * sizeof(T));
60  }
61  }
62  }
63 
64  // move ctr
66  // copy assign ctor
68  // move assign ctor
70 
72  freeList();
73  }
74 
75  inline size_t size() const {
76  return numElements.load();
77  }
78 
79  inline T* getBlock(size_t blockNum) const {
80  return blockLookupTable[blockNum];
81  }
82 
83  inline T& get(size_t index) const {
84  size_t nindex = index + INITIALBLOCKSIZE;
85  size_t blockNum = (63 - __builtin_clzll(nindex));
86  size_t blockInd = (nindex) & ((1 << blockNum) - 1);
87  return this->getBlock(blockNum - BLOCKBITS)[blockInd];
88  }
89 
90  void insertAt(size_t index, T value) {
91  // starting with an initial blocksize requires some shifting to transform into a nice powers of two
92  // series
93  size_t blockNum = (63 - __builtin_clzll(index + INITIALBLOCKSIZE)) - BLOCKBITS;
94 
95  // allocate the block if not allocated
96  if (blockLookupTable[blockNum].load() == nullptr) {
97  slock.lock();
98  if (blockLookupTable[blockNum].load() == nullptr) {
99  blockLookupTable[blockNum].store(new T[INITIALBLOCKSIZE << blockNum]);
100  }
101  slock.unlock();
102  }
103 
104  this->get(index) = value;
105  // we ALWAYS increment size, even if there was something there before (its impossible to tell!)
106  // the onus is up to the user to not call this for an index twice
107  ++numElements;
108  }
109 
110  void clear() {
111  freeList();
112  numElements.store(0);
113  }
114  const size_t BLOCKBITS = 16ul;
115  const size_t INITIALBLOCKSIZE = (1ul << BLOCKBITS);
116 
117  // number of elements currently stored within
118  std::atomic<size_t> numElements{0};
119 
120  // 2^64 - 1 elements can be stored (default initialised to nullptrs)
121  static constexpr size_t maxContainers = 64;
122  std::array<std::atomic<T*>, maxContainers> blockLookupTable = {};
123 
124  // for parallel node insertions
125  mutable SpinLock slock;
126 
127  /**
128  * Free the arrays allocated within the linked list nodes
129  */
130  void freeList() {
131  slock.lock();
132  // delete all - deleting a nullptr is a no-op
133  for (size_t i = 0; i < maxContainers; ++i) {
134  delete[] blockLookupTable[i].load();
135  // reset the container within to be empty.
136  blockLookupTable[i].store(nullptr);
137  }
138  slock.unlock();
139  }
140 };
141 
142 template <class T>
143 class PiggyList {
144 public:
146  PiggyList(size_t initialbitsize)
147  : BLOCKBITS(initialbitsize), num_containers(0), container_size(0), m_size(0) {}
148 
149  /** copy constructor */
150  PiggyList(const PiggyList& other) : BLOCKBITS(other.BLOCKBITS) {
151  num_containers.store(other.num_containers.load());
152  container_size.store(other.container_size.load());
153  m_size.store(other.m_size.load());
154  // copy each chunk from other into this
155  // the size of the next container to allocate
156  size_t cSize = BLOCKSIZE;
157  for (size_t i = 0; i < other.num_containers; ++i) {
158  this->blockLookupTable[i] = new T[cSize];
159  std::memcpy(this->blockLookupTable[i], other.blockLookupTable[i], cSize * sizeof(T));
160  cSize <<= 1;
161  }
162  // if this isn't the case, uhh
163  assert((cSize >> 1) == container_size.load());
164  }
165 
166  /** move constructor */
167  PiggyList(PiggyList&& other) = delete;
168  /** copy assign ctor **/
169  PiggyList& operator=(const PiggyList& other) = delete;
170 
172  freeList();
173  }
174 
175  /**
176  * Well, returns the number of nodes exist within the list + number of nodes queued to be inserted
177  * The reason for this, is that there may be many nodes queued up
178  * that haven't had time to had containers created and updated
179  * @return the number of nodes exist within the list + number of nodes queued to be inserted
180  */
181  inline size_t size() const {
182  return m_size.load();
183  };
184 
185  inline T* getBlock(size_t blocknum) const {
186  return this->blockLookupTable[blocknum];
187  }
188 
189  size_t append(T element) {
190  size_t new_index = m_size.fetch_add(1, std::memory_order_acquire);
191 
192  // will this not fit?
193  if (container_size < new_index + 1) {
194  sl.lock();
195  // check and add as many containers as required
196  while (container_size < new_index + 1) {
198  num_containers += 1;
200  // double the number elements that will be allocated next time
201  allocsize <<= 1;
202  }
203  sl.unlock();
204  }
205 
206  this->get(new_index) = element;
207  return new_index;
208  }
209 
210  size_t createNode() {
211  size_t new_index = m_size.fetch_add(1, std::memory_order_acquire);
212 
213  // will this not fit?
214  if (container_size < new_index + 1) {
215  sl.lock();
216  // check and add as many containers as required
217  while (container_size < new_index + 1) {
219  num_containers += 1;
221  // double the number elements that will be allocated next time
222  allocsize <<= 1;
223  }
224  sl.unlock();
225  }
226 
227  return new_index;
228  }
229 
230  /**
231  * Retrieve a reference to the stored value at index
232  * @param index position to search
233  * @return the value at index
234  */
235  inline T& get(size_t index) const {
236  // supa fast 2^16 size first block
237  size_t nindex = index + BLOCKSIZE;
238  size_t blockNum = (63 - __builtin_clzll(nindex));
239  size_t blockInd = (nindex) & ((1 << blockNum) - 1);
240  return this->getBlock(blockNum - BLOCKBITS)[blockInd];
241  }
242 
243  /**
244  * Clear all elements from the PiggyList
245  */
246  void clear() {
247  freeList();
248  m_size = 0;
249  num_containers = 0;
250 
252  container_size = 0;
253  }
254 
255  class iterator : std::iterator<std::forward_iterator_tag, T> {
256  size_t cIndex = 0;
258 
259  public:
260  // default ctor, to silence
261  iterator() = default;
262 
263  /* begin iterator for iterating over all elements */
265  /* ender iterator for marking the end of the iteration */
266  iterator(PiggyList* bl, size_t beginInd) : cIndex(beginInd), bl(bl){};
267 
268  T operator*() {
269  return bl->get(cIndex);
270  };
271  const T operator*() const {
272  return bl->get(cIndex);
273  };
274 
276  ++cIndex;
277  return *this;
278  };
279 
281  iterator ret(*this);
282  ++cIndex;
283  return ret;
284  };
285 
286  bool operator==(const iterator& x) const {
287  return x.cIndex == this->cIndex && x.bl == this->bl;
288  };
289 
290  bool operator!=(const iterator& x) const {
291  return !(x == *this);
292  };
293  };
294 
295  iterator begin() {
296  return iterator(this);
297  }
298  iterator end() {
299  return iterator(this, size());
300  }
301  const size_t BLOCKBITS = 16ul;
302  const size_t BLOCKSIZE = (1ul << BLOCKBITS);
303 
304  // number of inserted
305  std::atomic<size_t> num_containers = 0;
307  std::atomic<size_t> container_size = 0;
308  std::atomic<size_t> m_size = 0;
309 
310  // > 2^64 elements can be stored (default initialise to nullptrs)
311  static constexpr size_t max_conts = 64;
312  std::array<T*, max_conts> blockLookupTable = {};
313 
314  // for parallel node insertions
315  mutable SpinLock sl;
316 
317  /**
318  * Free the arrays allocated within the linked list nodes
319  */
320  void freeList() {
321  sl.lock();
322  // we don't know which ones are taken up!
323  for (size_t i = 0; i < num_containers; ++i) {
324  delete[] blockLookupTable[i];
325  }
326  sl.unlock();
327  }
328 };
329 
330 } // namespace souffle
souffle::RandomInsertPiggyList::slock
SpinLock slock
Definition: PiggyList.h:125
souffle::PiggyList::get
T & get(size_t index) const
Retrieve a reference to the stored value at index.
Definition: PiggyList.h:235
souffle::PiggyList::size
size_t size() const
Well, returns the number of nodes exist within the list + number of nodes queued to be inserted The r...
Definition: PiggyList.h:181
souffle::PiggyList::allocsize
size_t allocsize
Definition: PiggyList.h:306
souffle::PiggyList::iterator::operator*
const T operator*() const
Definition: PiggyList.h:271
souffle::PiggyList::PiggyList
PiggyList(size_t initialbitsize)
Definition: PiggyList.h:146
souffle::RandomInsertPiggyList::insertAt
void insertAt(size_t index, T value)
Definition: PiggyList.h:90
souffle::PiggyList::getBlock
T * getBlock(size_t blocknum) const
Definition: PiggyList.h:185
souffle::RandomInsertPiggyList::get
T & get(size_t index) const
Definition: PiggyList.h:83
ParallelUtil.h
souffle::PiggyList::BLOCKBITS
const size_t BLOCKBITS
Definition: PiggyList.h:301
souffle::PiggyList::iterator::operator*
T operator*()
Definition: PiggyList.h:268
souffle::PiggyList::begin
iterator begin()
Definition: PiggyList.h:295
souffle::PiggyList::iterator
Definition: PiggyList.h:255
souffle::PiggyList::iterator::iterator
iterator()=default
souffle::PiggyList::append
size_t append(T element)
Definition: PiggyList.h:189
souffle::PiggyList::iterator::operator==
bool operator==(const iterator &x) const
Definition: PiggyList.h:286
souffle::PiggyList::iterator::cIndex
size_t cIndex
Definition: PiggyList.h:256
souffle::RandomInsertPiggyList::INITIALBLOCKSIZE
const size_t INITIALBLOCKSIZE
Definition: PiggyList.h:115
souffle::RandomInsertPiggyList::RandomInsertPiggyList
RandomInsertPiggyList(size_t initialbitsize)
Definition: PiggyList.h:42
souffle::PiggyList::createNode
size_t createNode()
Definition: PiggyList.h:210
souffle::RandomInsertPiggyList::blockLookupTable
std::array< std::atomic< T * >, maxContainers > blockLookupTable
Definition: PiggyList.h:122
souffle::PiggyList::num_containers
std::atomic< size_t > num_containers
Definition: PiggyList.h:305
souffle::RandomInsertPiggyList::numElements
std::atomic< size_t > numElements
Definition: PiggyList.h:118
souffle::PiggyList::iterator::bl
PiggyList * bl
Definition: PiggyList.h:257
souffle::PiggyList::PiggyList
PiggyList()
Definition: PiggyList.h:145
souffle::PiggyList::iterator::iterator
iterator(PiggyList *bl)
Definition: PiggyList.h:264
souffle::PiggyList::iterator::iterator
iterator(PiggyList *bl, size_t beginInd)
Definition: PiggyList.h:266
i
size_t i
Definition: json11.h:663
souffle::PiggyList::iterator::operator++
iterator operator++()
Definition: PiggyList.h:280
souffle::PiggyList::sl
SpinLock sl
Definition: PiggyList.h:315
souffle::PiggyList::blockLookupTable
std::array< T *, max_conts > blockLookupTable
Definition: PiggyList.h:312
souffle::PiggyList
Definition: PiggyList.h:143
souffle::PiggyList::clear
void clear()
Clear all elements from the PiggyList.
Definition: PiggyList.h:246
souffle::RandomInsertPiggyList::BLOCKBITS
const size_t BLOCKBITS
Definition: PiggyList.h:114
souffle::SpinLock
A 'sequential' non-locking implementation for a spin lock.
Definition: ParallelUtil.h:487
souffle::RandomInsertPiggyList::operator=
RandomInsertPiggyList & operator=(RandomInsertPiggyList &other)=delete
souffle::PiggyList::PiggyList
PiggyList(const PiggyList &other)
copy constructor
Definition: PiggyList.h:150
souffle::PiggyList::operator=
PiggyList & operator=(const PiggyList &other)=delete
copy assign ctor
souffle::RandomInsertPiggyList::clear
void clear()
Definition: PiggyList.h:110
souffle::PiggyList::iterator::operator!=
bool operator!=(const iterator &x) const
Definition: PiggyList.h:290
souffle::RandomInsertPiggyList::getBlock
T * getBlock(size_t blockNum) const
Definition: PiggyList.h:79
souffle::RandomInsertPiggyList::freeList
void freeList()
Free the arrays allocated within the linked list nodes.
Definition: PiggyList.h:130
souffle::PiggyList::m_size
std::atomic< size_t > m_size
Definition: PiggyList.h:308
souffle::RandomInsertPiggyList::RandomInsertPiggyList
RandomInsertPiggyList(const RandomInsertPiggyList &other)
copy constructor
Definition: PiggyList.h:45
souffle::PiggyList::container_size
std::atomic< size_t > container_size
Definition: PiggyList.h:307
souffle::PiggyList::~PiggyList
~PiggyList()
Definition: PiggyList.h:171
souffle::SpinLock::lock
void lock()
Definition: ParallelUtil.h:491
souffle::RandomInsertPiggyList::RandomInsertPiggyList
RandomInsertPiggyList()=default
souffle::PiggyList::BLOCKSIZE
const size_t BLOCKSIZE
Definition: PiggyList.h:302
souffle::RandomInsertPiggyList::size
size_t size() const
Definition: PiggyList.h:75
souffle::PiggyList::freeList
void freeList()
Free the arrays allocated within the linked list nodes.
Definition: PiggyList.h:320
souffle
Definition: AggregateOp.h:25
souffle::RandomInsertPiggyList
A PiggyList that allows insertAt functionality.
Definition: PiggyList.h:37
souffle::RandomInsertPiggyList::maxContainers
static constexpr size_t maxContainers
Definition: PiggyList.h:121
souffle::PiggyList::end
iterator end()
Definition: PiggyList.h:298
souffle::SpinLock::unlock
void unlock()
Definition: ParallelUtil.h:497
souffle::PiggyList::max_conts
static constexpr size_t max_conts
Definition: PiggyList.h:311
souffle::PiggyList::iterator::operator++
iterator & operator++(int)
Definition: PiggyList.h:275
souffle::RandomInsertPiggyList::~RandomInsertPiggyList
~RandomInsertPiggyList()
Definition: PiggyList.h:71