This is an automated email from the ASF dual-hosted git repository.

pitrou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new aa78eb89733 GH-50280: [C++] Implement VisitTwoBitRuns and 
VisitTwoSetBitRuns methods (#50281)
aa78eb89733 is described below

commit aa78eb89733134c4a8db8b5840960676f96df103
Author: Zehua Zou <[email protected]>
AuthorDate: Fri Jul 24 00:34:31 2026 +0800

    GH-50280: [C++] Implement VisitTwoBitRuns and VisitTwoSetBitRuns methods 
(#50281)
    
    ### Rationale for this change
    
    We already have `VisitTwoBitBlocks` which can iterate over two bitmaps 
simultaneously. But there is no mirror code about `BitRunReader` and 
`SetBitRunReader`.
    
    ### What changes are included in this PR?
    
    Implement `VisitTwoBitRuns` and `VisitTwoSetBitRuns` methods and refactor 
`VisitTwoBitBlocks` in `GroupedPivotAccumulator` as examples.
    
    ### Are these changes tested?
    
    Yes.
    
    ### Are there any user-facing changes?
    
    No.
    * GitHub Issue: #50280
    
    Lead-authored-by: Zehua Zou <[email protected]>
    Co-authored-by: Antoine Pitrou <[email protected]>
    Signed-off-by: Antoine Pitrou <[email protected]>
---
 cpp/src/arrow/compute/kernels/codegen_internal.h   |  29 ++--
 cpp/src/arrow/compute/kernels/scalar_if_else.cc    |  63 +++-----
 .../kernels/vector_selection_take_internal.cc      |  41 ++---
 cpp/src/arrow/util/bit_block_counter_benchmark.cc  | 168 +++++++++++++++++++--
 cpp/src/arrow/util/bit_run_reader.h                |  85 +++++++++++
 cpp/src/arrow/util/bitmap_test.cc                  | 123 +++++++++++++++
 6 files changed, 411 insertions(+), 98 deletions(-)

diff --git a/cpp/src/arrow/compute/kernels/codegen_internal.h 
b/cpp/src/arrow/compute/kernels/codegen_internal.h
index d33ffacb2f5..9d12d62d95f 100644
--- a/cpp/src/arrow/compute/kernels/codegen_internal.h
+++ b/cpp/src/arrow/compute/kernels/codegen_internal.h
@@ -38,6 +38,7 @@
 #include "arrow/type.h"
 #include "arrow/type_traits.h"
 #include "arrow/util/bit_block_counter.h"
+#include "arrow/util/bit_run_reader.h"
 #include "arrow/util/bit_util.h"
 #include "arrow/util/bitmap_generate.h"
 #include "arrow/util/bitmap_reader.h"
@@ -509,18 +510,24 @@ static void VisitTwoArrayValuesInline(const ArraySpan& 
arr0, const ArraySpan& ar
   ArrayIterator<Arg0Type> arr0_it(arr0);
   ArrayIterator<Arg1Type> arr1_it(arr1);
 
-  auto visit_valid = [&](int64_t i) {
-    valid_func(GetViewType<Arg0Type>::LogicalValue(arr0_it()),
-               GetViewType<Arg1Type>::LogicalValue(arr1_it()));
-  };
-  auto visit_null = [&]() {
-    arr0_it();
-    arr1_it();
-    null_func();
+  auto visit_run = [&](int64_t position, int64_t run_length, bool is_set) {
+    if (is_set) {
+      for (int64_t i = 0; i < run_length; ++i) {
+        valid_func(GetViewType<Arg0Type>::LogicalValue(arr0_it()),
+                   GetViewType<Arg1Type>::LogicalValue(arr1_it()));
+      }
+    } else {
+      for (int64_t i = 0; i < run_length; ++i) {
+        arr0_it();
+        arr1_it();
+        null_func();
+      }
+    }
   };
-  VisitTwoBitBlocksVoid(arr0.buffers[0].data, arr0.offset, 
arr1.buffers[0].data,
-                        arr1.offset, arr0.length, std::move(visit_valid),
-                        std::move(visit_null));
+
+  ::arrow::internal::VisitTwoBitRunsVoid(arr0.buffers[0].data, arr0.offset,
+                                         arr1.buffers[0].data, arr1.offset, 
arr0.length,
+                                         std::move(visit_run));
 }
 
 // ----------------------------------------------------------------------
diff --git a/cpp/src/arrow/compute/kernels/scalar_if_else.cc 
b/cpp/src/arrow/compute/kernels/scalar_if_else.cc
index 0c5cfe1c903..5618f24e5ce 100644
--- a/cpp/src/arrow/compute/kernels/scalar_if_else.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_if_else.cc
@@ -15,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <cstdint>
 #include <cstring>
 #include <limits>
 #include "arrow/array/builder_nested.h"
@@ -1607,26 +1608,14 @@ Status ExecArrayCaseWhen(KernelContext* ctx, const 
ExecSpan& batch, ExecResult*
 
     if (cond_array.GetNullCount() == 0) {
       // If no valid buffer, visit mask & cond bitmap simultaneously
-      BinaryBitBlockCounter counter(mask, /*start_offset=*/0, cond_values, 
cond_offset,
-                                    batch.length);
-      while (offset < batch.length) {
-        const auto block = counter.NextAndWord();
-        if (block.AllSet()) {
-          CopyValues<Type>(value, offset, block.length, out_valid, out_values,
-                           out_offset + offset);
-          bit_util::SetBitsTo(mask, offset, block.length, false);
-        } else if (block.popcount) {
-          for (int64_t j = 0; j < block.length; ++j) {
-            if (bit_util::GetBit(mask, offset + j) &&
-                bit_util::GetBit(cond_values, cond_offset + offset + j)) {
-              CopyValues<Type>(value, offset + j, /*length=*/1, out_valid, 
out_values,
-                               out_offset + offset + j);
-              bit_util::SetBitTo(mask, offset + j, false);
-            }
-          }
-        }
-        offset += block.length;
-      }
+      auto visit_run = [&](int64_t position, int64_t run_length) {
+        CopyValues<Type>(value, position, run_length, out_valid, out_values,
+                         out_offset + position);
+        bit_util::SetBitsTo(mask, position, run_length, false);
+        return Status::OK();
+      };
+      RETURN_NOT_OK(::arrow::internal::VisitTwoSetBitRuns(
+          mask, /*start_offset=*/0, cond_values, cond_offset, batch.length, 
visit_run));
     } else {
       // Visit mask & cond bitmap & cond validity
       const uint8_t* cond_valid = cond_array.buffers[0].data;
@@ -1657,32 +1646,20 @@ Status ExecArrayCaseWhen(KernelContext* ctx, const 
ExecSpan& batch, ExecResult*
   }
   if (!have_else_arg) {
     // Need to initialize any remaining null slots (uninitialized memory)
-    BitBlockCounter counter(mask, /*offset=*/0, batch.length);
-    int64_t offset = 0;
     auto bit_width = checked_cast<const 
FixedWidthType&>(*out->type()).bit_width();
     auto byte_width = bit_util::BytesForBits(bit_width);
-    while (offset < batch.length) {
-      const auto block = counter.NextWord();
-      if (block.AllSet()) {
-        if (bit_width == 1) {
-          bit_util::SetBitsTo(out_values, out_offset + offset, block.length, 
false);
-        } else {
-          std::memset(out_values + (out_offset + offset) * byte_width, 0x00,
-                      byte_width * block.length);
-        }
-      } else if (!block.NoneSet()) {
-        for (int64_t j = 0; j < block.length; ++j) {
-          if (bit_util::GetBit(out_valid, out_offset + offset + j)) continue;
-          if (bit_width == 1) {
-            bit_util::ClearBit(out_values, out_offset + offset + j);
-          } else {
-            std::memset(out_values + (out_offset + offset + j) * byte_width, 
0x00,
-                        byte_width);
-          }
-        }
+
+    auto visit_run = [&](int64_t position, int64_t run_length) {
+      if (bit_width == 1) {
+        bit_util::SetBitsTo(out_values, out_offset + position, run_length, 
false);
+      } else {
+        std::memset(out_values + (out_offset + position) * byte_width, 0x00,
+                    byte_width * run_length);
       }
-      offset += block.length;
-    }
+      bit_util::SetBitsTo(mask, position, run_length, false);
+    };
+
+    ::arrow::internal::VisitSetBitRunsVoid(mask, /*offset=*/0, batch.length, 
visit_run);
   }
   return Status::OK();
 }
diff --git a/cpp/src/arrow/compute/kernels/vector_selection_take_internal.cc 
b/cpp/src/arrow/compute/kernels/vector_selection_take_internal.cc
index fedafeb5bea..1237aae35c6 100644
--- a/cpp/src/arrow/compute/kernels/vector_selection_take_internal.cc
+++ b/cpp/src/arrow/compute/kernels/vector_selection_take_internal.cc
@@ -34,6 +34,7 @@
 #include "arrow/compute/registry.h"
 #include "arrow/memory_pool.h"
 #include "arrow/record_batch.h"
+#include "arrow/status.h"
 #include "arrow/table.h"
 #include "arrow/type.h"
 #include "arrow/type_traits.h"
@@ -135,37 +136,17 @@ Result<std::shared_ptr<ArrayData>> 
GetTakeIndicesFromBitmapImpl(
     // data bitmap together.
     DCHECK_EQ(null_selection, FilterOptions::DROP);
 
-    // The position relative to the start of the filter
-    T position = 0;
-    // The current position taking the filter offset into account
-    int64_t position_with_offset = filter.offset;
-
-    BinaryBitBlockCounter filter_counter(filter_data, filter.offset, 
filter_is_valid,
-                                         filter.offset, filter.length);
-    while (position < filter.length) {
-      BitBlockCount and_block = filter_counter.NextAndWord();
-      RETURN_NOT_OK(builder.Reserve(and_block.popcount));
-      if (and_block.AllSet()) {
-        // All the values are selected and non-null
-        for (int64_t i = 0; i < and_block.length; ++i) {
-          builder.UnsafeAppend(position++);
-        }
-        position_with_offset += and_block.length;
-      } else if (!and_block.NoneSet()) {
-        // Some of the values are false or null
-        for (int64_t i = 0; i < and_block.length; ++i) {
-          if (bit_util::GetBit(filter_is_valid, position_with_offset) &&
-              bit_util::GetBit(filter_data, position_with_offset)) {
-            builder.UnsafeAppend(position);
-          }
-          ++position;
-          ++position_with_offset;
-        }
-      } else {
-        position += and_block.length;
-        position_with_offset += and_block.length;
+    auto visit_run = [&](int64_t position, int64_t run_length) {
+      RETURN_NOT_OK(builder.Reserve(run_length));
+      for (int64_t i = 0; i < run_length; ++i) {
+        builder.UnsafeAppend(static_cast<T>(position + i));
       }
-    }
+      return Status::OK();
+    };
+
+    RETURN_NOT_OK(::arrow::internal::VisitTwoSetBitRuns(filter_data, 
filter.offset,
+                                                        filter_is_valid, 
filter.offset,
+                                                        filter.length, 
visit_run));
   } else {
     // The filter has no nulls, so we need only look for true values
     RETURN_NOT_OK(::arrow::internal::VisitSetBitRuns(
diff --git a/cpp/src/arrow/util/bit_block_counter_benchmark.cc 
b/cpp/src/arrow/util/bit_block_counter_benchmark.cc
index c08fcfdb604..7be82b24b86 100644
--- a/cpp/src/arrow/util/bit_block_counter_benchmark.cc
+++ b/cpp/src/arrow/util/bit_block_counter_benchmark.cc
@@ -23,22 +23,24 @@
 
 #include "arrow/array/array_base.h"
 #include "arrow/array/array_primitive.h"
+#include "arrow/testing/gtest_util.h"
 #include "arrow/testing/random.h"
 #include "arrow/util/bit_block_counter.h"
+#include "arrow/util/bit_run_reader.h"
 #include "arrow/util/bit_util.h"
 #include "arrow/util/bitmap_reader.h"
 
 namespace arrow {
 namespace internal {
 
-struct UnaryBitBlockBenchmark {
+struct UnaryBitmapTraversalBenchmark {
   benchmark::State& state;
   int64_t offset;
   int64_t bitmap_length;
   std::shared_ptr<Array> arr;
   int64_t expected;
 
-  explicit UnaryBitBlockBenchmark(benchmark::State& state, int64_t offset = 0)
+  explicit UnaryBitmapTraversalBenchmark(benchmark::State& state, int64_t 
offset = 0)
       : state(state), offset(offset), bitmap_length(1 << 20) {
     random::RandomArrayGenerator rng(/*seed=*/0);
     // State parameter is the average number of total values for each null
@@ -111,9 +113,57 @@ struct UnaryBitBlockBenchmark {
     }
     state.SetItemsProcessed(state.iterations() * bitmap_length);
   }
+
+  void BenchVisitBitRuns() {
+    const auto& int8_arr = static_cast<const Int8Array&>(*arr);
+    const uint8_t* bitmap = arr->null_bitmap_data();
+    for (auto _ : state) {
+      int64_t result = 0;
+      ABORT_NOT_OK(VisitBitRuns(bitmap, this->offset, bitmap_length - 
this->offset,
+                                [&](int64_t position, int64_t length, bool 
set) {
+                                  if (set) {
+                                    int64_t run_sum = 0;
+                                    const int64_t end = position + length;
+                                    for (int64_t i = position; i < end; ++i) {
+                                      run_sum += int8_arr.Value(this->offset + 
i);
+                                    }
+                                    result += run_sum;
+                                  }
+                                  return Status::OK();
+                                }));
+      // Sanity check
+      if (result != expected) {
+        std::abort();
+      }
+    }
+    state.SetItemsProcessed(state.iterations() * bitmap_length);
+  }
+
+  void BenchVisitSetBitRuns() {
+    const auto& int8_arr = static_cast<const Int8Array&>(*arr);
+    const uint8_t* bitmap = arr->null_bitmap_data();
+    for (auto _ : state) {
+      int64_t result = 0;
+      ABORT_NOT_OK(VisitSetBitRuns(bitmap, this->offset, bitmap_length - 
this->offset,
+                                   [&](int64_t position, int64_t length) {
+                                     int64_t run_sum = 0;
+                                     const int64_t end = position + length;
+                                     for (int64_t i = position; i < end; ++i) {
+                                       run_sum += int8_arr.Value(this->offset 
+ i);
+                                     }
+                                     result += run_sum;
+                                     return Status::OK();
+                                   }));
+      // Sanity check
+      if (result != expected) {
+        std::abort();
+      }
+    }
+    state.SetItemsProcessed(state.iterations() * bitmap_length);
+  }
 };
 
-struct BinaryBitBlockBenchmark {
+struct BinaryBitmapTraversalBenchmark {
   benchmark::State& state;
   int64_t offset;
   int64_t bitmap_length;
@@ -123,7 +173,7 @@ struct BinaryBitBlockBenchmark {
   const Int8Array* left_int8;
   const Int8Array* right_int8;
 
-  explicit BinaryBitBlockBenchmark(benchmark::State& state, int64_t offset = 0)
+  explicit BinaryBitmapTraversalBenchmark(benchmark::State& state, int64_t 
offset = 0)
       : state(state), offset(offset), bitmap_length(1 << 20) {
     random::RandomArrayGenerator rng(/*seed=*/0);
 
@@ -202,52 +252,134 @@ struct BinaryBitBlockBenchmark {
     }
     state.SetItemsProcessed(state.iterations() * bitmap_length);
   }
+
+  void BenchVisitTwoBitRuns() {
+    const uint8_t* left_bitmap = left->null_bitmap_data();
+    const uint8_t* right_bitmap = right->null_bitmap_data();
+    for (auto _ : state) {
+      int64_t result = 0;
+      VisitTwoBitRunsVoid(left_bitmap, this->offset, right_bitmap, 
this->offset,
+                          bitmap_length - this->offset,
+                          [&](int64_t position, int64_t length, bool set) {
+                            if (set) {
+                              int64_t run_sum = 0;
+                              const int64_t end = position + length;
+                              for (int64_t i = position; i < end; ++i) {
+                                run_sum += left_int8->Value(this->offset + i) +
+                                           right_int8->Value(this->offset + i);
+                              }
+                              result += run_sum;
+                            }
+                          });
+      // Sanity check
+      if (result != expected) {
+        std::abort();
+      }
+    }
+    state.SetItemsProcessed(state.iterations() * bitmap_length);
+  }
+
+  void BenchVisitTwoSetBitRuns() {
+    const uint8_t* left_bitmap = left->null_bitmap_data();
+    const uint8_t* right_bitmap = right->null_bitmap_data();
+    for (auto _ : state) {
+      int64_t result = 0;
+      VisitTwoSetBitRunsVoid(left_bitmap, this->offset, right_bitmap, 
this->offset,
+                             bitmap_length - this->offset,
+                             [&](int64_t position, int64_t length) {
+                               int64_t run_sum = 0;
+                               const int64_t end = position + length;
+                               for (int64_t i = position; i < end; ++i) {
+                                 run_sum += left_int8->Value(this->offset + i) 
+
+                                            right_int8->Value(this->offset + 
i);
+                               }
+                               result += run_sum;
+                             });
+      // Sanity check
+      if (result != expected) {
+        std::abort();
+      }
+    }
+    state.SetItemsProcessed(state.iterations() * bitmap_length);
+  }
 };
 
 static void BitBlockCounterSum(benchmark::State& state) {
-  UnaryBitBlockBenchmark(state, /*offset=*/0)
+  UnaryBitmapTraversalBenchmark(state, /*offset=*/0)
       .BenchBitBlockCounter([](BitBlockCounter* counter) { return 
counter->NextWord(); });
 }
 
 static void BitBlockCounterSumWithOffset(benchmark::State& state) {
-  UnaryBitBlockBenchmark(state, /*offset=*/4)
+  UnaryBitmapTraversalBenchmark(state, /*offset=*/4)
       .BenchBitBlockCounter([](BitBlockCounter* counter) { return 
counter->NextWord(); });
 }
 
 static void BitBlockCounterFourWordsSum(benchmark::State& state) {
-  UnaryBitBlockBenchmark(state, /*offset=*/0)
+  UnaryBitmapTraversalBenchmark(state, /*offset=*/0)
       .BenchBitBlockCounter(
           [](BitBlockCounter* counter) { return counter->NextFourWords(); });
 }
 
 static void BitBlockCounterFourWordsSumWithOffset(benchmark::State& state) {
-  UnaryBitBlockBenchmark(state, /*offset=*/4)
+  UnaryBitmapTraversalBenchmark(state, /*offset=*/4)
       .BenchBitBlockCounter(
           [](BitBlockCounter* counter) { return counter->NextFourWords(); });
 }
 
 static void BitmapReaderSum(benchmark::State& state) {
-  UnaryBitBlockBenchmark(state, /*offset=*/0).BenchBitmapReader();
+  UnaryBitmapTraversalBenchmark(state, /*offset=*/0).BenchBitmapReader();
 }
 
 static void BitmapReaderSumWithOffset(benchmark::State& state) {
-  UnaryBitBlockBenchmark(state, /*offset=*/4).BenchBitmapReader();
+  UnaryBitmapTraversalBenchmark(state, /*offset=*/4).BenchBitmapReader();
+}
+
+static void VisitBitRunsSum(benchmark::State& state) {
+  UnaryBitmapTraversalBenchmark(state, /*offset=*/0).BenchVisitBitRuns();
+}
+
+static void VisitBitRunsSumWithOffset(benchmark::State& state) {
+  UnaryBitmapTraversalBenchmark(state, /*offset=*/4).BenchVisitBitRuns();
+}
+
+static void VisitSetBitRunsSum(benchmark::State& state) {
+  UnaryBitmapTraversalBenchmark(state, /*offset=*/0).BenchVisitSetBitRuns();
+}
+
+static void VisitSetBitRunsSumWithOffset(benchmark::State& state) {
+  UnaryBitmapTraversalBenchmark(state, /*offset=*/4).BenchVisitSetBitRuns();
 }
 
 static void BinaryBitBlockCounterSum(benchmark::State& state) {
-  BinaryBitBlockBenchmark(state, /*offset=*/0).BenchBitBlockCounter();
+  BinaryBitmapTraversalBenchmark(state, /*offset=*/0).BenchBitBlockCounter();
 }
 
 static void BinaryBitBlockCounterSumWithOffset(benchmark::State& state) {
-  BinaryBitBlockBenchmark(state, /*offset=*/4).BenchBitBlockCounter();
+  BinaryBitmapTraversalBenchmark(state, /*offset=*/4).BenchBitBlockCounter();
 }
 
 static void BinaryBitmapReaderSum(benchmark::State& state) {
-  BinaryBitBlockBenchmark(state, /*offset=*/0).BenchBitmapReader();
+  BinaryBitmapTraversalBenchmark(state, /*offset=*/0).BenchBitmapReader();
 }
 
 static void BinaryBitmapReaderSumWithOffset(benchmark::State& state) {
-  BinaryBitBlockBenchmark(state, /*offset=*/4).BenchBitmapReader();
+  BinaryBitmapTraversalBenchmark(state, /*offset=*/4).BenchBitmapReader();
+}
+
+static void BinaryVisitTwoBitRunsSum(benchmark::State& state) {
+  BinaryBitmapTraversalBenchmark(state, /*offset=*/0).BenchVisitTwoBitRuns();
+}
+
+static void BinaryVisitTwoBitRunsSumWithOffset(benchmark::State& state) {
+  BinaryBitmapTraversalBenchmark(state, /*offset=*/4).BenchVisitTwoBitRuns();
+}
+
+static void BinaryVisitTwoSetBitRunsSum(benchmark::State& state) {
+  BinaryBitmapTraversalBenchmark(state, 
/*offset=*/0).BenchVisitTwoSetBitRuns();
+}
+
+static void BinaryVisitTwoSetBitRunsSumWithOffset(benchmark::State& state) {
+  BinaryBitmapTraversalBenchmark(state, 
/*offset=*/4).BenchVisitTwoSetBitRuns();
 }
 
 // Range value: average number of total values per null
@@ -257,10 +389,18 @@ BENCHMARK(BitBlockCounterFourWordsSum)->Range(2, 1 << 16);
 BENCHMARK(BitBlockCounterFourWordsSumWithOffset)->Range(2, 1 << 16);
 BENCHMARK(BitmapReaderSum)->Range(2, 1 << 16);
 BENCHMARK(BitmapReaderSumWithOffset)->Range(2, 1 << 16);
+BENCHMARK(VisitBitRunsSum)->Range(2, 1 << 16);
+BENCHMARK(VisitBitRunsSumWithOffset)->Range(2, 1 << 16);
+BENCHMARK(VisitSetBitRunsSum)->Range(2, 1 << 16);
+BENCHMARK(VisitSetBitRunsSumWithOffset)->Range(2, 1 << 16);
 BENCHMARK(BinaryBitBlockCounterSum)->Range(2, 1 << 16);
 BENCHMARK(BinaryBitBlockCounterSumWithOffset)->Range(2, 1 << 16);
 BENCHMARK(BinaryBitmapReaderSum)->Range(2, 1 << 16);
 BENCHMARK(BinaryBitmapReaderSumWithOffset)->Range(2, 1 << 16);
+BENCHMARK(BinaryVisitTwoBitRunsSum)->Range(2, 1 << 16);
+BENCHMARK(BinaryVisitTwoBitRunsSumWithOffset)->Range(2, 1 << 16);
+BENCHMARK(BinaryVisitTwoSetBitRunsSum)->Range(2, 1 << 16);
+BENCHMARK(BinaryVisitTwoSetBitRunsSumWithOffset)->Range(2, 1 << 16);
 
 }  // namespace internal
 }  // namespace arrow
diff --git a/cpp/src/arrow/util/bit_run_reader.h 
b/cpp/src/arrow/util/bit_run_reader.h
index 1a9638880c5..84366e2789c 100644
--- a/cpp/src/arrow/util/bit_run_reader.h
+++ b/cpp/src/arrow/util/bit_run_reader.h
@@ -17,15 +17,21 @@
 
 #pragma once
 
+#include <algorithm>
+#include <array>
 #include <bit>
 #include <cassert>
 #include <cstdint>
 #include <cstring>
 #include <string>
+#include <utility>
 
+#include "arrow/status.h"
 #include "arrow/util/bit_util.h"
+#include "arrow/util/bitmap_ops.h"
 #include "arrow/util/bitmap_reader.h"
 #include "arrow/util/endian.h"
+#include "arrow/util/logging.h"
 #include "arrow/util/macros.h"
 #include "arrow/util/visibility.h"
 
@@ -536,5 +542,84 @@ inline void VisitSetBitRunsVoid(const 
std::shared_ptr<Buffer>& bitmap, int64_t o
                       std::forward<Visit>(visit));
 }
 
+template <typename Visit>
+inline Status VisitTwoSetBitRuns(const uint8_t* left_bitmap, int64_t 
left_offset,
+                                 const uint8_t* right_bitmap, int64_t 
right_offset,
+                                 int64_t length, Visit&& visit) {
+  if (length == 0) {
+    return Status::OK();
+  }
+  if (left_bitmap == NULLPTR) {
+    return VisitSetBitRuns(right_bitmap, right_offset, length,
+                           std::forward<Visit>(visit));
+  }
+  if (right_bitmap == NULLPTR) {
+    return VisitSetBitRuns(left_bitmap, left_offset, length, 
std::forward<Visit>(visit));
+  }
+
+  constexpr int64_t kBitmapChunkLength = 4096;  // 512 bytes of scratch space 
on stack
+  std::array<uint8_t, bit_util::BytesForBits(kBitmapChunkLength)> bitmap_chunk;
+
+  int64_t offset = 0;
+  while (offset < length) {
+    const auto chunk_length = std::min(kBitmapChunkLength, length - offset);
+    BitmapAnd(left_bitmap, left_offset + offset, right_bitmap, right_offset + 
offset,
+              chunk_length,
+              /*out_offset=*/0, bitmap_chunk.data());
+
+    auto visit_with_offset = [&](int64_t position_in_chunk, int64_t 
run_length) {
+      return visit(position_in_chunk + offset, run_length);
+    };
+
+    ARROW_RETURN_NOT_OK(VisitSetBitRuns(bitmap_chunk.data(), /*offset=*/0, 
chunk_length,
+                                        std::move(visit_with_offset)));
+    offset += chunk_length;
+  }
+  return Status::OK();
+}
+
+template <typename Visit>
+inline Status VisitTwoBitRuns(const uint8_t* left_bitmap, int64_t left_offset,
+                              const uint8_t* right_bitmap, int64_t 
right_offset,
+                              int64_t length, Visit&& visit) {
+  int64_t output_position = 0;
+  ARROW_RETURN_NOT_OK(VisitTwoSetBitRuns(
+      left_bitmap, left_offset, right_bitmap, right_offset, length,
+      [&](int64_t position, int64_t run_length) {
+        if (output_position < position) {
+          ARROW_RETURN_NOT_OK(visit(output_position, position - 
output_position, false));
+        }
+        ARROW_RETURN_NOT_OK(visit(position, run_length, true));
+        output_position = position + run_length;
+        return Status::OK();
+      }));
+  if (output_position < length) {
+    return visit(output_position, length - output_position, false);
+  }
+  return Status::OK();
+}
+
+template <typename Visit>
+inline void VisitTwoBitRunsVoid(const uint8_t* left_bitmap, int64_t 
left_offset,
+                                const uint8_t* right_bitmap, int64_t 
right_offset,
+                                int64_t length, Visit&& visit) {
+  ARROW_CHECK_OK(VisitTwoBitRuns(left_bitmap, left_offset, right_bitmap, 
right_offset,
+                                 length, [&](int64_t position, int64_t length, 
bool set) {
+                                   visit(position, length, set);
+                                   return Status::OK();
+                                 }));
+}
+
+template <typename Visit>
+inline void VisitTwoSetBitRunsVoid(const uint8_t* left_bitmap, int64_t 
left_offset,
+                                   const uint8_t* right_bitmap, int64_t 
right_offset,
+                                   int64_t length, Visit&& visit) {
+  ARROW_CHECK_OK(VisitTwoSetBitRuns(left_bitmap, left_offset, right_bitmap, 
right_offset,
+                                    length, [&](int64_t position, int64_t 
length) {
+                                      visit(position, length);
+                                      return Status::OK();
+                                    }));
+}
+
 }  // namespace internal
 }  // namespace arrow
diff --git a/cpp/src/arrow/util/bitmap_test.cc 
b/cpp/src/arrow/util/bitmap_test.cc
index 0a71f4e6038..4ede199e626 100644
--- a/cpp/src/arrow/util/bitmap_test.cc
+++ b/cpp/src/arrow/util/bitmap_test.cc
@@ -332,6 +332,49 @@ class TestSetBitRunReader : public ::testing::Test {
     AssertBitRuns(buffer.data(), start_offset, length, expected);
   }
 
+  void AssertVisitTwoBitRuns(const uint8_t* left_data, int64_t left_offset,
+                             const uint8_t* right_data, int64_t right_offset,
+                             int64_t length,
+                             const std::vector<int64_t>& expected_positions,
+                             const std::vector<BitRun>& expected_runs,
+                             const std::vector<SetBitRun>& expected_set_runs) {
+    std::vector<int64_t> positions;
+    std::vector<BitRun> runs;
+    ASSERT_OK(VisitTwoBitRuns(left_data, left_offset, right_data, 
right_offset, length,
+                              [&](int64_t position, int64_t run_length, bool 
set) {
+                                positions.push_back(position);
+                                runs.push_back({run_length, set});
+                                return Status::OK();
+                              }));
+    ASSERT_THAT(positions, ElementsAreArray(expected_positions));
+    ASSERT_THAT(runs, ElementsAreArray(expected_runs));
+
+    positions.clear();
+    runs.clear();
+    VisitTwoBitRunsVoid(left_data, left_offset, right_data, right_offset, 
length,
+                        [&](int64_t position, int64_t run_length, bool set) {
+                          positions.push_back(position);
+                          runs.push_back({run_length, set});
+                        });
+    ASSERT_THAT(positions, ElementsAreArray(expected_positions));
+    ASSERT_THAT(runs, ElementsAreArray(expected_runs));
+
+    std::vector<SetBitRun> set_runs;
+    ASSERT_OK(VisitTwoSetBitRuns(left_data, left_offset, right_data, 
right_offset, length,
+                                 [&](int64_t position, int64_t run_length) {
+                                   set_runs.push_back({position, run_length});
+                                   return Status::OK();
+                                 }));
+    ASSERT_THAT(set_runs, ElementsAreArray(expected_set_runs));
+
+    set_runs.clear();
+    VisitTwoSetBitRunsVoid(left_data, left_offset, right_data, right_offset, 
length,
+                           [&](int64_t position, int64_t run_length) {
+                             set_runs.push_back({position, run_length});
+                           });
+    ASSERT_THAT(set_runs, ElementsAreArray(expected_set_runs));
+  }
+
   struct Range {
     int64_t offset;
     int64_t length;
@@ -484,6 +527,86 @@ TEST_F(TestSetBitRunReader, Random) {
   }
 }
 
+TEST_F(TestSetBitRunReader, VisitTwoBitRuns) {
+  auto left = BitmapFromString("11110000 11111100");
+  auto right = BitmapFromString("11001100 00111111");
+  const int64_t left_offset = 1;
+  const int64_t right_offset = 2;
+  const int64_t length = 12;
+
+  // With left_offset = 1 and right_offset = 2:
+  // position:  0 1 2 3 4 5 6 7 8 9 10 11
+  // left[+1]:  1 1 1 0 0 0 0 1 1 1  1  1
+  // right[+2]: 0 0 1 1 0 0 0 0 1 1  1  1
+  // AND:       0 0 1 0 0 0 0 0 1 1  1  1
+  const std::vector<int64_t> expected_positions = {0, 2, 3, 8};
+  const std::vector<BitRun> expected_runs = {{.length = 2, .set = false},
+                                             {.length = 1, .set = true},
+                                             {.length = 5, .set = false},
+                                             {.length = 4, .set = true}};
+  const std::vector<SetBitRun> expected_set_runs = {{.position = 2, .length = 
1},
+                                                    {.position = 8, .length = 
4}};
+
+  AssertVisitTwoBitRuns(left->data(), left_offset, right->data(), 
right_offset, length,
+                        expected_positions, expected_runs, expected_set_runs);
+}
+
+TEST_F(TestSetBitRunReader, VisitTwoBitRunsWithNullBitmap) {
+  auto bitmap = BitmapFromString("01101101");
+  const int64_t left_offset = 0;
+  const int64_t right_offset = 1;
+  const int64_t length = 6;
+
+  // With a null left bitmap and right_offset = 1:
+  // left all set: 1 1 1 1 1 1
+  // right[1..6]:  1 1 0 1 1 0
+  // AND:          1 1 0 1 1 0
+  const std::vector<int64_t> expected_positions = {0, 2, 3, 5};
+  const std::vector<BitRun> expected_runs = {{.length = 2, .set = true},
+                                             {.length = 1, .set = false},
+                                             {.length = 2, .set = true},
+                                             {.length = 1, .set = false}};
+  const std::vector<SetBitRun> expected_set_runs = {{.position = 0, .length = 
2},
+                                                    {.position = 3, .length = 
2}};
+
+  AssertVisitTwoBitRuns(nullptr, left_offset, bitmap->data(), right_offset, 
length,
+                        expected_positions, expected_runs, expected_set_runs);
+}
+
+TEST_F(TestSetBitRunReader, VisitTwoBitRunsFalseTail) {
+  auto left = BitmapFromString("11111111");
+  auto right = BitmapFromString("11110000");
+
+  std::vector<int64_t> positions;
+  std::vector<BitRun> runs;
+  ASSERT_OK(VisitTwoBitRuns(left->data(), /*left_offset=*/0, right->data(),
+                            /*right_offset=*/0, /*length=*/8,
+                            [&](int64_t position, int64_t length, bool set) {
+                              positions.push_back(position);
+                              runs.push_back({length, set});
+                              return Status::OK();
+                            }));
+  ASSERT_THAT(positions, ElementsAreArray({0, 4}));
+  ASSERT_THAT(runs, ElementsAreArray({BitRun{4, true}, BitRun{4, false}}));
+}
+
+TEST_F(TestSetBitRunReader, VisitTwoBitRunsNoOverlap) {
+  auto left = BitmapFromString("11110000");
+  auto right = BitmapFromString("00001111");
+
+  std::vector<int64_t> positions;
+  std::vector<BitRun> runs;
+  ASSERT_OK(VisitTwoBitRuns(left->data(), /*left_offset=*/0, right->data(),
+                            /*right_offset=*/0, /*length=*/8,
+                            [&](int64_t position, int64_t length, bool set) {
+                              positions.push_back(position);
+                              runs.push_back({length, set});
+                              return Status::OK();
+                            }));
+  ASSERT_THAT(positions, ElementsAreArray({0}));
+  ASSERT_THAT(runs, ElementsAreArray({BitRun{8, false}}));
+}
+
 // Tests for BitRunReader.
 
 TEST(BitRunReader, ZeroLength) {

Reply via email to