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

apitrou 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 83f35de2eb GH-43953: [C++] Add tests based on random data and 
benchmarks to ChunkResolver::ResolveMany (#43954)
83f35de2eb is described below

commit 83f35de2ebe4672d0d57290840d0153ac8a016e8
Author: Felipe Oliveira Carvalho <[email protected]>
AuthorDate: Tue Sep 24 11:36:24 2024 -0300

    GH-43953: [C++] Add tests based on random data and benchmarks to 
ChunkResolver::ResolveMany (#43954)
    
    ### Rationale for this change
    
    Improve tests and add benchmarks. I wrote the tests and benchmarks while 
trying to improve the performance of `ResolveMany` and failing at it.
    
    ### What changes are included in this PR?
    
    Tests, benchmarks, and changes that don't really affect performance but 
might unlock more optimization opportunities in the future.
    
    ### Are these changes tested?
    
    Yes.
    
    * GitHub Issue: #43953
    
    Lead-authored-by: Felipe Oliveira Carvalho <[email protected]>
    Co-authored-by: Antoine Pitrou <[email protected]>
    Signed-off-by: Antoine Pitrou <[email protected]>
---
 cpp/src/arrow/CMakeLists.txt              |   1 +
 cpp/src/arrow/chunk_resolver.cc           | 104 +++++++++++--------
 cpp/src/arrow/chunk_resolver.h            |  57 +++++-----
 cpp/src/arrow/chunk_resolver_benchmark.cc | 166 ++++++++++++++++++++++++++++++
 cpp/src/arrow/chunked_array.cc            |   4 +-
 cpp/src/arrow/chunked_array_test.cc       |  68 ++++++++++++
 6 files changed, 326 insertions(+), 74 deletions(-)

diff --git a/cpp/src/arrow/CMakeLists.txt b/cpp/src/arrow/CMakeLists.txt
index e77a02d0c0..c911f0f4e9 100644
--- a/cpp/src/arrow/CMakeLists.txt
+++ b/cpp/src/arrow/CMakeLists.txt
@@ -1218,6 +1218,7 @@ add_arrow_test(sparse_tensor_test)
 add_arrow_test(stl_test SOURCES stl_iterator_test.cc stl_test.cc)
 
 add_arrow_benchmark(builder_benchmark)
+add_arrow_benchmark(chunk_resolver_benchmark)
 add_arrow_benchmark(compare_benchmark)
 add_arrow_benchmark(memory_pool_benchmark)
 add_arrow_benchmark(type_benchmark)
diff --git a/cpp/src/arrow/chunk_resolver.cc b/cpp/src/arrow/chunk_resolver.cc
index 8541274807..bda6b17810 100644
--- a/cpp/src/arrow/chunk_resolver.cc
+++ b/cpp/src/arrow/chunk_resolver.cc
@@ -55,43 +55,57 @@ inline std::vector<int64_t> MakeChunksOffsets(const 
std::vector<T>& chunks) {
   return offsets;
 }
 
+template <typename IndexType>
+inline TypedChunkLocation<IndexType> ResolveOneInline(uint32_t num_offsets,
+                                                      const uint64_t* offsets,
+                                                      IndexType 
typed_logical_index,
+                                                      int32_t num_chunks,
+                                                      int32_t chunk_hint) {
+  const auto index = static_cast<uint64_t>(typed_logical_index);
+  // use or update chunk_hint
+  if (index >= offsets[chunk_hint] &&
+      (chunk_hint == num_chunks || index < offsets[chunk_hint + 1])) {
+    // hint is correct!
+  } else {
+    // lo < hi is guaranteed by `num_offsets = chunks.size() + 1`
+    auto chunk_index =
+        ChunkResolver::Bisect(index, offsets, /*lo=*/0, /*hi=*/num_offsets);
+    chunk_hint = static_cast<int32_t>(chunk_index);
+  }
+  // chunk_index is in [0, chunks.size()] no matter what the value
+  // of logical_index is, so it's always safe to dereference offsets
+  // as it contains chunks.size()+1 values.
+  auto loc = TypedChunkLocation<IndexType>(
+      /*chunk_index=*/chunk_hint,
+      /*index_in_chunk=*/typed_logical_index -
+          static_cast<IndexType>(offsets[chunk_hint]));
+#if defined(ARROW_VALGRIND) || defined(ADDRESS_SANITIZER)
+  // Make it more likely that Valgrind/ASAN can catch an invalid memory
+  // access by poisoning the index-in-chunk value when the logical
+  // index is out-of-bounds.
+  if (static_cast<int32_t>(loc.chunk_index) == num_chunks) {
+    loc.index_in_chunk = std::numeric_limits<IndexType>::max();
+  }
+#endif
+  return loc;
+}
+
 /// \pre all the pre-conditions of ChunkResolver::ResolveMany()
 /// \pre num_offsets - 1 <= std::numeric_limits<IndexType>::max()
 template <typename IndexType>
-void ResolveManyInline(size_t num_offsets, const int64_t* signed_offsets,
+void ResolveManyInline(uint32_t num_offsets, const int64_t* signed_offsets,
                        int64_t n_indices, const IndexType* logical_index_vec,
                        TypedChunkLocation<IndexType>* out_chunk_location_vec,
-                       IndexType chunk_hint) {
+                       int32_t chunk_hint) {
   auto* offsets = reinterpret_cast<const uint64_t*>(signed_offsets);
-  const auto num_chunks = static_cast<IndexType>(num_offsets - 1);
+  const auto num_chunks = static_cast<int32_t>(num_offsets - 1);
   // chunk_hint in [0, num_offsets) per the precondition.
   for (int64_t i = 0; i < n_indices; i++) {
-    auto typed_logical_index = logical_index_vec[i];
-    const auto index = static_cast<uint64_t>(typed_logical_index);
-    // use or update chunk_hint
-    if (index >= offsets[chunk_hint] &&
-        (chunk_hint == num_chunks || index < offsets[chunk_hint + 1])) {
-      // hint is correct!
-    } else {
-      // lo < hi is guaranteed by `num_offsets = chunks.size() + 1`
-      auto chunk_index =
-          ChunkResolver::Bisect(index, offsets, /*lo=*/0, /*hi=*/num_offsets);
-      chunk_hint = static_cast<IndexType>(chunk_index);
-    }
-    out_chunk_location_vec[i].chunk_index = chunk_hint;
-    // chunk_index is in [0, chunks.size()] no matter what the
-    // value of logical_index is, so it's always safe to dereference
-    // offset_ as it contains chunks.size()+1 values.
-    out_chunk_location_vec[i].index_in_chunk =
-        typed_logical_index - static_cast<IndexType>(offsets[chunk_hint]);
-#if defined(ARROW_VALGRIND) || defined(ADDRESS_SANITIZER)
-    // Make it more likely that Valgrind/ASAN can catch an invalid memory
-    // access by poisoning the index-in-chunk value when the logical
-    // index is out-of-bounds.
-    if (chunk_hint == num_chunks) {
-      out_chunk_location_vec[i].index_in_chunk = 
std::numeric_limits<IndexType>::max();
-    }
-#endif
+    const auto typed_logical_index = logical_index_vec[i];
+    const auto loc = ResolveOneInline(num_offsets, offsets, 
typed_logical_index,
+                                      num_chunks, chunk_hint);
+    out_chunk_location_vec[i] = loc;
+    chunk_hint = static_cast<int32_t>(loc.chunk_index);
   }
 }
 
@@ -127,30 +141,30 @@ ChunkResolver& ChunkResolver::operator=(const 
ChunkResolver& other) noexcept {
 
 void ChunkResolver::ResolveManyImpl(int64_t n_indices, const uint8_t* 
logical_index_vec,
                                     TypedChunkLocation<uint8_t>* 
out_chunk_location_vec,
-                                    uint8_t chunk_hint) const {
-  ResolveManyInline(offsets_.size(), offsets_.data(), n_indices, 
logical_index_vec,
-                    out_chunk_location_vec, chunk_hint);
-}
-
-void ChunkResolver::ResolveManyImpl(int64_t n_indices, const uint32_t* 
logical_index_vec,
-                                    TypedChunkLocation<uint32_t>* 
out_chunk_location_vec,
-                                    uint32_t chunk_hint) const {
-  ResolveManyInline(offsets_.size(), offsets_.data(), n_indices, 
logical_index_vec,
-                    out_chunk_location_vec, chunk_hint);
+                                    int32_t chunk_hint) const {
+  ResolveManyInline(static_cast<uint32_t>(offsets_.size()), offsets_.data(), 
n_indices,
+                    logical_index_vec, out_chunk_location_vec, chunk_hint);
 }
 
 void ChunkResolver::ResolveManyImpl(int64_t n_indices, const uint16_t* 
logical_index_vec,
                                     TypedChunkLocation<uint16_t>* 
out_chunk_location_vec,
-                                    uint16_t chunk_hint) const {
-  ResolveManyInline(offsets_.size(), offsets_.data(), n_indices, 
logical_index_vec,
-                    out_chunk_location_vec, chunk_hint);
+                                    int32_t chunk_hint) const {
+  ResolveManyInline(static_cast<uint32_t>(offsets_.size()), offsets_.data(), 
n_indices,
+                    logical_index_vec, out_chunk_location_vec, chunk_hint);
+}
+
+void ChunkResolver::ResolveManyImpl(int64_t n_indices, const uint32_t* 
logical_index_vec,
+                                    TypedChunkLocation<uint32_t>* 
out_chunk_location_vec,
+                                    int32_t chunk_hint) const {
+  ResolveManyInline(static_cast<uint32_t>(offsets_.size()), offsets_.data(), 
n_indices,
+                    logical_index_vec, out_chunk_location_vec, chunk_hint);
 }
 
 void ChunkResolver::ResolveManyImpl(int64_t n_indices, const uint64_t* 
logical_index_vec,
                                     TypedChunkLocation<uint64_t>* 
out_chunk_location_vec,
-                                    uint64_t chunk_hint) const {
-  ResolveManyInline(offsets_.size(), offsets_.data(), n_indices, 
logical_index_vec,
-                    out_chunk_location_vec, chunk_hint);
+                                    int32_t chunk_hint) const {
+  ResolveManyInline(static_cast<uint32_t>(offsets_.size()), offsets_.data(), 
n_indices,
+                    logical_index_vec, out_chunk_location_vec, chunk_hint);
 }
 
 }  // namespace arrow::internal
diff --git a/cpp/src/arrow/chunk_resolver.h b/cpp/src/arrow/chunk_resolver.h
index 83fda62387..4a5e27c053 100644
--- a/cpp/src/arrow/chunk_resolver.h
+++ b/cpp/src/arrow/chunk_resolver.h
@@ -72,7 +72,7 @@ struct ARROW_EXPORT ChunkResolver {
   /// \brief Cache of the index of the last resolved chunk.
   ///
   /// \invariant `cached_chunk_ in [0, chunks.size()]`
-  mutable std::atomic<int64_t> cached_chunk_;
+  mutable std::atomic<int32_t> cached_chunk_;
 
  public:
   explicit ChunkResolver(const ArrayVector& chunks) noexcept;
@@ -92,6 +92,8 @@ struct ARROW_EXPORT ChunkResolver {
     for (size_t i = 1; i < offsets_.size(); i++) {
       assert(offsets_[i] >= offsets_[i - 1]);
     }
+    assert(offsets_.size() - 1 <=
+           static_cast<size_t>(std::numeric_limits<int32_t>::max()));
 #endif
   }
 
@@ -102,7 +104,7 @@ struct ARROW_EXPORT ChunkResolver {
   ChunkResolver& operator=(const ChunkResolver& other) noexcept;
 
   int64_t logical_array_length() const { return offsets_.back(); }
-  int64_t num_chunks() const { return static_cast<int64_t>(offsets_.size()) - 
1; }
+  int32_t num_chunks() const { return static_cast<int32_t>(offsets_.size() - 
1); }
 
   int64_t chunk_length(int64_t chunk_index) const {
     return offsets_[chunk_index + 1] - offsets_[chunk_index];
@@ -140,9 +142,9 @@ struct ARROW_EXPORT ChunkResolver {
   ///         bounds, or with chunk_index == chunks.size() if logical index is
   ///         `>= chunked_array.length()`.
   inline ChunkLocation ResolveWithHint(int64_t index, ChunkLocation hint) 
const {
-    assert(hint.chunk_index < static_cast<int64_t>(offsets_.size()));
-    const auto chunk_index =
-        ResolveChunkIndex</*StoreCachedChunk=*/false>(index, hint.chunk_index);
+    assert(hint.chunk_index < static_cast<uint32_t>(offsets_.size()));
+    const auto chunk_index = ResolveChunkIndex</*StoreCachedChunk=*/false>(
+        index, static_cast<int32_t>(hint.chunk_index));
     return ChunkLocation{chunk_index, index - offsets_[chunk_index]};
   }
 
@@ -169,13 +171,12 @@ struct ARROW_EXPORT ChunkResolver {
   [[nodiscard]] bool ResolveMany(int64_t n_indices, const IndexType* 
logical_index_vec,
                                  TypedChunkLocation<IndexType>* 
out_chunk_location_vec,
                                  IndexType chunk_hint = 0) const {
-    if constexpr (sizeof(IndexType) < sizeof(uint64_t)) {
+    if constexpr (sizeof(IndexType) < sizeof(uint32_t)) {
       // The max value returned by Bisect is `offsets.size() - 1` (= 
chunks.size()).
-      constexpr uint64_t kMaxIndexTypeValue = 
std::numeric_limits<IndexType>::max();
+      constexpr int64_t kMaxIndexTypeValue = 
std::numeric_limits<IndexType>::max();
       // A ChunkedArray with enough empty chunks can make the index of a chunk
       // exceed the logical index and thus the maximum value of IndexType.
-      const bool chunk_index_fits_on_type =
-          static_cast<uint64_t>(offsets_.size() - 1) <= kMaxIndexTypeValue;
+      const bool chunk_index_fits_on_type = num_chunks() <= kMaxIndexTypeValue;
       if (ARROW_PREDICT_FALSE(!chunk_index_fits_on_type)) {
         return false;
       }
@@ -194,17 +195,18 @@ struct ARROW_EXPORT ChunkResolver {
       using U = std::make_unsigned_t<IndexType>;
       ResolveManyImpl(n_indices, reinterpret_cast<const U*>(logical_index_vec),
                       
reinterpret_cast<TypedChunkLocation<U>*>(out_chunk_location_vec),
-                      static_cast<U>(chunk_hint));
+                      static_cast<int32_t>(chunk_hint));
     } else {
       static_assert(std::is_unsigned_v<IndexType>);
-      ResolveManyImpl(n_indices, logical_index_vec, out_chunk_location_vec, 
chunk_hint);
+      ResolveManyImpl(n_indices, logical_index_vec, out_chunk_location_vec,
+                      static_cast<int32_t>(chunk_hint));
     }
     return true;
   }
 
  private:
   template <bool StoreCachedChunk>
-  inline int64_t ResolveChunkIndex(int64_t index, int64_t cached_chunk) const {
+  inline int64_t ResolveChunkIndex(int64_t index, int32_t cached_chunk) const {
     // It is common for algorithms sequentially processing arrays to make 
consecutive
     // accesses at a relatively small distance from each other, hence often 
falling in the
     // same chunk.
@@ -212,16 +214,17 @@ struct ARROW_EXPORT ChunkResolver {
     // This is guaranteed when merging (assuming each side of the merge uses 
its
     // own resolver), and is the most common case in recursive invocations of
     // partitioning.
-    const auto num_offsets = static_cast<int64_t>(offsets_.size());
+    const auto num_offsets = static_cast<uint32_t>(offsets_.size());
     const int64_t* offsets = offsets_.data();
     if (ARROW_PREDICT_TRUE(index >= offsets[cached_chunk]) &&
-        (cached_chunk + 1 == num_offsets || index < offsets[cached_chunk + 
1])) {
+        (static_cast<uint32_t>(cached_chunk + 1) == num_offsets ||
+         index < offsets[cached_chunk + 1])) {
       return cached_chunk;
     }
     // lo < hi is guaranteed by `num_offsets = chunks.size() + 1`
     const auto chunk_index = Bisect(index, offsets, /*lo=*/0, 
/*hi=*/num_offsets);
     if constexpr (StoreCachedChunk) {
-      assert(chunk_index < static_cast<int64_t>(offsets_.size()));
+      assert(static_cast<uint32_t>(chunk_index) < 
static_cast<uint32_t>(offsets_.size()));
       cached_chunk_.store(chunk_index, std::memory_order_relaxed);
     }
     return chunk_index;
@@ -230,13 +233,13 @@ struct ARROW_EXPORT ChunkResolver {
   /// \pre all the pre-conditions of ChunkResolver::ResolveMany()
   /// \pre num_offsets - 1 <= std::numeric_limits<IndexType>::max()
   void ResolveManyImpl(int64_t, const uint8_t*, TypedChunkLocation<uint8_t>*,
-                       uint8_t) const;
+                       int32_t) const;
   void ResolveManyImpl(int64_t, const uint16_t*, TypedChunkLocation<uint16_t>*,
-                       uint16_t) const;
+                       int32_t) const;
   void ResolveManyImpl(int64_t, const uint32_t*, TypedChunkLocation<uint32_t>*,
-                       uint32_t) const;
+                       int32_t) const;
   void ResolveManyImpl(int64_t, const uint64_t*, TypedChunkLocation<uint64_t>*,
-                       uint64_t) const;
+                       int32_t) const;
 
  public:
   /// \brief Find the index of the chunk that contains the logical index.
@@ -249,15 +252,15 @@ struct ARROW_EXPORT ChunkResolver {
   /// \pre index >= 0 (otherwise, when index is negative, hi-1 is returned)
   /// \pre lo < hi
   /// \pre lo >= 0 && hi <= offsets_.size()
-  static inline int64_t Bisect(int64_t index, const int64_t* offsets, int64_t 
lo,
-                               int64_t hi) {
+  static inline int32_t Bisect(int64_t index, const int64_t* offsets, int32_t 
lo,
+                               int32_t hi) {
     return Bisect(static_cast<uint64_t>(index),
-                  reinterpret_cast<const uint64_t*>(offsets), 
static_cast<uint64_t>(lo),
-                  static_cast<uint64_t>(hi));
+                  reinterpret_cast<const uint64_t*>(offsets), 
static_cast<uint32_t>(lo),
+                  static_cast<uint32_t>(hi));
   }
 
-  static inline int64_t Bisect(uint64_t index, const uint64_t* offsets, 
uint64_t lo,
-                               uint64_t hi) {
+  static inline int32_t Bisect(uint64_t index, const uint64_t* offsets, 
uint32_t lo,
+                               uint32_t hi) {
     // Similar to std::upper_bound(), but slightly different as our offsets
     // array always starts with 0.
     auto n = hi - lo;
@@ -265,8 +268,8 @@ struct ARROW_EXPORT ChunkResolver {
     // (lo < hi is guaranteed by the precondition).
     assert(n > 1 && "lo < hi is a precondition of Bisect");
     do {
-      const uint64_t m = n >> 1;
-      const uint64_t mid = lo + m;
+      const uint32_t m = n >> 1;
+      const uint32_t mid = lo + m;
       if (index >= offsets[mid]) {
         lo = mid;
         n -= m;
diff --git a/cpp/src/arrow/chunk_resolver_benchmark.cc 
b/cpp/src/arrow/chunk_resolver_benchmark.cc
new file mode 100644
index 0000000000..0756de3fbe
--- /dev/null
+++ b/cpp/src/arrow/chunk_resolver_benchmark.cc
@@ -0,0 +1,166 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "benchmark/benchmark.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <random>
+#include <vector>
+
+#include "arrow/chunk_resolver.h"
+#include "arrow/util/benchmark_util.h"
+#include "arrow/util/pcg_random.h"
+
+namespace arrow {
+
+using internal::ChunkResolver;
+using internal::TypedChunkLocation;
+
+namespace {
+
+int64_t constexpr kChunkedArrayLength = std::numeric_limits<uint16_t>::max();
+
+struct ResolveManyBenchmark {
+  benchmark::State& state;
+  random::pcg64 rng;
+  // Values from the state.range(i)
+  int64_t chunked_array_length;
+  int32_t num_chunks;
+  int64_t num_logical_indices;
+
+  explicit ResolveManyBenchmark(benchmark::State& state)
+      : state(state),
+        rng(42),
+        chunked_array_length(state.range(0)),
+        num_chunks(static_cast<int32_t>(state.range(1))),
+        num_logical_indices(state.range(2)) {}
+
+  std::vector<int64_t> GenChunkedArrayOffsets() {
+    std::uniform_int_distribution<int64_t> offset_gen(1, chunked_array_length);
+    std::vector<int64_t> offsets;
+    offsets.reserve(num_chunks + 1);
+    offsets.push_back(0);
+    while (offsets.size() < static_cast<size_t>(num_chunks)) {
+      offsets.push_back(offset_gen(rng));
+    }
+    offsets.push_back(chunked_array_length);
+    std::sort(offsets.begin() + 1, offsets.end());
+    return offsets;
+  }
+
+  template <typename IndexType>
+  std::vector<IndexType> GenRandomIndices(IndexType max_index, bool sorted) {
+    std::uniform_int_distribution<IndexType> index_gen(0, max_index);
+    std::vector<IndexType> indices;
+    indices.reserve(num_logical_indices);
+    while (indices.size() < static_cast<size_t>(num_logical_indices)) {
+      indices.push_back(index_gen(rng));
+    }
+    if (sorted) {
+      std::sort(indices.begin(), indices.end());
+    }
+    return indices;
+  }
+
+  template <typename IndexType>
+  void Bench(bool sorted) {
+    if constexpr (sizeof(IndexType) < 8) {
+      constexpr uint64_t kLimitIndex = std::numeric_limits<IndexType>::max();
+      ARROW_CHECK_LE(static_cast<uint64_t>(chunked_array_length), kLimitIndex);
+    }
+    const auto max_random_index = static_cast<IndexType>(chunked_array_length);
+    auto offsets = GenChunkedArrayOffsets();
+    auto logical_indices = GenRandomIndices<IndexType>(max_random_index, 
sorted);
+    ChunkResolver resolver(std::move(offsets));
+    std::vector<TypedChunkLocation<IndexType>> 
chunk_location_vec(num_logical_indices);
+    BENCHMARK_UNUSED bool all_succeeded = true;
+    for (auto _ : state) {
+      const bool success = resolver.ResolveMany<IndexType>(
+          num_logical_indices, logical_indices.data(), 
chunk_location_vec.data());
+      all_succeeded &= success;
+    }
+    ARROW_CHECK(all_succeeded);
+    state.SetItemsProcessed(state.iterations() * num_logical_indices);
+  }
+};
+
+template <typename IndexType>
+void ResolveManySetArgs(benchmark::internal::Benchmark* bench) {
+  constexpr int32_t kNonAligned = 3;
+  const int64_t kNumIndicesFew = (kChunkedArrayLength >> 7) - kNonAligned;
+  const int64_t kNumIndicesMany = (kChunkedArrayLength >> 1) - kNonAligned;
+
+  bench->ArgNames({"chunked_array_length", "num_chunks", "num_indices"});
+
+  switch (sizeof(IndexType)) {
+    case 1:
+      // Unexpected. See comments below.
+    case 2:
+    case 4:
+    case 8:
+      bench->Args({kChunkedArrayLength, /*num_chunks*/ 10000, kNumIndicesFew});
+      bench->Args({kChunkedArrayLength, /*num_chunks*/ 100, kNumIndicesFew});
+      bench->Args({kChunkedArrayLength, /*num_chunks*/ 10000, 
kNumIndicesMany});
+      bench->Args({kChunkedArrayLength, /*num_chunks*/ 100, kNumIndicesMany});
+      break;
+  }
+}
+
+template <typename IndexType>
+void ResolveManyBench(benchmark::State& state, bool sorted) {
+  ResolveManyBenchmark{state}.Bench<IndexType>(sorted);
+}
+
+void ResolveManyUInt16Random(benchmark::State& state) {
+  ResolveManyBench<uint16_t>(state, false);
+}
+
+void ResolveManyUInt32Random(benchmark::State& state) {
+  ResolveManyBench<uint32_t>(state, false);
+}
+
+void ResolveManyUInt64Random(benchmark::State& state) {
+  ResolveManyBench<uint64_t>(state, false);
+}
+
+void ResolveManyUInt16Sorted(benchmark::State& state) {
+  ResolveManyBench<uint16_t>(state, true);
+}
+
+void ResolveManyUInt32Sorted(benchmark::State& state) {
+  ResolveManyBench<uint32_t>(state, true);
+}
+
+void ResolveManyUInt64Sorted(benchmark::State& state) {
+  ResolveManyBench<uint64_t>(state, true);
+}
+
+}  // namespace
+
+// We don't benchmark with uint8_t because it's too fast -- any meaningful
+// array of logical indices will contain all the possible values.
+
+BENCHMARK(ResolveManyUInt16Random)->Apply(ResolveManySetArgs<uint16_t>);
+BENCHMARK(ResolveManyUInt32Random)->Apply(ResolveManySetArgs<uint32_t>);
+BENCHMARK(ResolveManyUInt64Random)->Apply(ResolveManySetArgs<uint64_t>);
+
+BENCHMARK(ResolveManyUInt16Sorted)->Apply(ResolveManySetArgs<uint16_t>);
+BENCHMARK(ResolveManyUInt32Sorted)->Apply(ResolveManySetArgs<uint32_t>);
+BENCHMARK(ResolveManyUInt64Sorted)->Apply(ResolveManySetArgs<uint64_t>);
+
+}  // namespace arrow
diff --git a/cpp/src/arrow/chunked_array.cc b/cpp/src/arrow/chunked_array.cc
index dd6aa51534..2ea2f514b9 100644
--- a/cpp/src/arrow/chunked_array.cc
+++ b/cpp/src/arrow/chunked_array.cc
@@ -51,11 +51,11 @@ ChunkedArray::ChunkedArray(ArrayVector chunks, 
std::shared_ptr<DataType> type)
       null_count_(0),
       chunk_resolver_{chunks_} {
   if (type_ == nullptr) {
-    ARROW_CHECK_GT(chunks_.size(), 0)
+    ARROW_CHECK_GT(chunks_.size(), static_cast<size_t>(0))
         << "cannot construct ChunkedArray from empty vector and omitted type";
     type_ = chunks_[0]->type();
   }
-
+  ARROW_CHECK_LE(chunks.size(), 
static_cast<size_t>(std::numeric_limits<int>::max()));
   for (const auto& chunk : chunks_) {
     length_ += chunk->length();
     null_count_ += chunk->null_count();
diff --git a/cpp/src/arrow/chunked_array_test.cc 
b/cpp/src/arrow/chunked_array_test.cc
index bf9d4af7c7..f98dde689c 100644
--- a/cpp/src/arrow/chunked_array_test.cc
+++ b/cpp/src/arrow/chunked_array_test.cc
@@ -32,6 +32,7 @@
 #include "arrow/type.h"
 #include "arrow/util/endian.h"
 #include "arrow/util/key_value_metadata.h"
+#include "arrow/util/pcg_random.h"
 
 namespace arrow {
 
@@ -373,10 +374,27 @@ TEST(TestChunkResolver, Resolve) {
   ASSERT_EQ(resolver.Resolve(10).chunk_index, 3);
 }
 
+template <class RNG>
+std::vector<int64_t> GenChunkedArrayOffsets(RNG& rng, int32_t num_chunks,
+                                            int64_t chunked_array_len) {
+  std::uniform_int_distribution<int64_t> offset_gen(1, chunked_array_len - 1);
+  std::vector<int64_t> offsets;
+  offsets.reserve(num_chunks + 1);
+  offsets.push_back(0);
+  while (offsets.size() < static_cast<size_t>(num_chunks)) {
+    offsets.push_back(offset_gen(rng));
+  }
+  offsets.push_back(chunked_array_len);
+  std::sort(offsets.begin() + 1, offsets.end());
+  return offsets;
+}
+
 template <typename T>
 class TestChunkResolverMany : public ::testing::Test {
  public:
   using IndexType = T;
+  static constexpr int32_t kMaxInt32 = std::numeric_limits<int32_t>::max();
+  static constexpr uint64_t kMaxValidIndex = 
std::numeric_limits<IndexType>::max();
 
   Result<std::vector<ChunkLocation>> ResolveMany(
       const ChunkResolver& resolver, const std::vector<IndexType>& 
logical_index_vec) {
@@ -510,6 +528,55 @@ class TestChunkResolverMany : public ::testing::Test {
       ASSERT_OK(ResolveMany(resolver_with_empty, logical_index_vec));
     }
   }
+
+  void TestRandomInput(int32_t num_chunks, int64_t chunked_array_len) {
+    random::pcg64 rng(42);
+
+    // Generate random chunk offsets...
+    auto offsets = GenChunkedArrayOffsets(rng, num_chunks, chunked_array_len);
+    ASSERT_EQ(offsets.size(), static_cast<size_t>(num_chunks) + 1);
+    // ...and ensure there is at least one empty chunk.
+    std::uniform_int_distribution<int32_t> chunk_index_gen(
+        1, static_cast<int32_t>(num_chunks - 1));
+    auto chunk_index = chunk_index_gen(rng);
+    offsets[chunk_index] = offsets[chunk_index - 1];
+
+    // Generate random query array of logical indices...
+    const auto num_logical_indices = 3 * static_cast<int64_t>(num_chunks) / 2;
+    std::vector<IndexType> logical_index_vec;
+    logical_index_vec.reserve(num_logical_indices);
+    std::uniform_int_distribution<uint64_t> logical_index_gen(1, 
kMaxValidIndex);
+    for (int64_t i = 0; i < num_logical_indices; i++) {
+      const auto index = static_cast<IndexType>(logical_index_gen(rng));
+      logical_index_vec.push_back(index);
+    }
+    // ...and sprinkle some extreme logical index values.
+    std::uniform_int_distribution<int64_t> position_gen(0, 
logical_index_vec.size() - 1);
+    for (int i = 0; i < 2; i++) {
+      auto max_valid_index =
+          std::min(kMaxValidIndex, static_cast<uint64_t>(chunked_array_len));
+      // zero and last valid logical index
+      logical_index_vec[position_gen(rng)] = 0;
+      logical_index_vec[position_gen(rng)] = 
static_cast<IndexType>(max_valid_index - 1);
+      // out of  bounds indices
+      logical_index_vec[position_gen(rng)] = 
static_cast<IndexType>(max_valid_index);
+      if (max_valid_index < kMaxValidIndex) {
+        logical_index_vec[position_gen(rng)] =
+            static_cast<IndexType>(max_valid_index + 1);
+      }
+    }
+
+    ChunkResolver resolver(std::move(offsets));
+    CheckResolveMany(resolver, logical_index_vec);
+  }
+
+  void TestRandomInput() {
+    const int64_t num_chunks = static_cast<int64_t>(
+        std::min(kMaxValidIndex - 1, static_cast<uint64_t>(1) << 16));
+    const int64_t avg_chunk_length = 20;
+    const int64_t chunked_array_len = num_chunks * 2 * avg_chunk_length;
+    TestRandomInput(num_chunks, chunked_array_len);
+  }
 };
 
 TYPED_TEST_SUITE(TestChunkResolverMany, IndexTypes);
@@ -517,5 +584,6 @@ TYPED_TEST_SUITE(TestChunkResolverMany, IndexTypes);
 TYPED_TEST(TestChunkResolverMany, Basics) { this->TestBasics(); }
 TYPED_TEST(TestChunkResolverMany, OutOfBounds) { this->TestOutOfBounds(); }
 TYPED_TEST(TestChunkResolverMany, Overflow) { this->TestOverflow(); }
+TYPED_TEST(TestChunkResolverMany, RandomInput) { this->TestRandomInput(); }
 
 }  // namespace arrow

Reply via email to