Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-08-27 08:47:18

0001 // Licensed to the Apache Software Foundation (ASF) under one
0002 // or more contributor license agreements.  See the NOTICE file
0003 // distributed with this work for additional information
0004 // regarding copyright ownership.  The ASF licenses this file
0005 // to you under the Apache License, Version 2.0 (the
0006 // "License"); you may not use this file except in compliance
0007 // with the License.  You may obtain a copy of the License at
0008 //
0009 //   http://www.apache.org/licenses/LICENSE-2.0
0010 //
0011 // Unless required by applicable law or agreed to in writing,
0012 // software distributed under the License is distributed on an
0013 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
0014 // KIND, either express or implied.  See the License for the
0015 // specific language governing permissions and limitations
0016 // under the License.
0017 
0018 #pragma once
0019 
0020 #include <atomic>
0021 #include <cassert>
0022 #include <cstdint>
0023 #include <limits>
0024 #include <type_traits>
0025 #include <vector>
0026 
0027 #include "arrow/type_fwd.h"
0028 #include "arrow/util/macros.h"
0029 #include "arrow/util/span.h"
0030 
0031 namespace arrow {
0032 
0033 class ChunkResolver;
0034 
0035 template <typename IndexType>
0036 struct ARROW_EXPORT TypedChunkLocation {
0037   /// \brief Index of the chunk in the array of chunks
0038   ///
0039   /// The value is always in the range `[0, chunks.size()]`. `chunks.size()` is used
0040   /// to represent out-of-bounds locations.
0041   IndexType chunk_index = 0;
0042 
0043   /// \brief Index of the value in the chunk
0044   ///
0045   /// The value is UNDEFINED if `chunk_index >= chunks.size()`
0046   IndexType index_in_chunk = 0;
0047 
0048   TypedChunkLocation() = default;
0049 
0050   TypedChunkLocation(IndexType chunk_index, IndexType index_in_chunk)
0051       : chunk_index(chunk_index), index_in_chunk(index_in_chunk) {
0052     static_assert(sizeof(TypedChunkLocation<IndexType>) == 2 * sizeof(IndexType));
0053     static_assert(alignof(TypedChunkLocation<IndexType>) == alignof(IndexType));
0054   }
0055 
0056   bool operator==(TypedChunkLocation other) const {
0057     return chunk_index == other.chunk_index && index_in_chunk == other.index_in_chunk;
0058   }
0059 };
0060 
0061 using ChunkLocation = TypedChunkLocation<int64_t>;
0062 
0063 /// \brief An utility that incrementally resolves logical indices into
0064 /// physical indices in a chunked array.
0065 class ARROW_EXPORT ChunkResolver {
0066  private:
0067   /// \brief Array containing `chunks.size() + 1` offsets.
0068   ///
0069   /// `offsets_[i]` is the starting logical index of chunk `i`. `offsets_[0]` is always 0
0070   /// and `offsets_[chunks.size()]` is the logical length of the chunked array.
0071   std::vector<int64_t> offsets_;
0072 
0073   /// \brief Cache of the index of the last resolved chunk.
0074   ///
0075   /// \invariant `cached_chunk_ in [0, chunks.size()]`
0076   mutable std::atomic<int32_t> cached_chunk_;
0077 
0078  public:
0079   explicit ChunkResolver(const ArrayVector& chunks) noexcept;
0080   explicit ChunkResolver(util::span<const Array* const> chunks) noexcept;
0081   explicit ChunkResolver(const RecordBatchVector& batches) noexcept;
0082 
0083   /// \brief Construct a ChunkResolver from a vector of chunks.size() + 1 offsets.
0084   ///
0085   /// The first offset must be 0 and the last offset must be the logical length of the
0086   /// chunked array. Each offset before the last represents the starting logical index of
0087   /// the corresponding chunk.
0088   explicit ChunkResolver(std::vector<int64_t> offsets) noexcept
0089       : offsets_(std::move(offsets)), cached_chunk_(0) {
0090 #ifndef NDEBUG
0091     assert(offsets_.size() >= 1);
0092     assert(offsets_[0] == 0);
0093     for (size_t i = 1; i < offsets_.size(); i++) {
0094       assert(offsets_[i] >= offsets_[i - 1]);
0095     }
0096     assert(offsets_.size() - 1 <=
0097            static_cast<size_t>(std::numeric_limits<int32_t>::max()));
0098 #endif
0099   }
0100 
0101   ChunkResolver(ChunkResolver&& other) noexcept;
0102   ChunkResolver& operator=(ChunkResolver&& other) noexcept;
0103 
0104   ChunkResolver(const ChunkResolver& other) noexcept;
0105   ChunkResolver& operator=(const ChunkResolver& other) noexcept;
0106 
0107   int64_t logical_array_length() const { return offsets_.back(); }
0108   int32_t num_chunks() const { return static_cast<int32_t>(offsets_.size() - 1); }
0109 
0110   int64_t chunk_length(int64_t chunk_index) const {
0111     return offsets_[chunk_index + 1] - offsets_[chunk_index];
0112   }
0113 
0114   /// \brief Resolve a logical index to a ChunkLocation.
0115   ///
0116   /// The returned ChunkLocation contains the chunk index and the within-chunk index
0117   /// equivalent to the logical index.
0118   ///
0119   /// \pre `index >= 0`
0120   /// \post `location.chunk_index` in `[0, chunks.size()]`
0121   /// \param index The logical index to resolve
0122   /// \return ChunkLocation with a valid chunk_index if index is within
0123   ///         bounds, or with `chunk_index == chunks.size()` if logical index is
0124   ///         `>= chunked_array.length()`.
0125   inline ChunkLocation Resolve(int64_t index) const {
0126     const auto cached_chunk = cached_chunk_.load(std::memory_order_relaxed);
0127     const auto chunk_index =
0128         ResolveChunkIndex</*StoreCachedChunk=*/true>(index, cached_chunk);
0129     return ChunkLocation{chunk_index, index - offsets_[chunk_index]};
0130   }
0131 
0132   /// \brief Resolve a logical index to a ChunkLocation.
0133   ///
0134   /// The returned ChunkLocation contains the chunk index and the within-chunk index
0135   /// equivalent to the logical index.
0136   ///
0137   /// \pre `index >= 0`
0138   /// \post `location.chunk_index` in `[0, chunks.size()]`
0139   /// \param index The logical index to resolve
0140   /// \param hint ChunkLocation{} or the last ChunkLocation returned by
0141   ///             this ChunkResolver.
0142   /// \return ChunkLocation with a valid chunk_index if index is within
0143   ///         bounds, or with `chunk_index == chunks.size()` if logical index is
0144   ///         `>= chunked_array.length()`.
0145   inline ChunkLocation ResolveWithHint(int64_t index, ChunkLocation hint) const {
0146     assert(hint.chunk_index < static_cast<uint32_t>(offsets_.size()));
0147     const auto chunk_index = ResolveChunkIndex</*StoreCachedChunk=*/false>(
0148         index, static_cast<int32_t>(hint.chunk_index));
0149     return ChunkLocation{chunk_index, index - offsets_[chunk_index]};
0150   }
0151 
0152   /// \brief Resolve `n_indices` logical indices to chunk indices.
0153   ///
0154   /// \pre 0 <= logical_index_vec[i] < logical_array_length()
0155   ///      (for well-defined and valid chunk index results)
0156   /// \pre out_chunk_location_vec has space for `n_indices` locations
0157   /// \pre chunk_hint in [0, chunks.size()]
0158   /// \post out_chunk_location_vec[i].chunk_index in [0, chunks.size()] for i in [0, n)
0159   /// \post if logical_index_vec[i] >= chunked_array.length(), then
0160   ///       out_chunk_location_vec[i].chunk_index == chunks.size()
0161   ///       and out_chunk_location_vec[i].index_in_chunk is UNDEFINED (can be
0162   ///       out-of-bounds)
0163   /// \post if logical_index_vec[i] < 0, then both values in out_chunk_index_vec[i]
0164   ///       are UNDEFINED
0165   ///
0166   /// \param n_indices The number of logical indices to resolve
0167   /// \param logical_index_vec The logical indices to resolve
0168   /// \param out_chunk_location_vec The output array where the locations will be written
0169   /// \param chunk_hint 0 or the last chunk_index produced by ResolveMany
0170   /// \return false iff chunks.size() > std::numeric_limits<IndexType>::max()
0171   template <typename IndexType>
0172   [[nodiscard]] bool ResolveMany(int64_t n_indices, const IndexType* logical_index_vec,
0173                                  TypedChunkLocation<IndexType>* out_chunk_location_vec,
0174                                  IndexType chunk_hint = 0) const {
0175     if constexpr (sizeof(IndexType) < sizeof(uint32_t)) {
0176       // The max value returned by Bisect is `offsets.size() - 1` (= chunks.size()).
0177       constexpr int64_t kMaxIndexTypeValue = std::numeric_limits<IndexType>::max();
0178       // A ChunkedArray with enough empty chunks can make the index of a chunk
0179       // exceed the logical index and thus the maximum value of IndexType.
0180       const bool chunk_index_fits_on_type = num_chunks() <= kMaxIndexTypeValue;
0181       if (ARROW_PREDICT_FALSE(!chunk_index_fits_on_type)) {
0182         return false;
0183       }
0184       // Since an index-in-chunk cannot possibly exceed the logical index being
0185       // queried, we don't have to worry about these values not fitting on IndexType.
0186     }
0187     if constexpr (std::is_signed_v<IndexType>) {
0188       // We interpret signed integers as unsigned and avoid having to generate double
0189       // the amount of binary code to handle each integer width.
0190       //
0191       // Negative logical indices can become large values when cast to unsigned, and
0192       // they are gracefully handled by ResolveManyImpl, but both the chunk index
0193       // and the index in chunk values will be undefined in these cases. This
0194       // happend because int8_t(-1) == uint8_t(255) and 255 could be a valid
0195       // logical index in the chunked array.
0196       using U = std::make_unsigned_t<IndexType>;
0197       ResolveManyImpl(n_indices, reinterpret_cast<const U*>(logical_index_vec),
0198                       reinterpret_cast<TypedChunkLocation<U>*>(out_chunk_location_vec),
0199                       static_cast<int32_t>(chunk_hint));
0200     } else {
0201       static_assert(std::is_unsigned_v<IndexType>);
0202       ResolveManyImpl(n_indices, logical_index_vec, out_chunk_location_vec,
0203                       static_cast<int32_t>(chunk_hint));
0204     }
0205     return true;
0206   }
0207 
0208  private:
0209   template <bool StoreCachedChunk>
0210   inline int64_t ResolveChunkIndex(int64_t index, int32_t cached_chunk) const {
0211     // It is common for algorithms sequentially processing arrays to make consecutive
0212     // accesses at a relatively small distance from each other, hence often falling in the
0213     // same chunk.
0214     //
0215     // This is guaranteed when merging (assuming each side of the merge uses its
0216     // own resolver), and is the most common case in recursive invocations of
0217     // partitioning.
0218     const auto num_offsets = static_cast<uint32_t>(offsets_.size());
0219     const int64_t* offsets = offsets_.data();
0220     if (ARROW_PREDICT_TRUE(index >= offsets[cached_chunk]) &&
0221         (static_cast<uint32_t>(cached_chunk + 1) == num_offsets ||
0222          index < offsets[cached_chunk + 1])) {
0223       return cached_chunk;
0224     }
0225     // lo < hi is guaranteed by `num_offsets = chunks.size() + 1`
0226     const auto chunk_index = Bisect(index, offsets, /*lo=*/0, /*hi=*/num_offsets);
0227     if constexpr (StoreCachedChunk) {
0228       assert(static_cast<uint32_t>(chunk_index) < static_cast<uint32_t>(offsets_.size()));
0229       cached_chunk_.store(chunk_index, std::memory_order_relaxed);
0230     }
0231     return chunk_index;
0232   }
0233 
0234   /// \pre all the pre-conditions of ChunkResolver::ResolveMany()
0235   /// \pre num_offsets - 1 <= std::numeric_limits<IndexType>::max()
0236   void ResolveManyImpl(int64_t, const uint8_t*, TypedChunkLocation<uint8_t>*,
0237                        int32_t) const;
0238   void ResolveManyImpl(int64_t, const uint16_t*, TypedChunkLocation<uint16_t>*,
0239                        int32_t) const;
0240   void ResolveManyImpl(int64_t, const uint32_t*, TypedChunkLocation<uint32_t>*,
0241                        int32_t) const;
0242   void ResolveManyImpl(int64_t, const uint64_t*, TypedChunkLocation<uint64_t>*,
0243                        int32_t) const;
0244 
0245  public:
0246   /// \brief Find the index of the chunk that contains the logical index.
0247   ///
0248   /// Any non-negative index is accepted. When `hi=num_offsets`, the largest
0249   /// possible return value is `num_offsets-1` which is equal to
0250   /// `chunks.size()`. Which is returned when the logical index is greater or
0251   /// equal the logical length of the chunked array.
0252   ///
0253   /// \pre index >= 0 (otherwise, when index is negative, hi-1 is returned)
0254   /// \pre lo < hi
0255   /// \pre lo >= 0 && hi <= offsets_.size()
0256   static inline int32_t Bisect(int64_t index, const int64_t* offsets, int32_t lo,
0257                                int32_t hi) {
0258     return Bisect(static_cast<uint64_t>(index),
0259                   reinterpret_cast<const uint64_t*>(offsets), static_cast<uint32_t>(lo),
0260                   static_cast<uint32_t>(hi));
0261   }
0262 
0263   static inline int32_t Bisect(uint64_t index, const uint64_t* offsets, uint32_t lo,
0264                                uint32_t hi) {
0265     // Similar to std::upper_bound(), but slightly different as our offsets
0266     // array always starts with 0.
0267     auto n = hi - lo;
0268     // First iteration does not need to check for n > 1
0269     // (lo < hi is guaranteed by the precondition).
0270     assert(n > 1 && "lo < hi is a precondition of Bisect");
0271     do {
0272       const uint32_t m = n >> 1;
0273       const uint32_t mid = lo + m;
0274       if (index >= offsets[mid]) {
0275         lo = mid;
0276         n -= m;
0277       } else {
0278         n = m;
0279       }
0280     } while (n > 1);
0281     return lo;
0282   }
0283 };
0284 
0285 // Explicitly instantiate template base struct, for DLL linking on Windows
0286 template struct TypedChunkLocation<int32_t>;
0287 template struct TypedChunkLocation<int16_t>;
0288 template struct TypedChunkLocation<int8_t>;
0289 template struct TypedChunkLocation<uint8_t>;
0290 template struct TypedChunkLocation<uint16_t>;
0291 template struct TypedChunkLocation<uint32_t>;
0292 template struct TypedChunkLocation<int64_t>;
0293 template struct TypedChunkLocation<uint64_t>;
0294 }  // namespace arrow