2 Copyright (C) 2002-2025 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.
12 #include "CxxUtils/AthUnlikelyMacros.h"
18 //*********************************************************************************
19 // Constructors, destructors, assignment.
23 * @brief The number of bits that this container can hold.
26 ConcurrentBitset::bit_t ConcurrentBitset::capacity() const
28 return (*m_impl).nbits();
33 * @brief Count the number of 1 bits in the set.
36 ConcurrentBitset::bit_t ConcurrentBitset::count() const
38 return (*m_impl).count();
43 * @brief Count the number of 1 bits in the set.
45 * Note: If you regard this like a std::bitset, you would expect this to return
46 * the number of bits that the set can hold, while if you regard this like
47 * a set<bit_t>, then you would expect this to return the number of 1 bits.
48 * We follow the latter here.
51 ConcurrentBitset::bit_t ConcurrentBitset::size() const
53 return (*m_impl).count();
58 * @brief Test to see if a bit is set.
59 * @param bit Number of the bit to test.
60 * @return true if the bit is set; false otherwise.
62 * Returns false if @c bit is beyond the end of the set.
65 bool ConcurrentBitset::test (bit_t bit) const
67 return (*m_impl).test (bit);
71 * @brief Test to see if a bit is set.
72 * @param bit Number of the bit to test.
73 * @return 1 if the bit is set; 0 otherwise.
75 * Returns 0 if @c bit is beyond the end of the set.
78 size_t ConcurrentBitset::count (bit_t bit) const
85 * @brief Return true if there are no 1 bits in the set.
88 bool ConcurrentBitset::empty() const
95 * @brief Return true if there are no 1 bits in the set.
98 bool ConcurrentBitset::none() const
100 return (*m_impl).none();
105 * @brief Return true if all bits in the set are 1.
108 bool ConcurrentBitset::all() const
110 return (*m_impl).all();
115 * @brief Return true if there are any 1 bits in the set.
118 bool ConcurrentBitset::any() const
124 //*********************************************************************************
125 // Single-bit manipulation.
129 * @brief Turn on one bit.
130 * @param bit The bit to turn on.
132 * Does nothing if @c bit beyond the end of the set.
135 ConcurrentBitset& ConcurrentBitset::set (bit_t bit)
143 * @brief Turn off one bit.
144 * @param bit The bit to turn off.
146 * Does nothing if @c bit beyond the end of the set.
149 ConcurrentBitset& ConcurrentBitset::reset (bit_t bit)
151 (*m_impl).reset (bit);
157 * @brief Turn off one bit.
158 * @param bit The bit to turn off.
160 * Does nothing if @c bit beyond the end of the set.
163 ConcurrentBitset& ConcurrentBitset::erase (bit_t bit)
165 (*m_impl).reset (bit);
171 * @brief Flip the value of one bit.
172 * @param bit The bit to turn flip.
174 * Does nothing if @c bit beyond the end of the set.
177 ConcurrentBitset& ConcurrentBitset::flip (bit_t bit)
179 (*m_impl).flip (bit);
185 * @brief Set the value of one bit.
186 * @param bit The bit to turn set.
187 * @param val The value to which to set it.
189 * Does nothing if @c bit beyond the end of the set.
192 ConcurrentBitset& ConcurrentBitset::set (bit_t bit, bool val)
198 (*m_impl).reset (bit);
204 //*********************************************************************************
209 * @brief Clear all bits in the set.
211 * This operation is not necessarily atomic; a simultaneous read may be able
212 * to see the operation partially done.
215 ConcurrentBitset& ConcurrentBitset::clear()
223 * @brief Clear all bits in the set.
225 * This operation is not necessarily atomic; a simultaneous read may be able
226 * to see the operation partially done.
229 ConcurrentBitset& ConcurrentBitset::reset()
237 * @brief Turn on all bits in the set.
239 * This operation is not necessarily atomic; a simultaneous read may be able
240 * to see the operation partially done.
243 ConcurrentBitset& ConcurrentBitset::set()
250 * @brief Flip the state of all bits in the set.
252 * This operation is not necessarily atomic; a simultaneous read may be able
253 * to see the operation partially done.
256 ConcurrentBitset& ConcurrentBitset::flip()
264 * @brief AND this set with another set.
265 * @param other The other set.
267 * This operation is not necessarily atomic; a simultaneous read may be able
268 * to see the operation partially done.
271 ConcurrentBitset& ConcurrentBitset::operator&= (const ConcurrentBitset& other)
273 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a &= v; },
280 * @brief OR this set with another set.
281 * @param other The other set.
283 * This operation is not necessarily atomic; a simultaneous read may be able
284 * to see the operation partially done.
287 ConcurrentBitset& ConcurrentBitset::operator|= (const ConcurrentBitset& other)
289 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a |= v; },
296 * @brief XOR this set with another set.
297 * @param other The other set.
299 * This operation is not necessarily atomic; a simultaneous read may be able
300 * to see the operation partially done.
303 ConcurrentBitset& ConcurrentBitset::operator^= (const ConcurrentBitset& other)
305 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a ^= v; },
312 * @brief Subtract another set from this set.
313 * @param other The other set.
315 * This is the same as (*this) &= ~other;
317 * This operation is not necessarily atomic; a simultaneous read may be able
318 * to see the operation partially done.
321 ConcurrentBitset& ConcurrentBitset::operator-= (const ConcurrentBitset& other)
323 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a &= ~v; },
330 * @brief Return a new set that is the complement of this set.
333 ConcurrentBitset ConcurrentBitset::operator~() const
335 ConcurrentBitset b = *this;
341 //*********************************************************************************
346 * @brief Test two sets for equality.
347 * @param other The other set to test.
350 bool ConcurrentBitset::operator== (const ConcurrentBitset& other) const
352 return (*m_impl) == (*other.m_impl);
357 * @brief Test two sets for inequality.
358 * @param other The other set to test.
361 bool ConcurrentBitset::operator!= (const ConcurrentBitset& other) const
363 return ! ((*m_impl) == (*other.m_impl));
367 //*********************************************************************************
372 * @brief Set a bit to 1. Expand the set if needed.
373 * @param bit Number of the bit to set.
374 * @param new_nbits Hint for new size of set, if it needs to be expanded.
376 * If @c bit is past the end of the container, then the container will be
377 * expanded as needed.
379 * This operation is incompatible with any other simultaneous writes
380 * to the same set (reads are ok).
383 ConcurrentBitset& ConcurrentBitset::insert (bit_t bit, bit_t new_nbits /*= 0*/)
385 if (bit >= (*m_impl).nbits()) {
391 new_nbits = (bit * 2 + BLOCKSIZE-1) & ~(BLOCKSIZE-1);
400 * @brief Set several bits to 1. Expand the set if needed.
401 * @param beg Start of range of bits to set.
402 * @param end End of range of bits to set.
403 * @param new_nbits Hint for new size of set, if it needs to be expanded.
405 * The iteration range should be over something convertible to @c bit_t.
406 * If any bit is past the end of the container, then the container will be
407 * expanded as needed.
409 * This operation is incompatible with any other simultaneous writes
410 * to the same set (reads are ok).
414 * std::vector<bit_t> bits { 4, 10, 12};
415 * CxxUtils::ConcurrentBitset bs = ...;
416 * bs.insert (bits.begin(), bits.end());
419 template <class ITERATOR,
420 typename /*= typename std::enable_if<
422 typename std::forward_iterator_tag,
423 typename std::iterator_traits<ITERATOR>::iterator_category
427 ConcurrentBitset::insert (ITERATOR beg, ITERATOR end, bit_t new_nbits /*= 0*/)
429 for (; beg != end; ++beg) {
430 insert (*beg, new_nbits);
437 * @brief Set several bits to 1. Expand the set if needed.
438 * @param l List of bits to set.
439 * @param new_nbits Hint for new size of set, if it needs to be expanded.
441 * If any bit is past the end of the container, then the container will be
442 * expanded as needed.
444 * This operation is incompatible with any other simultaneous writes
445 * to the same set (reads are ok).
449 * std::vector<bit_t> bits { 4, 10, 12};
450 * bs.insert ({4, 10, 12});
455 ConcurrentBitset::insert (std::initializer_list<bit_t> l, bit_t new_nbits /*= 0*/)
457 auto max_it = std::max_element (l.begin(), l.end());
458 size_t bmax = max_it == l.end() ? 0 : *max_it + 1;
459 if (new_nbits > bmax) new_nbits = bmax;
469 * @brief Turn on bits listed in another set.
470 * @param other Set of bits to turn on.
472 * This is the same as @c operator|=, except that if the size of @c other is
473 * larger than this set, then this set will be expanded to match @c other.
475 * This operation is incompatible with any other simultaneous writes
476 * to the same set (reads are ok).
479 ConcurrentBitset& ConcurrentBitset::insert (const ConcurrentBitset& other)
481 const Impl* otherImpl = other.m_impl;
482 expand (otherImpl->nbits());
483 (*m_impl).operate ([] (std::atomic<Block_t>& a, Block_t v) { a |= v; },
489 //*********************************************************************************
490 // Array-like element access.
494 * @brief Constructor.
495 * @param impl ConcurrentBitset implementation object.
496 * @param bit Bit number to which this reference refers.
499 ConcurrentBitset::reference::reference (Impl& impl, bit_t bit)
500 : m_block (impl.block(bit)),
501 m_mask (1UL << (bit&MASK))
507 * @brief Set the referenced bit to a given value.
508 * @param val Value to which to set the referenced bit.
511 ConcurrentBitset::reference&
512 ConcurrentBitset::reference::operator= (bool val) noexcept
523 * @brief Copy the value of another referenced bit.
524 * @param r The other reference.
528 * ConcurrentBitset& bs1 = ...;
529 * ConcurrentBitset& bs2 = ...;
534 ConcurrentBitset::reference&
535 ConcurrentBitset::reference::operator= (const reference& r) noexcept
538 *this = static_cast<bool> (r);
545 * @brief Return the value of the referenced bit.
548 ConcurrentBitset::reference::operator bool() const noexcept
550 return (*m_block & m_mask) != 0;
555 * @brief Return the complement of the value of the referenced bit.
558 bool ConcurrentBitset::reference::operator~() const noexcept
560 return (*m_block & m_mask) == 0;
565 * @brief Invert the referenced bit.
568 ConcurrentBitset::reference&
569 ConcurrentBitset::reference::flip() noexcept
577 * @brief Return the value of one bit.
578 * @param bit The number of the bit to test.
581 bool ConcurrentBitset::operator[] (bit_t bit) const
583 return (*m_impl).test (bit);
588 * @brief Return a reference to one bit.
589 * @param bit The number of the bit to reference.
591 * The reference will be invalidated by calls to @c insert() or @c operator=.
592 * Effects are undefined if @c bit is past the end of the set.
595 ConcurrentBitset::reference ConcurrentBitset::operator[] (bit_t bit)
597 return reference (*m_impl, bit);
601 //*********************************************************************************
602 // Iterator operations.
606 * @brief Constructor.
607 * @param cache Cached block at the current iteration point, shifted such
608 * that bit number @c bit has just been shifted off the right.
609 * @param bit Bit number the at which the iterator is currently pointing.
610 * @param data Pointer to the block at which the iterator is currently pointing.
611 * @param end One past the last block of the iteration range.
614 ConcurrentBitset::const_iterator::const_iterator (Block_t cache,
616 const std::atomic<Block_t>* data,
617 const std::atomic<Block_t>* end)
618 : m_cache (cache), m_bit (bit), m_data(data), m_end (end)
624 * @brief Return the bit number which the iterator is currently referencing.
627 ConcurrentBitset::bit_t
628 ConcurrentBitset::const_iterator::operator*() const
635 * @brief Advance the iterator to the next set bit.
638 ConcurrentBitset::const_iterator&
639 ConcurrentBitset::const_iterator::operator++()
641 // Are there any more bits set in this block?
642 Block_t cache = m_cache;
643 if (ATH_LIKELY (cache != 0)) {
644 // Yes --- find the first set bit.
645 // Shift until that bit just falls off the right and adjust
646 // the current position.
647 // We know that @c b will be less than @c BLOCKSIZE
648 // (avoiding undefined behavior), since at least one bit must have
649 // already been shifted off of the cache.
650 int b = std::countr_zero (cache)+1;
652 m_cache = cache >> b;
655 // No, move to the next block.
656 // Bit number at the start of the next block.
657 unsigned int bit = (m_bit + BLOCKSIZE) & ~MASK;
659 // Pointers to the next block, and the end of iteration.
660 const std::atomic<Block_t>* data = m_data + 1;
661 const std::atomic<Block_t>* end = m_end;
663 // Iterate until we find a block with at least one bit set.
665 // Read the current block; test to see if there are any set bits.
667 if (ATH_UNLIKELY (cache != 0)) {
668 // Found a block with at least one bit set. Which is the first?
669 int b = std::countr_zero (cache);
671 // Adjust the current position.
675 // Shift the bit found off of cache.
676 // Need to do it in two steps, because @c b might be @c BLOCKSIZE-1
677 // (and shifting by @c BLOCKSIZE is undefined).
678 m_cache = (cache>>1) >> b;
682 // Move to the next block.
687 // Reached the end without finding any more bits set.
688 // Mark that we hit the end.
689 m_bit = ~static_cast<bit_t>(0);
697 * @brief Advance the iterator to the next set bit (postincrement).
700 ConcurrentBitset::const_iterator
701 ConcurrentBitset::const_iterator::operator++(int)
703 const_iterator tmp = *this;
710 * @brief Compare two iterators.
711 * @param other The other iterator to compare.
714 bool ConcurrentBitset::const_iterator::operator== (const const_iterator& other) const
716 return m_bit == other.m_bit;
721 * @brief Compare two iterators.
722 * @param other The other iterator to compare.
725 bool ConcurrentBitset::const_iterator::operator!= (const const_iterator& other) const
727 return m_bit != other.m_bit;
732 * @brief Return a begin iterator.
735 ConcurrentBitset::const_iterator
736 ConcurrentBitset::begin() const
738 return (*m_impl).begin();
743 * @brief Return an end iterator.
746 ConcurrentBitset::const_iterator
747 ConcurrentBitset::end() const
749 return (*m_impl).end();
754 * @brief If bit @c bit is set, return an iterator pointing to it.
755 * Otherwise, return an end iterator.
756 * @param bit Bit number to test.
759 ConcurrentBitset::const_iterator
760 ConcurrentBitset::find (bit_t bit) const
762 return (*m_impl).find(bit);
766 //*********************************************************************************
771 * @brief Allocate an Impl structure.
772 * @param sz Size of an Impl structure.
773 * @param nbits Number of bits to allocate.
775 * The storage for the bits follows contiguously after the Impl structure itself
776 * (to save a memory reference); hence, we use a custom allocator.
779 void* ConcurrentBitset::Impl::operator new (size_t /*sz*/,
780 ConcurrentBitset::bit_t nbits)
782 bit_t nblocks = nBlocks (nbits);
783 // The Impl structure contains one Block_t at the end.
784 return malloc (sizeof(Impl) + (nblocks-1)*sizeof(Block_t));
789 * @brief Free an Impl structure.
790 * @param p Pointer to the object to free.
793 void ConcurrentBitset::Impl::operator delete (void* p)
800 * @brief Find number of blocks needed to hold a given number of bits.
801 * @param nbits The number of bits.
804 ConcurrentBitset::bit_t
805 ConcurrentBitset::nBlocks (bit_t nbits)
807 if (nbits == 0) return 1;
808 return (nbits+BLOCKSIZE-1) / BLOCKSIZE;
813 * @brief Create a new, uninitialized implementation object.
814 * @param nbits Number of bits to allocate.
816 * This will allocate memory for the Impl object,
817 * but will does not run the constructor.
820 ConcurrentBitset::Impl*
821 ConcurrentBitset::newImpl (bit_t nbits)
823 bit_t nblocks = nBlocks (nbits);
824 // The Impl structure contains one Block_t at the end.
825 return reinterpret_cast<Impl*>(malloc (sizeof(Impl) + (nblocks-1)*sizeof(Block_t)));
830 * @brief Expand the container.
831 * @param new_nbits The desired new size of the container.
834 void ConcurrentBitset::expand (bit_t new_nbits)
836 // Check the size of the container.
837 // Call the out-of-line portion if we actually need to expand.
838 if (new_nbits > (*m_impl).nbits()) {
839 expandOol (new_nbits);
845 * @brief Constructor.
846 * @param nbits Number of bits in the set.
849 ConcurrentBitset::Impl::Impl (bit_t nbits)
851 m_nblocks (nBlocks (nbits))
853 // Start with all bits 0.
859 * @brief Copy constructor.
860 * @brief Other object to copy.
861 * @brief Number of bits to use for this container.
863 * If @c nbits is smaller than the size of @c other, then the size of @c other
864 * will be used instead.
867 ConcurrentBitset::Impl::Impl (const Impl& other, bit_t nbits /*= 0*/)
868 : m_nbits (std::max (other.m_nbits, nbits)),
869 m_nblocks ((m_nbits+BLOCKSIZE-1) / BLOCKSIZE),
870 m_hwm (static_cast<size_t> (other.m_hwm))
872 // Copy, then clear the remainder.
873 // We don't care about the relative ordering, so to this with relaxed
874 // memory ordering, and add a barrier at the end.
875 for (bit_t i=0; i < other.m_nblocks; i++) {
876 m_data[i].store (other.m_data[i].load(std::memory_order_relaxed),
877 std::memory_order_relaxed);
879 for (bit_t i=other.m_nblocks; i < m_nblocks; i++) {
880 m_data[i].store (0, std::memory_order_relaxed);
882 std::atomic_thread_fence (std::memory_order_seq_cst);
887 * @brief Copy from another instance.
888 * @param other Object from which to copy.
890 * This does not change the size of the container.
891 * If This container is larger than @c other, then the remainder will be
892 * filled with zeros. If @c other is larger than this container, then the
893 * remainder will be ignored.
896 void ConcurrentBitset::Impl::assign (const Impl& other)
898 // Copy, then clear the remainder.
899 // We don't care about the relative ordering, so do this with relaxed
900 // memory ordering, and add a barrier at the end.
901 bit_t ncopy = std::min (m_nblocks, other.m_nblocks);
902 for (bit_t i=0; i < ncopy; i++) {
903 m_data[i].store (other.m_data[i].load(std::memory_order_relaxed),
904 std::memory_order_relaxed);
906 for (bit_t i=ncopy; i < m_nblocks; i++) {
907 m_data[i].store (0, std::memory_order_relaxed);
909 std::atomic_thread_fence (std::memory_order_seq_cst);
911 // Copy hwm last. It can only increase, so as long as we're after the barrier,
912 // we should get something that's correct.
913 m_hwm = static_cast<bit_t> (other.m_hwm);
918 * @brief Return the number of bits in the set.
921 ConcurrentBitset::bit_t ConcurrentBitset::Impl::nbits() const
928 * @brief Test to see if a bit is set.
929 * @param bit Number of the bit to test.
930 * @return true if the bit is set; false otherwise.
932 * Returns false if @c bit is beyond the end of the set.
935 bool ConcurrentBitset::Impl::test (bit_t bit) const
937 if (bit >= m_nbits) return false;
938 bit_t pos = bit / BLOCKSIZE;
939 return (m_data[pos] & (1UL<<(bit&MASK))) != 0;
944 * @brief Return a pointer to the block containing @c bit.
945 * @param bit Desired bit number.
947 * Returns nullptr if @c bit is past the end of the set.
950 std::atomic<ConcurrentBitset::Block_t>*
951 ConcurrentBitset::Impl::block (bit_t bit)
953 if (bit >= m_nbits) return nullptr;
954 bit_t pos = bit / BLOCKSIZE;
960 * @brief Turn on one bit.
961 * @param bit The bit to turn on.
963 * Does nothing if @c bit beyond the end of the set.
966 void ConcurrentBitset::Impl::set (bit_t bit)
968 if (bit >= m_nbits) return;
969 bit_t pos = bit / BLOCKSIZE;
970 // Update HWM if pos is larger.
971 CxxUtils::atomic_fetch_max (&m_hwm, pos);
972 m_data[pos] |= 1UL<<(bit&MASK);
977 * @brief Turn off one bit.
978 * @param bit The bit to turn off.
980 * Does nothing if @c bit beyond the end of the set.
983 void ConcurrentBitset::Impl::reset (bit_t bit)
985 if (bit >= m_nbits) return;
986 bit_t pos = bit / BLOCKSIZE;
987 m_data[pos] &= ~(1UL<<(bit&MASK));
992 * @brief Flip the value of one bit.
993 * @param bit The bit to turn flip.
995 * Does nothing if @c bit beyond the end of the set.
998 void ConcurrentBitset::Impl::flip (bit_t bit)
1000 if (bit >= m_nbits) return;
1001 size_t pos = bit / BLOCKSIZE;
1002 // Update HWM if pos is larger.
1003 CxxUtils::atomic_fetch_max (&m_hwm, pos);
1004 m_data[pos] ^= 1UL<<(bit&MASK);
1009 * @brief Clear all bits in the set.
1011 * This operation is not necessarily atomic; a simultaneous read may be able
1012 * to see the operation partially done.
1015 void ConcurrentBitset::Impl::clear()
1017 for (bit_t i=0; i<m_nblocks; i++) {
1018 m_data[i].store (0, std::memory_order_relaxed);
1020 std::atomic_thread_fence (std::memory_order_seq_cst);
1026 * @brief Apply a binary operation.
1027 * @param op Operation to apply.
1028 * @param other Second set for the operation.
1030 * Each block B in this set is replaced by B OP OTHER, where OTHER is
1031 * the corresponding block in the other container. (If this set
1032 * is larger than @c other, then the trailing blocks will be 0.)
1035 void ConcurrentBitset::Impl::operate (operator_t op, const Impl& other)
1037 bit_t nblocks = std::min (m_nblocks, other.m_nblocks);
1039 for (bit_t i = 0; i < nblocks; i++) {
1040 op (m_data[i], other.m_data[i]);
1041 if (m_data[i]) hwm = i;
1043 for (bit_t i = nblocks; i < m_nblocks; i++) {
1044 op (m_data[i], static_cast<Block_t> (0));
1045 if (m_data[i]) hwm = i;
1047 CxxUtils::atomic_fetch_max (&m_hwm, hwm);
1052 * @brief Compare with another set.
1053 * @param other Other set with which to compare.
1056 bool ConcurrentBitset::Impl::operator== (const Impl& other) const
1058 bit_t ntest = std::min (m_nblocks, other.m_nblocks);
1059 for (bit_t i = 0; i < ntest; i++) {
1060 if (m_data[i] != other.m_data[i]) return false;
1063 for (bit_t i = ntest; i < m_nblocks; i++) {
1064 if (m_data[i] != 0) return false;
1067 for (bit_t i = ntest; i < other.m_nblocks; i++) {
1068 if (other.m_data[i] != 0) return false;
1076 * @brief Return an iterator referencing the first 1 bit.
1079 ConcurrentBitset::const_iterator
1080 ConcurrentBitset::Impl::begin() const
1082 // Set the iterator to just before the start of the container.
1083 // Then use the increment operator to search for the first 1.
1084 bit_t offs = m_nblocks ? (static_cast<bit_t>(m_hwm) + 1) : 0;
1085 const_iterator it (0,
1086 static_cast<bit_t>(-BLOCKSIZE),
1095 * @brief Return the end iterator.
1098 ConcurrentBitset::const_iterator
1099 ConcurrentBitset::Impl::end() const
1101 return const_iterator (0,
1102 ~static_cast<bit_t>(0),
1108 * @brief If bit @c bit is set, return an iterator pointing to it.
1109 * Otherwise, return an end iterator.
1110 * @param bit Bit number to test.
1113 ConcurrentBitset::const_iterator
1114 ConcurrentBitset::Impl::find (bit_t bit) const
1118 // Construct an iterator pointing at this bit.
1119 bit_t pos = bit / BLOCKSIZE;
1120 const std::atomic<Block_t>* data = m_data + pos;
1121 Block_t cache = *data;
1122 bit_t offs = bit&MASK;
1127 return const_iterator (cache, bit, m_data+pos,
1128 &m_data[0] + m_hwm + 1);
1134 } // namespace CxxUtils