2 Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
6 * @file CxxUtils/ConcurrentBitset.icc
7 * @author scott snyder <snyder@bnl.gov>
9 * @brief Variable-sized bitset allowing (mostly) concurrent access.
13 #include "CxxUtils/AthUnlikelyMacros.h"
19 //*********************************************************************************
20 // Constructors, destructors, assignment.
24 * @brief The number of bits that this container can hold.
27 ConcurrentBitset::bit_t ConcurrentBitset::capacity() const
29 return (*m_impl).nbits();
34 * @brief Count the number of 1 bits in the set.
37 ConcurrentBitset::bit_t ConcurrentBitset::count() const
39 return (*m_impl).count();
44 * @brief Count the number of 1 bits in the set.
46 * Note: If you regard this like a std::bitset, you would expect this to return
47 * the number of bits that the set can hold, while if you regard this like
48 * a set<bit_t>, then you would expect this to return the number of 1 bits.
49 * We follow the latter here.
52 ConcurrentBitset::bit_t ConcurrentBitset::size() const
54 return (*m_impl).count();
59 * @brief Test to see if a bit is set.
60 * @param bit Number of the bit to test.
61 * @return true if the bit is set; false otherwise.
63 * Returns false if @c bit is beyond the end of the set.
66 bool ConcurrentBitset::test (bit_t bit) const
68 return (*m_impl).test (bit);
72 * @brief Test to see if a bit is set.
73 * @param bit Number of the bit to test.
74 * @return 1 if the bit is set; 0 otherwise.
76 * Returns 0 if @c bit is beyond the end of the set.
79 size_t ConcurrentBitset::count (bit_t bit) const
86 * @brief Return true if there are no 1 bits in the set.
89 bool ConcurrentBitset::empty() const
96 * @brief Return true if there are no 1 bits in the set.
99 bool ConcurrentBitset::none() const
101 return (*m_impl).none();
106 * @brief Return true if all bits in the set are 1.
109 bool ConcurrentBitset::all() const
111 return (*m_impl).all();
116 * @brief Return true if there are any 1 bits in the set.
119 bool ConcurrentBitset::any() const
125 //*********************************************************************************
126 // Single-bit manipulation.
130 * @brief Turn on one bit.
131 * @param bit The bit to turn on.
133 * Does nothing if @c bit beyond the end of the set.
136 ConcurrentBitset& ConcurrentBitset::set (bit_t bit)
144 * @brief Turn off one bit.
145 * @param bit The bit to turn off.
147 * Does nothing if @c bit beyond the end of the set.
150 ConcurrentBitset& ConcurrentBitset::reset (bit_t bit)
152 (*m_impl).reset (bit);
158 * @brief Turn off one bit.
159 * @param bit The bit to turn off.
161 * Does nothing if @c bit beyond the end of the set.
164 ConcurrentBitset& ConcurrentBitset::erase (bit_t bit)
166 (*m_impl).reset (bit);
172 * @brief Flip the value of one bit.
173 * @param bit The bit to turn flip.
175 * Does nothing if @c bit beyond the end of the set.
178 ConcurrentBitset& ConcurrentBitset::flip (bit_t bit)
180 (*m_impl).flip (bit);
186 * @brief Set the value of one bit.
187 * @param bit The bit to turn set.
188 * @param val The value to which to set it.
190 * Does nothing if @c bit beyond the end of the set.
193 ConcurrentBitset& ConcurrentBitset::set (bit_t bit, bool val)
199 (*m_impl).reset (bit);
205 //*********************************************************************************
210 * @brief Clear all bits in the set.
212 * This operation is not necessarily atomic; a simultaneous read may be able
213 * to see the operation partially done.
216 ConcurrentBitset& ConcurrentBitset::clear()
224 * @brief Clear all bits in the set.
226 * This operation is not necessarily atomic; a simultaneous read may be able
227 * to see the operation partially done.
230 ConcurrentBitset& ConcurrentBitset::reset()
238 * @brief Turn on all bits in the set.
240 * This operation is not necessarily atomic; a simultaneous read may be able
241 * to see the operation partially done.
244 ConcurrentBitset& ConcurrentBitset::set()
251 * @brief Flip the state of all bits in the set.
253 * This operation is not necessarily atomic; a simultaneous read may be able
254 * to see the operation partially done.
257 ConcurrentBitset& ConcurrentBitset::flip()
265 * @brief AND this set with another set.
266 * @param other The other set.
268 * This operation is not necessarily atomic; a simultaneous read may be able
269 * to see the operation partially done.
272 ConcurrentBitset& ConcurrentBitset::operator&= (const ConcurrentBitset& other)
274 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a &= v; },
281 * @brief OR this set with another set.
282 * @param other The other set.
284 * This operation is not necessarily atomic; a simultaneous read may be able
285 * to see the operation partially done.
288 ConcurrentBitset& ConcurrentBitset::operator|= (const ConcurrentBitset& other)
290 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a |= v; },
297 * @brief XOR this set with another set.
298 * @param other The other set.
300 * This operation is not necessarily atomic; a simultaneous read may be able
301 * to see the operation partially done.
304 ConcurrentBitset& ConcurrentBitset::operator^= (const ConcurrentBitset& other)
306 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a ^= v; },
313 * @brief Subtract another set from this set.
314 * @param other The other set.
316 * This is the same as (*this) &= ~other;
318 * This operation is not necessarily atomic; a simultaneous read may be able
319 * to see the operation partially done.
322 ConcurrentBitset& ConcurrentBitset::operator-= (const ConcurrentBitset& other)
324 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a &= ~v; },
331 * @brief Return a new set that is the complement of this set.
334 ConcurrentBitset ConcurrentBitset::operator~() const
336 ConcurrentBitset b = *this;
342 //*********************************************************************************
347 * @brief Test two sets for equality.
348 * @param other The other set to test.
351 bool ConcurrentBitset::operator== (const ConcurrentBitset& other) const
353 return (*m_impl) == (*other.m_impl);
358 * @brief Test two sets for inequality.
359 * @param other The other set to test.
362 bool ConcurrentBitset::operator!= (const ConcurrentBitset& other) const
364 return ! ((*m_impl) == (*other.m_impl));
368 //*********************************************************************************
373 * @brief Set a bit to 1. Expand the set if needed.
374 * @param bit Number of the bit to set.
375 * @param new_nbits Hint for new size of set, if it needs to be expanded.
377 * If @c bit is past the end of the container, then the container will be
378 * expanded as needed.
380 * This operation is incompatible with any other simultaneous writes
381 * to the same set (reads are ok).
384 ConcurrentBitset& ConcurrentBitset::insert (bit_t bit, bit_t new_nbits /*= 0*/)
386 if (bit >= (*m_impl).nbits()) {
392 new_nbits = (bit * 2 + BLOCKSIZE-1) & ~(BLOCKSIZE-1);
401 * @brief Set several bits to 1. Expand the set if needed.
402 * @param beg Start of range of bits to set.
403 * @param end End of range of bits to set.
404 * @param new_nbits Hint for new size of set, if it needs to be expanded.
406 * The iteration range should be over something convertible to @c bit_t.
407 * If any bit is past the end of the container, then the container will be
408 * expanded as needed.
410 * This operation is incompatible with any other simultaneous writes
411 * to the same set (reads are ok).
415 * std::vector<bit_t> bits { 4, 10, 12};
416 * CxxUtils::ConcurrentBitset bs = ...;
417 * bs.insert (bits.begin(), bits.end());
420 template <class ITERATOR,
421 typename /*= typename std::enable_if<
423 typename std::forward_iterator_tag,
424 typename std::iterator_traits<ITERATOR>::iterator_category
428 ConcurrentBitset::insert (ITERATOR beg, ITERATOR end, bit_t new_nbits /*= 0*/)
430 for (; beg != end; ++beg) {
431 insert (*beg, new_nbits);
438 * @brief Set several bits to 1. Expand the set if needed.
439 * @param l List of bits to set.
440 * @param new_nbits Hint for new size of set, if it needs to be expanded.
442 * If any bit is past the end of the container, then the container will be
443 * expanded as needed.
445 * This operation is incompatible with any other simultaneous writes
446 * to the same set (reads are ok).
450 * std::vector<bit_t> bits { 4, 10, 12};
451 * bs.insert ({4, 10, 12});
456 ConcurrentBitset::insert (std::initializer_list<bit_t> l, bit_t new_nbits /*= 0*/)
458 auto max_it = std::max_element (l.begin(), l.end());
459 size_t bmax = max_it == l.end() ? 0 : *max_it + 1;
460 if (new_nbits > bmax) new_nbits = bmax;
470 * @brief Turn on bits listed in another set.
471 * @param other Set of bits to turn on.
473 * This is the same as @c operator|=, except that if the size of @c other is
474 * larger than this set, then this set will be expanded to match @c other.
476 * This operation is incompatible with any other simultaneous writes
477 * to the same set (reads are ok).
480 ConcurrentBitset& ConcurrentBitset::insert (const ConcurrentBitset& other)
482 const Impl* otherImpl = other.m_impl;
483 expand (otherImpl->nbits());
484 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a |= v; },
490 //*********************************************************************************
491 // Array-like element access.
495 * @brief Constructor.
496 * @param impl ConcurrentBitset implementation object.
497 * @param bit Bit number to which this reference refers.
500 ConcurrentBitset::reference::reference (Impl& impl, bit_t bit)
501 : m_block (impl.block(bit)),
502 m_mask (1UL << (bit&MASK))
508 * @brief Set the referenced bit to a given value.
509 * @param val Value to which to set the referenced bit.
512 ConcurrentBitset::reference&
513 ConcurrentBitset::reference::operator= (bool val) noexcept
524 * @brief Copy the value of another referenced bit.
525 * @param r The other reference.
529 * ConcurrentBitset& bs1 = ...;
530 * ConcurrentBitset& bs2 = ...;
535 ConcurrentBitset::reference&
536 ConcurrentBitset::reference::operator= (const reference& r) noexcept
539 *this = static_cast<bool> (r);
546 * @brief Return the value of the referenced bit.
549 ConcurrentBitset::reference::operator bool() const noexcept
551 return (*m_block & m_mask) != 0;
556 * @brief Return the complement of the value of the referenced bit.
559 bool ConcurrentBitset::reference::operator~() const noexcept
561 return (*m_block & m_mask) == 0;
566 * @brief Invert the referenced bit.
569 ConcurrentBitset::reference&
570 ConcurrentBitset::reference::flip() noexcept
578 * @brief Return the value of one bit.
579 * @param bit The number of the bit to test.
582 bool ConcurrentBitset::operator[] (bit_t bit) const
584 return (*m_impl).test (bit);
589 * @brief Return a reference to one bit.
590 * @param bit The number of the bit to reference.
592 * The reference will be invalidated by calls to @c insert() or @c operator=.
593 * Effects are undefined if @c bit is past the end of the set.
596 ConcurrentBitset::reference ConcurrentBitset::operator[] (bit_t bit)
598 return reference (*m_impl, bit);
602 //*********************************************************************************
603 // Iterator operations.
607 * @brief Constructor.
608 * @param cache Cached block at the current iteration point, shifted such
609 * that bit number @c bit has just been shifted off the right.
610 * @param bit Bit number the at which the iterator is currently pointing.
611 * @param data Pointer to the block at which the iterator is currently pointing.
612 * @param end One past the last block of the iteration range.
615 ConcurrentBitset::const_iterator::const_iterator (Block_t cache,
617 const std::atomic<Block_t>* data,
618 const std::atomic<Block_t>* end)
619 : m_cache (cache), m_bit (bit), m_data(data), m_end (end)
625 * @brief Return the bit number which the iterator is currently referencing.
628 ConcurrentBitset::bit_t
629 ConcurrentBitset::const_iterator::operator*() const
636 * @brief Advance the iterator to the next set bit.
639 ConcurrentBitset::const_iterator&
640 ConcurrentBitset::const_iterator::operator++()
642 // Are there any more bits set in this block?
643 Block_t cache = m_cache;
644 if (ATH_LIKELY (cache != 0)) {
645 // Yes --- find the first set bit.
646 // Shift until that bit just falls off the right and adjust
647 // the current position.
648 // We know that @c b will be less than @c BLOCKSIZE
649 // (avoiding undefined behavior), since at least one bit must have
650 // already been shifted off of the cache.
651 int b = CxxUtils::count_trailing_zeros (cache)+1;
653 m_cache = cache >> b;
656 // No, move to the next block.
657 // Bit number at the start of the next block.
658 unsigned int bit = (m_bit + BLOCKSIZE) & ~MASK;
660 // Pointers to the next block, and the end of iteration.
661 const std::atomic<Block_t>* data = m_data + 1;
662 const std::atomic<Block_t>* end = m_end;
664 // Iterate until we find a block with at least one bit set.
666 // Read the current block; test to see if there are any set bits.
668 if (ATH_UNLIKELY (cache != 0)) {
669 // Found a block with at least one bit set. Which is the first?
670 int b = CxxUtils::count_trailing_zeros (cache);
672 // Adjust the current position.
676 // Shift the bit found off of cache.
677 // Need to do it in two steps, because @c b might be @c BLOCKSIZE-1
678 // (and shifting by @c BLOCKSIZE is undefined).
679 m_cache = (cache>>1) >> b;
683 // Move to the next block.
688 // Reached the end without finding any more bits set.
689 // Mark that we hit the end.
690 m_bit = ~static_cast<bit_t>(0);
698 * @brief Advance the iterator to the next set bit (postincrement).
701 ConcurrentBitset::const_iterator
702 ConcurrentBitset::const_iterator::operator++(int)
704 const_iterator tmp = *this;
711 * @brief Compare two iterators.
712 * @param other The other iterator to compare.
715 bool ConcurrentBitset::const_iterator::operator== (const const_iterator& other) const
717 return m_bit == other.m_bit;
722 * @brief Compare two iterators.
723 * @param other The other iterator to compare.
726 bool ConcurrentBitset::const_iterator::operator!= (const const_iterator& other) const
728 return m_bit != other.m_bit;
733 * @brief Return a begin iterator.
736 ConcurrentBitset::const_iterator
737 ConcurrentBitset::begin() const
739 return (*m_impl).begin();
744 * @brief Return an end iterator.
747 ConcurrentBitset::const_iterator
748 ConcurrentBitset::end() const
750 return (*m_impl).end();
755 * @brief If bit @c bit is set, return an iterator pointing to it.
756 * Otherwise, return an end iterator.
757 * @param bit Bit number to test.
760 ConcurrentBitset::const_iterator
761 ConcurrentBitset::find (bit_t bit) const
763 return (*m_impl).find(bit);
767 //*********************************************************************************
772 * @brief Allocate an Impl structure.
773 * @param sz Size of an Impl structure.
774 * @param nbits Number of bits to allocate.
776 * The storage for the bits follows contiguously after the Impl structure itself
777 * (to save a memory reference); hence, we use a custom allocator.
780 void* ConcurrentBitset::Impl::operator new (size_t /*sz*/,
781 ConcurrentBitset::bit_t nbits)
783 bit_t nblocks = nBlocks (nbits);
784 // The Impl structure contains one Block_t at the end.
785 return malloc (sizeof(Impl) + (nblocks-1)*sizeof(Block_t));
790 * @brief Free an Impl structure.
791 * @param p Pointer to the object to free.
794 void ConcurrentBitset::Impl::operator delete (void* p)
801 * @brief Find number of blocks needed to hold a given number of bits.
802 * @param nbits The number of bits.
805 ConcurrentBitset::bit_t
806 ConcurrentBitset::nBlocks (bit_t nbits)
808 if (nbits == 0) return 1;
809 return (nbits+BLOCKSIZE-1) / BLOCKSIZE;
814 * @brief Create a new, uninitialized implementation object.
815 * @param nbits Number of bits to allocate.
817 * This will allocate memory for the Impl object,
818 * but will does not run the constructor.
821 ConcurrentBitset::Impl*
822 ConcurrentBitset::newImpl (bit_t nbits)
824 bit_t nblocks = nBlocks (nbits);
825 // The Impl structure contains one Block_t at the end.
826 return reinterpret_cast<Impl*>(malloc (sizeof(Impl) + (nblocks-1)*sizeof(Block_t)));
831 * @brief Expand the container.
832 * @param new_nbits The desired new size of the container.
835 void ConcurrentBitset::expand (bit_t new_nbits)
837 // Check the size of the container.
838 // Call the out-of-line portion if we actually need to expand.
839 if (new_nbits > (*m_impl).nbits()) {
840 expandOol (new_nbits);
846 * @brief Constructor.
847 * @param nbits Number of bits in the set.
850 ConcurrentBitset::Impl::Impl (bit_t nbits)
852 m_nblocks (nBlocks (nbits))
854 // Start with all bits 0.
860 * @brief Copy constructor.
861 * @brief Other object to copy.
862 * @brief Number of bits to use for this container.
864 * If @c nbits is smaller than the size of @c other, then the size of @c other
865 * will be used instead.
868 ConcurrentBitset::Impl::Impl (const Impl& other, bit_t nbits /*= 0*/)
869 : m_nbits (std::max (other.m_nbits, nbits)),
870 m_nblocks ((m_nbits+BLOCKSIZE-1) / BLOCKSIZE),
871 m_hwm (static_cast<size_t> (other.m_hwm))
873 // Copy, then clear the remainder.
874 // We don't care about the relative ordering, so to this with relaxed
875 // memory ordering, and add a barrier at the end.
876 for (bit_t i=0; i < other.m_nblocks; i++) {
877 m_data[i].store (other.m_data[i].load(std::memory_order_relaxed),
878 std::memory_order_relaxed);
880 for (bit_t i=other.m_nblocks; i < m_nblocks; i++) {
881 m_data[i].store (0, std::memory_order_relaxed);
883 std::atomic_thread_fence (std::memory_order_seq_cst);
888 * @brief Copy from another instance.
889 * @param other Object from which to copy.
891 * This does not change the size of the container.
892 * If This container is larger than @c other, then the remainder will be
893 * filled with zeros. If @c other is larger than this container, then the
894 * remainder will be ignored.
897 void ConcurrentBitset::Impl::assign (const Impl& other)
899 // Copy, then clear the remainder.
900 // We don't care about the relative ordering, so do this with relaxed
901 // memory ordering, and add a barrier at the end.
902 bit_t ncopy = std::min (m_nblocks, other.m_nblocks);
903 for (bit_t i=0; i < ncopy; i++) {
904 m_data[i].store (other.m_data[i].load(std::memory_order_relaxed),
905 std::memory_order_relaxed);
907 for (bit_t i=ncopy; i < m_nblocks; i++) {
908 m_data[i].store (0, std::memory_order_relaxed);
910 std::atomic_thread_fence (std::memory_order_seq_cst);
912 // Copy hwm last. It can only increase, so as long as we're after the barrier,
913 // we should get something that's correct.
914 m_hwm = static_cast<bit_t> (other.m_hwm);
919 * @brief Return the number of bits in the set.
922 ConcurrentBitset::bit_t ConcurrentBitset::Impl::nbits() const
929 * @brief Test to see if a bit is set.
930 * @param bit Number of the bit to test.
931 * @return true if the bit is set; false otherwise.
933 * Returns false if @c bit is beyond the end of the set.
936 bool ConcurrentBitset::Impl::test (bit_t bit) const
938 if (bit >= m_nbits) return false;
939 bit_t pos = bit / BLOCKSIZE;
940 return (m_data[pos] & (1UL<<(bit&MASK))) != 0;
945 * @brief Return a pointer to the block containing @c bit.
946 * @param bit Desired bit number.
948 * Returns nullptr if @c bit is past the end of the set.
951 std::atomic<ConcurrentBitset::Block_t>*
952 ConcurrentBitset::Impl::block (bit_t bit)
954 if (bit >= m_nbits) return nullptr;
955 bit_t pos = bit / BLOCKSIZE;
961 * @brief Turn on one bit.
962 * @param bit The bit to turn on.
964 * Does nothing if @c bit beyond the end of the set.
967 void ConcurrentBitset::Impl::set (bit_t bit)
969 if (bit >= m_nbits) return;
970 bit_t pos = bit / BLOCKSIZE;
971 // Update HWM if pos is larger.
972 CxxUtils::atomic_fetch_max (&m_hwm, pos);
973 m_data[pos] |= 1UL<<(bit&MASK);
978 * @brief Turn off one bit.
979 * @param bit The bit to turn off.
981 * Does nothing if @c bit beyond the end of the set.
984 void ConcurrentBitset::Impl::reset (bit_t bit)
986 if (bit >= m_nbits) return;
987 bit_t pos = bit / BLOCKSIZE;
988 m_data[pos] &= ~(1UL<<(bit&MASK));
993 * @brief Flip the value of one bit.
994 * @param bit The bit to turn flip.
996 * Does nothing if @c bit beyond the end of the set.
999 void ConcurrentBitset::Impl::flip (bit_t bit)
1001 if (bit >= m_nbits) return;
1002 size_t pos = bit / BLOCKSIZE;
1003 // Update HWM if pos is larger.
1004 CxxUtils::atomic_fetch_max (&m_hwm, pos);
1005 m_data[pos] ^= 1UL<<(bit&MASK);
1010 * @brief Clear all bits in the set.
1012 * This operation is not necessarily atomic; a simultaneous read may be able
1013 * to see the operation partially done.
1016 void ConcurrentBitset::Impl::clear()
1018 for (bit_t i=0; i<m_nblocks; i++) {
1019 m_data[i].store (0, std::memory_order_relaxed);
1021 std::atomic_thread_fence (std::memory_order_seq_cst);
1027 * @brief Apply a binary operation.
1028 * @param op Operation to apply.
1029 * @param other Second set for the operation.
1031 * Each block B in this set is replaced by B OP OTHER, where OTHER is
1032 * the corresponding block in the other container. (If this set
1033 * is larger than @c other, then the trailing blocks will be 0.)
1036 void ConcurrentBitset::Impl::operate (operator_t op, const Impl& other)
1038 bit_t nblocks = std::min (m_nblocks, other.m_nblocks);
1040 for (bit_t i = 0; i < nblocks; i++) {
1041 op (m_data[i], other.m_data[i]);
1042 if (m_data[i]) hwm = i;
1044 for (bit_t i = nblocks; i < m_nblocks; i++) {
1045 op (m_data[i], static_cast<Block_t> (0));
1046 if (m_data[i]) hwm = i;
1048 CxxUtils::atomic_fetch_max (&m_hwm, hwm);
1053 * @brief Compare with another set.
1054 * @param other Other set with which to compare.
1057 bool ConcurrentBitset::Impl::operator== (const Impl& other) const
1059 bit_t ntest = std::min (m_nblocks, other.m_nblocks);
1060 for (bit_t i = 0; i < ntest; i++) {
1061 if (m_data[i] != other.m_data[i]) return false;
1064 for (bit_t i = ntest; i < m_nblocks; i++) {
1065 if (m_data[i] != 0) return false;
1068 for (bit_t i = ntest; i < other.m_nblocks; i++) {
1069 if (other.m_data[i] != 0) return false;
1077 * @brief Return an iterator referencing the first 1 bit.
1080 ConcurrentBitset::const_iterator
1081 ConcurrentBitset::Impl::begin() const
1083 // Set the iterator to just before the start of the container.
1084 // Then use the increment operator to search for the first 1.
1085 bit_t offs = m_nblocks ? (static_cast<bit_t>(m_hwm) + 1) : 0;
1086 const_iterator it (0,
1087 static_cast<bit_t>(-BLOCKSIZE),
1096 * @brief Return the end iterator.
1099 ConcurrentBitset::const_iterator
1100 ConcurrentBitset::Impl::end() const
1102 return const_iterator (0,
1103 ~static_cast<bit_t>(0),
1109 * @brief If bit @c bit is set, return an iterator pointing to it.
1110 * Otherwise, return an end iterator.
1111 * @param bit Bit number to test.
1114 ConcurrentBitset::const_iterator
1115 ConcurrentBitset::Impl::find (bit_t bit) const
1119 // Construct an iterator pointing at this bit.
1120 bit_t pos = bit / BLOCKSIZE;
1121 const std::atomic<Block_t>* data = m_data + pos;
1122 Block_t cache = *data;
1123 bit_t offs = bit&MASK;
1128 return const_iterator (cache, bit, m_data+pos,
1129 &m_data[0] + m_hwm + 1);
1135 } // namespace CxxUtils