From: Adam Kupczyk Date: Tue, 1 Jul 2025 11:48:45 +0000 (+0000) Subject: os/bluestore: Add set_atomic and clr_atomic to SimpleBitmap X-Git-Tag: v21.0.1~8^2~9 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=233415044d73e31a8a5580b5b1285469dc2961ae;p=ceph.git os/bluestore: Add set_atomic and clr_atomic to SimpleBitmap 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 --- diff --git a/src/os/bluestore/simple_bitmap.cc b/src/os/bluestore/simple_bitmap.cc index 5354a2d0d6c..4dd79e0e9df 100644 --- a/src/os/bluestore/simple_bitmap.cc +++ b/src/os/bluestore/simple_bitmap.cc @@ -18,6 +18,7 @@ #include "include/ceph_assert.h" #include "bluestore_types.h" #include "common/debug.h" +#include #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(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(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; +} diff --git a/src/os/bluestore/simple_bitmap.h b/src/os/bluestore/simple_bitmap.h index 4e1cd05136f..8b569f01da6 100644 --- a/src/os/bluestore/simple_bitmap.h +++ b/src/os/bluestore/simple_bitmap.h @@ -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; diff --git a/src/test/objectstore/test_bluestore_types.cc b/src/test/objectstore/test_bluestore_types.cc index 9d756bf5088..b5ae13ddc19 100644 --- a/src/test/objectstore/test_bluestore_types.cc +++ b/src/test/objectstore/test_bluestore_types.cc @@ -15,9 +15,12 @@ #include "perfglue/heap_profiler.h" #include "os/bluestore/Writer.h" #include "common/pretty_binary.h" - #include #include +#include +#include + +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(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(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);