]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
os/bluestore: Add set_atomic and clr_atomic to SimpleBitmap
authorAdam Kupczyk <akupczyk@ibm.com>
Tue, 1 Jul 2025 11:48:45 +0000 (11:48 +0000)
committerAdam Kupczyk <akupczyk@ibm.com>
Mon, 8 Jun 2026 17:04:28 +0000 (17:04 +0000)
The functions are analogs of set and clr respectively that allow to multithread use.
In addition return value is a count of set/cleared bits.

Signed-off-by: Adam Kupczyk <akupczyk@ibm.com>
src/os/bluestore/simple_bitmap.cc
src/os/bluestore/simple_bitmap.h
src/test/objectstore/test_bluestore_types.cc

index 5354a2d0d6c684158ad9dd7d2bdbaa96199629a6..4dd79e0e9df4682f08e5e3ca7556b7f3c984ebbb 100644 (file)
@@ -18,6 +18,7 @@
 #include "include/ceph_assert.h"
 #include "bluestore_types.h"
 #include "common/debug.h"
+#include <atomic>
 
 #define dout_context cct
 #define dout_subsys ceph_subsys_bluestore
@@ -275,3 +276,95 @@ extent_t SimpleBitmap::get_next_clr_extent(uint64_t offset)
   ext.length = (soffset - ext.offset);
   return ext;
 }
+
+//----------------------------------------------------------------------------
+uint64_t SimpleBitmap::set_atomic(uint64_t offset, uint64_t length)
+{
+  if (offset >= m_num_bits) {
+    return 0;
+  }
+  if (offset + length >= m_num_bits) {
+    length = m_num_bits - offset;
+  }
+  if (length == 0) {
+    return 0;
+  }
+
+  auto apply_mask = [&](uint64_t word, uint64_t mask) -> uint64_t {
+    // Bits are completely unrelated to each other
+    // Plus, we do not care who sets the which bits if 2 chains of sets overlap.
+    // So, going for relaxed.
+    uint64_t old_val = std::atomic_ref<uint64_t>(m_arr[word]).fetch_or(mask, std::memory_order::relaxed);
+    // We set b if mask[b] = 1 and old_val[b] = 0.
+    uint64_t we_set_those = ~old_val & mask;
+    return std::popcount(we_set_those);
+  };
+  auto [word_start, bit_start] = split(offset);
+  auto [word_end, bit_end] = split(offset + length - 1);
+
+  uint64_t mask;
+  if (word_start == word_end) {
+    mask = (FULL_MASK << bit_start) &
+           (FULL_MASK >> (MAX_BIT_NO - bit_end));
+    return apply_mask(word_start, mask);
+  }
+  uint64_t bit_set_count = 0;
+  uint64_t word = word_start;
+  mask = FULL_MASK << bit_start;
+  bit_set_count += apply_mask(word, mask);
+  word++;
+  while (word < word_end) {
+    mask = FULL_MASK;
+    bit_set_count += apply_mask(word, mask);
+    word++;
+  }
+  mask = FULL_MASK >> (MAX_BIT_NO - bit_end);
+  bit_set_count += apply_mask(word, mask);
+  return bit_set_count;
+}
+
+//----------------------------------------------------------------------------
+uint64_t SimpleBitmap::clr_atomic(uint64_t offset, uint64_t length)
+{
+  if (offset >= m_num_bits) {
+    return 0;
+  }
+  if (offset + length >= m_num_bits) {
+    length = m_num_bits - offset;
+  }
+  if (length == 0) {
+    return 0;
+  }
+
+  auto apply_mask = [&](uint64_t word, uint64_t mask) -> uint64_t {
+    // Bits are completely unrelated to each other
+    // Plus, we do not care who clears the which bits if 2 chains of sets overlap.
+    // So, going for relaxed.
+    uint64_t old_val = std::atomic_ref<uint64_t>(m_arr[word]).fetch_and(~mask, std::memory_order::relaxed);
+    // We cleared b if mask[b] = 1 and old_val[b] = 1.
+    uint64_t we_cleared_those = old_val & mask;
+    return std::popcount(we_cleared_those);
+  };
+  auto [word_start, bit_start] = split(offset);
+  auto [word_end, bit_end] = split(offset + length - 1);
+
+  uint64_t mask;
+  if (word_start == word_end) {
+    mask = (FULL_MASK << bit_start) &
+           (FULL_MASK >> (MAX_BIT_NO - bit_end));
+    return apply_mask(word_start, mask);
+  }
+  uint64_t bit_set_count = 0;
+  uint64_t word = word_start;
+  mask = FULL_MASK << bit_start;
+  bit_set_count += apply_mask(word, mask);
+  word++;
+  while (word < word_end) {
+    mask = FULL_MASK;
+    bit_set_count += apply_mask(word, mask);
+    word++;
+  }
+  mask = FULL_MASK >> (MAX_BIT_NO - bit_end);
+  bit_set_count += apply_mask(word, mask);
+  return bit_set_count;
+}
index 4e1cd05136f6df95cb2074e31ffa37ced72f6576..8b569f01da66abe8109d269d64ad93bcf8886545 100644 (file)
@@ -50,6 +50,18 @@ public:
   // returns a copy of the next clear extent starting at @offset
   extent_t get_next_clr_extent(uint64_t offset);
 
