2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
5 * @file CxxUtils/ConcurrentBitset.icc
6 * @author scott snyder <snyder@bnl.gov>
8 * @brief Variable-sized bitset allowing (mostly) concurrent access.
15//*********************************************************************************
16// Constructors, destructors, assignment.
20 * @brief The number of bits that this container can hold.
23ConcurrentBitset::bit_t ConcurrentBitset::capacity() const
25 return (*m_impl).nbits();
30 * @brief Count the number of 1 bits in the set.
33ConcurrentBitset::bit_t ConcurrentBitset::count() const
35 return (*m_impl).count();
40 * @brief Count the number of 1 bits in the set.
42 * Note: If you regard this like a std::bitset, you would expect this to return
43 * the number of bits that the set can hold, while if you regard this like
44 * a set<bit_t>, then you would expect this to return the number of 1 bits.
45 * We follow the latter here.
48ConcurrentBitset::bit_t ConcurrentBitset::size() const
50 return (*m_impl).count();
55 * @brief Test to see if a bit is set.
56 * @param bit Number of the bit to test.
57 * @return true if the bit is set; false otherwise.
59 * Returns false if @c bit is beyond the end of the set.
62bool ConcurrentBitset::test (bit_t bit) const
64 return (*m_impl).test (bit);
68 * @brief Test to see if a bit is set.
69 * @param bit Number of the bit to test.
70 * @return 1 if the bit is set; 0 otherwise.
72 * Returns 0 if @c bit is beyond the end of the set.
75size_t ConcurrentBitset::count (bit_t bit) const
82 * @brief Return true if there are no 1 bits in the set.
85bool ConcurrentBitset::empty() const
92 * @brief Return true if there are no 1 bits in the set.
95bool ConcurrentBitset::none() const
97 return (*m_impl).none();
102 * @brief Return true if all bits in the set are 1.
105bool ConcurrentBitset::all() const
107 return (*m_impl).all();
112 * @brief Return true if there are any 1 bits in the set.
115bool ConcurrentBitset::any() const
121//*********************************************************************************
122// Single-bit manipulation.
126 * @brief Turn on one bit.
127 * @param bit The bit to turn on.
129 * Does nothing if @c bit beyond the end of the set.
132ConcurrentBitset& ConcurrentBitset::set (bit_t bit)
140 * @brief Turn off one bit.
141 * @param bit The bit to turn off.
143 * Does nothing if @c bit beyond the end of the set.
146ConcurrentBitset& ConcurrentBitset::reset (bit_t bit)
148 (*m_impl).reset (bit);
154 * @brief Turn off one bit.
155 * @param bit The bit to turn off.
157 * Does nothing if @c bit beyond the end of the set.
160ConcurrentBitset& ConcurrentBitset::erase (bit_t bit)
162 (*m_impl).reset (bit);
168 * @brief Flip the value of one bit.
169 * @param bit The bit to turn flip.
171 * Does nothing if @c bit beyond the end of the set.
174ConcurrentBitset& ConcurrentBitset::flip (bit_t bit)
176 (*m_impl).flip (bit);
182 * @brief Set the value of one bit.
183 * @param bit The bit to turn set.
184 * @param val The value to which to set it.
186 * Does nothing if @c bit beyond the end of the set.
189ConcurrentBitset& ConcurrentBitset::set (bit_t bit, bool val)
195 (*m_impl).reset (bit);
201//*********************************************************************************
206 * @brief Clear all bits in the set.
208 * This operation is not necessarily atomic; a simultaneous read may be able
209 * to see the operation partially done.
212ConcurrentBitset& ConcurrentBitset::clear()
220 * @brief Clear all bits in the set.
222 * This operation is not necessarily atomic; a simultaneous read may be able
223 * to see the operation partially done.
226ConcurrentBitset& ConcurrentBitset::reset()
234 * @brief Turn on all bits in the set.
236 * This operation is not necessarily atomic; a simultaneous read may be able
237 * to see the operation partially done.
240ConcurrentBitset& ConcurrentBitset::set()
247 * @brief Flip the state of all bits in the set.
249 * This operation is not necessarily atomic; a simultaneous read may be able
250 * to see the operation partially done.
253ConcurrentBitset& ConcurrentBitset::flip()
261 * @brief AND this set with another set.
262 * @param other The other set.
264 * This operation is not necessarily atomic; a simultaneous read may be able
265 * to see the operation partially done.
268ConcurrentBitset& ConcurrentBitset::operator&= (const ConcurrentBitset& other)
270 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a &= v; },
277 * @brief OR this set with another set.
278 * @param other The other set.
280 * This operation is not necessarily atomic; a simultaneous read may be able
281 * to see the operation partially done.
284ConcurrentBitset& ConcurrentBitset::operator|= (const ConcurrentBitset& other)
286 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a |= v; },
293 * @brief XOR this set with another set.
294 * @param other The other set.
296 * This operation is not necessarily atomic; a simultaneous read may be able
297 * to see the operation partially done.
300ConcurrentBitset& ConcurrentBitset::operator^= (const ConcurrentBitset& other)
302 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a ^= v; },
309 * @brief Subtract another set from this set.
310 * @param other The other set.
312 * This is the same as (*this) &= ~other;
314 * This operation is not necessarily atomic; a simultaneous read may be able
315 * to see the operation partially done.
318ConcurrentBitset& ConcurrentBitset::operator-= (const ConcurrentBitset& other)
320 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a &= ~v; },
327 * @brief Return a new set that is the complement of this set.
330ConcurrentBitset ConcurrentBitset::operator~() const
332 ConcurrentBitset b = *this;
338//*********************************************************************************
343 * @brief Test two sets for equality.
344 * @param other The other set to test.
347bool ConcurrentBitset::operator== (const ConcurrentBitset& other) const
349 return (*m_impl) == (*other.m_impl);
354 * @brief Test two sets for inequality.
355 * @param other The other set to test.
358bool ConcurrentBitset::operator!= (const ConcurrentBitset& other) const
360 return ! ((*m_impl) == (*other.m_impl));
364//*********************************************************************************
369 * @brief Set a bit to 1. Expand the set if needed.
370 * @param bit Number of the bit to set.
371 * @param new_nbits Hint for new size of set, if it needs to be expanded.
373 * If @c bit is past the end of the container, then the container will be
374 * expanded as needed.
376 * This operation is incompatible with any other simultaneous writes
377 * to the same set (reads are ok).
380ConcurrentBitset& ConcurrentBitset::insert (bit_t bit, bit_t new_nbits /*= 0*/)
382 if (bit >= (*m_impl).nbits()) {
388 new_nbits = (bit * 2 + BLOCKSIZE-1) & ~(BLOCKSIZE-1);
397 * @brief Set several bits to 1. Expand the set if needed.
398 * @param beg Start of range of bits to set.
399 * @param end End of range of bits to set.
400 * @param new_nbits Hint for new size of set, if it needs to be expanded.
402 * The iteration range should be over something convertible to @c bit_t.
403 * If any bit is past the end of the container, then the container will be
404 * expanded as needed.
406 * This operation is incompatible with any other simultaneous writes
407 * to the same set (reads are ok).
411 * std::vector<bit_t> bits { 4, 10, 12};
412 * CxxUtils::ConcurrentBitset bs = ...;
413 * bs.insert (bits.begin(), bits.end());
416template <class ITERATOR,
417 typename /*= typename std::enable_if<
419 typename std::forward_iterator_tag,
420 typename std::iterator_traits<ITERATOR>::iterator_category
424ConcurrentBitset::insert (ITERATOR beg, ITERATOR end, bit_t new_nbits /*= 0*/)
426 for (; beg != end; ++beg) {
427 insert (*beg, new_nbits);
434 * @brief Set several bits to 1. Expand the set if needed.
435 * @param l List of bits to set.
436 * @param new_nbits Hint for new size of set, if it needs to be expanded.
438 * If any bit is past the end of the container, then the container will be
439 * expanded as needed.
441 * This operation is incompatible with any other simultaneous writes
442 * to the same set (reads are ok).
446 * std::vector<bit_t> bits { 4, 10, 12};
447 * bs.insert ({4, 10, 12});
452ConcurrentBitset::insert (std::initializer_list<bit_t> l, bit_t new_nbits /*= 0*/)
454 auto max_it = std::max_element (l.begin(), l.end());
455 size_t bmax = max_it == l.end() ? 0 : *max_it + 1;
456 if (new_nbits > bmax) new_nbits = bmax;
466 * @brief Turn on bits listed in another set.
467 * @param other Set of bits to turn on.
469 * This is the same as @c operator|=, except that if the size of @c other is
470 * larger than this set, then this set will be expanded to match @c other.
472 * This operation is incompatible with any other simultaneous writes
473 * to the same set (reads are ok).
476ConcurrentBitset& ConcurrentBitset::insert (const ConcurrentBitset& other)
478 const Impl* otherImpl = other.m_impl;
479 expand (otherImpl->nbits());
480 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a |= v; },
486//*********************************************************************************
487// Array-like element access.
491 * @brief Constructor.
492 * @param impl ConcurrentBitset implementation object.
493 * @param bit Bit number to which this reference refers.
496ConcurrentBitset::reference::reference (Impl& impl, bit_t bit)
497 : m_block (impl.block(bit)),
498 m_mask (1UL << (bit&MASK))
504 * @brief Set the referenced bit to a given value.
505 * @param val Value to which to set the referenced bit.
508ConcurrentBitset::reference&
509ConcurrentBitset::reference::operator= (bool val) noexcept
520 * @brief Copy the value of another referenced bit.
521 * @param r The other reference.
525 * ConcurrentBitset& bs1 = ...;
526 * ConcurrentBitset& bs2 = ...;
531ConcurrentBitset::reference&
532ConcurrentBitset::reference::operator= (const reference& r) noexcept
535 *this = static_cast<bool> (r);
542 * @brief Return the value of the referenced bit.
545ConcurrentBitset::reference::operator bool() const noexcept
547 return (*m_block & m_mask) != 0;
552 * @brief Return the complement of the value of the referenced bit.
555bool ConcurrentBitset::reference::operator~() const noexcept
557 return (*m_block & m_mask) == 0;
562 * @brief Invert the referenced bit.
565ConcurrentBitset::reference&
566ConcurrentBitset::reference::flip() noexcept
574 * @brief Return the value of one bit.
575 * @param bit The number of the bit to test.
578bool ConcurrentBitset::operator[] (bit_t bit) const
580 return (*m_impl).test (bit);
585 * @brief Return a reference to one bit.
586 * @param bit The number of the bit to reference.
588 * The reference will be invalidated by calls to @c insert() or @c operator=.
589 * Effects are undefined if @c bit is past the end of the set.
592ConcurrentBitset::reference ConcurrentBitset::operator[] (bit_t bit)
594 return reference (*m_impl, bit);
598//*********************************************************************************
599// Iterator operations.
603 * @brief Constructor.
604 * @param cache Cached block at the current iteration point, shifted such
605 * that bit number @c bit has just been shifted off the right.
606 * @param bit Bit number the at which the iterator is currently pointing.
607 * @param data Pointer to the block at which the iterator is currently pointing.
608 * @param end One past the last block of the iteration range.
611ConcurrentBitset::const_iterator::const_iterator (Block_t cache,
613 const std::atomic<Block_t>* data,
614 const std::atomic<Block_t>* end)
615 : m_cache (cache), m_bit (bit), m_data(data), m_end (end)
621 * @brief Return the bit number which the iterator is currently referencing.
624ConcurrentBitset::bit_t
625ConcurrentBitset::const_iterator::operator*() const
632 * @brief Advance the iterator to the next set bit.
635ConcurrentBitset::const_iterator&
636ConcurrentBitset::const_iterator::operator++()
638 // Are there any more bits set in this block?
639 Block_t cache = m_cache;
640 if (cache != 0) [[likely]] {
641 // Yes --- find the first set bit.
642 // Shift until that bit just falls off the right and adjust
643 // the current position.
644 // We know that @c b will be less than @c BLOCKSIZE
645 // (avoiding undefined behavior), since at least one bit must have
646 // already been shifted off of the cache.
647 int b = std::countr_zero (cache)+1;
649 m_cache = cache >> b;
652 // No, move to the next block.
653 // Bit number at the start of the next block.
654 unsigned int bit = (m_bit + BLOCKSIZE) & ~MASK;
656 // Pointers to the next block, and the end of iteration.
657 const std::atomic<Block_t>* data = m_data + 1;
658 const std::atomic<Block_t>* end = m_end;
660 // Iterate until we find a block with at least one bit set.
662 // Read the current block; test to see if there are any set bits.
664 if (cache != 0) [[unlikely]] {
665 // Found a block with at least one bit set. Which is the first?
666 int b = std::countr_zero (cache);
668 // Adjust the current position.
672 // Shift the bit found off of cache.
673 // Need to do it in two steps, because @c b might be @c BLOCKSIZE-1
674 // (and shifting by @c BLOCKSIZE is undefined).
675 m_cache = (cache>>1) >> b;
679 // Move to the next block.
684 // Reached the end without finding any more bits set.
685 // Mark that we hit the end.
686 m_bit = ~static_cast<bit_t>(0);
694 * @brief Advance the iterator to the next set bit (postincrement).
697ConcurrentBitset::const_iterator
698ConcurrentBitset::const_iterator::operator++(int)
700 const_iterator tmp = *this;
707 * @brief Compare two iterators.
708 * @param other The other iterator to compare.
711bool ConcurrentBitset::const_iterator::operator== (const const_iterator& other) const
713 return m_bit == other.m_bit;
718 * @brief Compare two iterators.
719 * @param other The other iterator to compare.
722bool ConcurrentBitset::const_iterator::operator!= (const const_iterator& other) const
724 return m_bit != other.m_bit;
729 * @brief Return a begin iterator.
732ConcurrentBitset::const_iterator
733ConcurrentBitset::begin() const
735 return (*m_impl).begin();
740 * @brief Return an end iterator.
743ConcurrentBitset::const_iterator
744ConcurrentBitset::end() const
746 return (*m_impl).end();
751 * @brief If bit @c bit is set, return an iterator pointing to it.
752 * Otherwise, return an end iterator.
753 * @param bit Bit number to test.
756ConcurrentBitset::const_iterator
757ConcurrentBitset::find (bit_t bit) const
759 return (*m_impl).find(bit);
763//*********************************************************************************
768 * @brief Allocate an Impl structure.
769 * @param sz Size of an Impl structure.
770 * @param nbits Number of bits to allocate.
772 * The storage for the bits follows contiguously after the Impl structure itself
773 * (to save a memory reference); hence, we use a custom allocator.
776void* ConcurrentBitset::Impl::operator new (size_t /*sz*/,
777 ConcurrentBitset::bit_t nbits)
779 bit_t nblocks = nBlocks (nbits);
780 // The Impl structure contains one Block_t at the end.
781 return malloc (sizeof(Impl) + (nblocks-1)*sizeof(Block_t));
786 * @brief Free an Impl structure.
787 * @param p Pointer to the object to free.
790void ConcurrentBitset::Impl::operator delete (void* p)
797 * @brief Find number of blocks needed to hold a given number of bits.
798 * @param nbits The number of bits.
801ConcurrentBitset::bit_t
802ConcurrentBitset::nBlocks (bit_t nbits)
804 if (nbits == 0) return 1;
805 return (nbits+BLOCKSIZE-1) / BLOCKSIZE;
810 * @brief Create a new, uninitialized implementation object.
811 * @param nbits Number of bits to allocate.
813 * This will allocate memory for the Impl object,
814 * but will does not run the constructor.
817ConcurrentBitset::Impl*
818ConcurrentBitset::newImpl (bit_t nbits)
820 bit_t nblocks = nBlocks (nbits);
821 // The Impl structure contains one Block_t at the end.
822 return reinterpret_cast<Impl*>(malloc (sizeof(Impl) + (nblocks-1)*sizeof(Block_t)));
827 * @brief Expand the container.
828 * @param new_nbits The desired new size of the container.
831void ConcurrentBitset::expand (bit_t new_nbits)
833 // Check the size of the container.
834 // Call the out-of-line portion if we actually need to expand.
835 if (new_nbits > (*m_impl).nbits()) {
836 expandOol (new_nbits);
842 * @brief Constructor.
843 * @param nbits Number of bits in the set.
846ConcurrentBitset::Impl::Impl (bit_t nbits)
848 m_nblocks (nBlocks (nbits))
850 // Start with all bits 0.
856 * @brief Copy constructor.
857 * @brief Other object to copy.
858 * @brief Number of bits to use for this container.
860 * If @c nbits is smaller than the size of @c other, then the size of @c other
861 * will be used instead.
864ConcurrentBitset::Impl::Impl (const Impl& other, bit_t nbits /*= 0*/)
865 : m_nbits (std::max (other.m_nbits, nbits)),
866 m_nblocks ((m_nbits+BLOCKSIZE-1) / BLOCKSIZE),
867 m_hwm (static_cast<size_t> (other.m_hwm))
869 // Copy, then clear the remainder.
870 // We don't care about the relative ordering, so to this with relaxed
871 // memory ordering, and add a barrier at the end.
872 for (bit_t i=0; i < other.m_nblocks; i++) {
873 m_data[i].store (other.m_data[i].load(std::memory_order_relaxed),
874 std::memory_order_relaxed);
876 for (bit_t i=other.m_nblocks; i < m_nblocks; i++) {
877 m_data[i].store (0, std::memory_order_relaxed);
879 std::atomic_thread_fence (std::memory_order_seq_cst);
884 * @brief Copy from another instance.
885 * @param other Object from which to copy.
887 * This does not change the size of the container.
888 * If This container is larger than @c other, then the remainder will be
889 * filled with zeros. If @c other is larger than this container, then the
890 * remainder will be ignored.
893void ConcurrentBitset::Impl::assign (const Impl& other)
895 // Copy, then clear the remainder.
896 // We don't care about the relative ordering, so do this with relaxed
897 // memory ordering, and add a barrier at the end.
898 bit_t ncopy = std::min (m_nblocks, other.m_nblocks);
899 for (bit_t i=0; i < ncopy; i++) {
900 m_data[i].store (other.m_data[i].load(std::memory_order_relaxed),
901 std::memory_order_relaxed);
903 for (bit_t i=ncopy; i < m_nblocks; i++) {
904 m_data[i].store (0, std::memory_order_relaxed);
906 std::atomic_thread_fence (std::memory_order_seq_cst);
908 // Copy hwm last. It can only increase, so as long as we're after the barrier,
909 // we should get something that's correct.
910 m_hwm = static_cast<bit_t> (other.m_hwm);
915 * @brief Return the number of bits in the set.
918ConcurrentBitset::bit_t ConcurrentBitset::Impl::nbits() const
925 * @brief Test to see if a bit is set.
926 * @param bit Number of the bit to test.
927 * @return true if the bit is set; false otherwise.
929 * Returns false if @c bit is beyond the end of the set.
932bool ConcurrentBitset::Impl::test (bit_t bit) const
934 if (bit >= m_nbits) return false;
935 bit_t pos = bit / BLOCKSIZE;
936 return (m_data[pos] & (1UL<<(bit&MASK))) != 0;
941 * @brief Return a pointer to the block containing @c bit.
942 * @param bit Desired bit number.
944 * Returns nullptr if @c bit is past the end of the set.
947std::atomic<ConcurrentBitset::Block_t>*
948ConcurrentBitset::Impl::block (bit_t bit)
950 if (bit >= m_nbits) return nullptr;
951 bit_t pos = bit / BLOCKSIZE;
957 * @brief Turn on one bit.
958 * @param bit The bit to turn on.
960 * Does nothing if @c bit beyond the end of the set.
963void ConcurrentBitset::Impl::set (bit_t bit)
965 if (bit >= m_nbits) return;
966 bit_t pos = bit / BLOCKSIZE;
967 // Update HWM if pos is larger.
968 CxxUtils::atomic_fetch_max (&m_hwm, pos);
969 m_data[pos] |= 1UL<<(bit&MASK);
974 * @brief Turn off one bit.
975 * @param bit The bit to turn off.
977 * Does nothing if @c bit beyond the end of the set.
980void ConcurrentBitset::Impl::reset (bit_t bit)
982 if (bit >= m_nbits) return;
983 bit_t pos = bit / BLOCKSIZE;
984 m_data[pos] &= ~(1UL<<(bit&MASK));
989 * @brief Flip the value of one bit.
990 * @param bit The bit to turn flip.
992 * Does nothing if @c bit beyond the end of the set.
995void ConcurrentBitset::Impl::flip (bit_t bit)
997 if (bit >= m_nbits) return;
998 size_t pos = bit / BLOCKSIZE;
999 // Update HWM if pos is larger.
1000 CxxUtils::atomic_fetch_max (&m_hwm, pos);
1001 m_data[pos] ^= 1UL<<(bit&MASK);
1006 * @brief Clear all bits in the set.
1008 * This operation is not necessarily atomic; a simultaneous read may be able
1009 * to see the operation partially done.
1012void ConcurrentBitset::Impl::clear()
1014 for (bit_t i=0; i<m_nblocks; i++) {
1015 m_data[i].store (0, std::memory_order_relaxed);
1017 std::atomic_thread_fence (std::memory_order_seq_cst);
1023 * @brief Apply a binary operation.
1024 * @param op Operation to apply.
1025 * @param other Second set for the operation.
1027 * Each block B in this set is replaced by B OP OTHER, where OTHER is
1028 * the corresponding block in the other container. (If this set
1029 * is larger than @c other, then the trailing blocks will be 0.)
1032void ConcurrentBitset::Impl::operate (operator_t op, const Impl& other)
1034 bit_t nblocks = std::min (m_nblocks, other.m_nblocks);
1036 for (bit_t i = 0; i < nblocks; i++) {
1037 op (m_data[i], other.m_data[i]);
1038 if (m_data[i]) hwm = i;
1040 for (bit_t i = nblocks; i < m_nblocks; i++) {
1041 op (m_data[i], static_cast<Block_t> (0));
1042 if (m_data[i]) hwm = i;
1044 CxxUtils::atomic_fetch_max (&m_hwm, hwm);
1049 * @brief Compare with another set.
1050 * @param other Other set with which to compare.
1053bool ConcurrentBitset::Impl::operator== (const Impl& other) const
1055 bit_t ntest = std::min (m_nblocks, other.m_nblocks);
1056 for (bit_t i = 0; i < ntest; i++) {
1057 if (m_data[i] != other.m_data[i]) return false;
1060 for (bit_t i = ntest; i < m_nblocks; i++) {
1061 if (m_data[i] != 0) return false;
1064 for (bit_t i = ntest; i < other.m_nblocks; i++) {
1065 if (other.m_data[i] != 0) return false;
1073 * @brief Return an iterator referencing the first 1 bit.
1076ConcurrentBitset::const_iterator
1077ConcurrentBitset::Impl::begin() const
1079 // Set the iterator to just before the start of the container.
1080 // Then use the increment operator to search for the first 1.
1081 bit_t offs = m_nblocks ? (static_cast<bit_t>(m_hwm) + 1) : 0;
1082 const_iterator it (0,
1083 static_cast<bit_t>(-BLOCKSIZE),
1092 * @brief Return the end iterator.
1095ConcurrentBitset::const_iterator
1096ConcurrentBitset::Impl::end() const
1098 return const_iterator (0,
1099 ~static_cast<bit_t>(0),
1105 * @brief If bit @c bit is set, return an iterator pointing to it.
1106 * Otherwise, return an end iterator.
1107 * @param bit Bit number to test.
1110ConcurrentBitset::const_iterator
1111ConcurrentBitset::Impl::find (bit_t bit) const
1115 // Construct an iterator pointing at this bit.
1116 bit_t pos = bit / BLOCKSIZE;
1117 const std::atomic<Block_t>* data = m_data + pos;
1118 Block_t cache = *data;
1119 bit_t offs = bit&MASK;
1124 return const_iterator (cache, bit, m_data+pos,
1125 &m_data[0] + m_hwm + 1);
1131} // namespace CxxUtils