+  // Sets a bit range (@length~@offset).
+  // This variant is safe for multithread operations.
+  // Can be intermixed with clr_atomic, but not with set or clr.
+  // Returns: count of bits set
+  uint64_t set_atomic(uint64_t offset, uint64_t length);
+
+  // Clears a bit range (@length~@offset).
+  // This variant is safe for multithread operations.
+  // Can be intermixed with set_atomic, but not with set or clr.
+  // Returns: count of bits set
+  uint64_t clr_atomic(uint64_t offset, uint64_t length);
+
   //----------------------------------------------------------------------------
   inline uint64_t get_size() {
     return m_num_bits;
@@ -138,6 +150,7 @@ private:
   constexpr static uint64_t      BITS_IN_WORD        = (BYTES_IN_WORD * 8);
   constexpr static uint64_t      BITS_IN_WORD_MASK   = (BITS_IN_WORD - 1);
   constexpr static uint64_t      BITS_IN_WORD_SHIFT  = 6;
+  constexpr static uint64_t      MAX_BIT_NO          = BITS_IN_WORD - 1;
   constexpr static uint64_t      FULL_MASK           = (~((uint64_t)0));
 
   CephContext *cct;
index 9d756bf5088c1fec7f3d1d1684c40d86b7b0e4b7..b5ae13ddc19f92b122eddf683cf73048ac1f05d7 100644 (file)
 #include "perfglue/heap_profiler.h"
 #include "os/bluestore/Writer.h"
 #include "common/pretty_binary.h"
-
 #include <bitset>
 #include <sstream>
+#include <boost/random/mersenne_twister.hpp>
+#include <boost/random/uniform_int.hpp>
+
+typedef boost::mt11213b generator_type;
 
 #define _STR(x) #x
 #define STRINGIFY(x) _STR(x)
@@ -3978,6 +3981,50 @@ TEST(SimpleBitmap, boundaries2) {
   }
 }
 
+TEST(SimpleBitmap, multithread) {
+  generator_type rng(1234567);
+  // 2^10 = 1K * 4KB = 4MB
+  // 2^20 = 1M * 4KB = 4GB
+  // 2^30 = 1G * 4KB = 4TB
+  for (uint64_t scale = 10; scale < 30; scale++) {
+    uint64_t bit_count = boost::uniform_int<uint64_t>(1 << scale, 2 << scale)(rng);
+    std::cout << "bit_count=" << bit_count << std::endl;
+    SimpleBitmap sbmap(g_ceph_context, bit_count);
+
+    auto randomer = [&](int thread_nr, int64_t* overall_set) {
+      generator_type local_rng(thread_nr);
+      boost::uniform_int<> size_gen(1, 100);
+      boost::uniform_int<> set_clear_gen(0,1);
+      int64_t overall_set_counter = 0;
+      for (int i = 0; i < 100000; i++) {
+        uint64_t size = size_gen(local_rng);
+        uint64_t location = boost::uniform_int<uint64_t>(0, bit_count - size)(local_rng);
+        int d = set_clear_gen(local_rng);
+        if (d) {
+          overall_set_counter += sbmap.set_atomic(location, size);
+        } else {
+          overall_set_counter -= sbmap.clr_atomic(location, size);
+        }
+      }
+      *overall_set = overall_set_counter;
+    };
+    static constexpr uint8_t thread_count = 8;
+    thread thr[thread_count];
+    int64_t overall_set[thread_count] = {0};
+    for (int t = 0; t < thread_count; t++) {
+      thr[t] = thread(randomer, t, &overall_set[t]);
+    }
+    int64_t bits_set_in_threads = 0;
+    for (int t = 0; t < thread_count; t++) {
+      thr[t].join();
+      bits_set_in_threads += overall_set[t];
+    }
+
+    uint64_t bits_cleared = sbmap.clr_atomic(0, bit_count);
+    EXPECT_EQ(bits_cleared, bits_set_in_threads);
+  }
+}
+
 TEST(shared_blob_2hash_tracker_t, basic_test) {
   shared_blob_2hash_tracker_t t1(1024 * 1024, 4096);