Merging PR_218 openai_rev package with new streamlit chat app
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arrow/result.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
template <typename InputIterator, typename OutputIterator, typename UnaryOperation>
|
||||
Status MaybeTransform(InputIterator first, InputIterator last, OutputIterator out,
|
||||
UnaryOperation unary_op) {
|
||||
for (; first != last; ++first, (void)++out) {
|
||||
ARROW_ASSIGN_OR_RAISE(*out, unary_op(*first));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "arrow/util/bit_util.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
struct BitmapWordAlignParams {
|
||||
int64_t leading_bits;
|
||||
int64_t trailing_bits;
|
||||
int64_t trailing_bit_offset;
|
||||
const uint8_t* aligned_start;
|
||||
int64_t aligned_bits;
|
||||
int64_t aligned_words;
|
||||
};
|
||||
|
||||
// Compute parameters for accessing a bitmap using aligned word instructions.
|
||||
// The returned parameters describe:
|
||||
// - a leading area of size `leading_bits` before the aligned words
|
||||
// - a word-aligned area of size `aligned_bits`
|
||||
// - a trailing area of size `trailing_bits` after the aligned words
|
||||
template <uint64_t ALIGN_IN_BYTES>
|
||||
inline BitmapWordAlignParams BitmapWordAlign(const uint8_t* data, int64_t bit_offset,
|
||||
int64_t length) {
|
||||
static_assert(bit_util::IsPowerOf2(ALIGN_IN_BYTES),
|
||||
"ALIGN_IN_BYTES should be a positive power of two");
|
||||
constexpr uint64_t ALIGN_IN_BITS = ALIGN_IN_BYTES * 8;
|
||||
|
||||
BitmapWordAlignParams p;
|
||||
|
||||
// Compute a "bit address" that we can align up to ALIGN_IN_BITS.
|
||||
// We don't care about losing the upper bits since we are only interested in the
|
||||
// difference between both addresses.
|
||||
const uint64_t bit_addr =
|
||||
reinterpret_cast<size_t>(data) * 8 + static_cast<uint64_t>(bit_offset);
|
||||
const uint64_t aligned_bit_addr = bit_util::RoundUpToPowerOf2(bit_addr, ALIGN_IN_BITS);
|
||||
|
||||
p.leading_bits = std::min<int64_t>(length, aligned_bit_addr - bit_addr);
|
||||
p.aligned_words = (length - p.leading_bits) / ALIGN_IN_BITS;
|
||||
p.aligned_bits = p.aligned_words * ALIGN_IN_BITS;
|
||||
p.trailing_bits = length - p.leading_bits - p.aligned_bits;
|
||||
p.trailing_bit_offset = bit_offset + p.leading_bits + p.aligned_bits;
|
||||
|
||||
p.aligned_start = data + (bit_offset + p.leading_bits) / 8;
|
||||
return p;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,145 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/util/launder.h"
|
||||
#include "arrow/util/macros.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
template <typename T>
|
||||
class AlignedStorage {
|
||||
public:
|
||||
static constexpr bool can_memcpy = std::is_trivial<T>::value;
|
||||
|
||||
constexpr T* get() noexcept {
|
||||
return arrow::internal::launder(reinterpret_cast<T*>(&data_));
|
||||
}
|
||||
|
||||
constexpr const T* get() const noexcept {
|
||||
// Use fully qualified name to avoid ambiguities with MSVC (ARROW-14800)
|
||||
return arrow::internal::launder(reinterpret_cast<const T*>(&data_));
|
||||
}
|
||||
|
||||
void destroy() noexcept {
|
||||
if (!std::is_trivially_destructible<T>::value) {
|
||||
get()->~T();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... A>
|
||||
void construct(A&&... args) noexcept {
|
||||
new (&data_) T(std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
void assign(V&& v) noexcept {
|
||||
*get() = std::forward<V>(v);
|
||||
}
|
||||
|
||||
void move_construct(AlignedStorage* other) noexcept {
|
||||
new (&data_) T(std::move(*other->get()));
|
||||
}
|
||||
|
||||
void move_assign(AlignedStorage* other) noexcept { *get() = std::move(*other->get()); }
|
||||
|
||||
template <bool CanMemcpy = can_memcpy>
|
||||
static typename std::enable_if<CanMemcpy>::type move_construct_several(
|
||||
AlignedStorage* ARROW_RESTRICT src, AlignedStorage* ARROW_RESTRICT dest, size_t n,
|
||||
size_t memcpy_length) noexcept {
|
||||
memcpy(dest->get(), src->get(), memcpy_length * sizeof(T));
|
||||
}
|
||||
|
||||
template <bool CanMemcpy = can_memcpy>
|
||||
static typename std::enable_if<CanMemcpy>::type
|
||||
move_construct_several_and_destroy_source(AlignedStorage* ARROW_RESTRICT src,
|
||||
AlignedStorage* ARROW_RESTRICT dest, size_t n,
|
||||
size_t memcpy_length) noexcept {
|
||||
memcpy(dest->get(), src->get(), memcpy_length * sizeof(T));
|
||||
}
|
||||
|
||||
template <bool CanMemcpy = can_memcpy>
|
||||
static typename std::enable_if<!CanMemcpy>::type move_construct_several(
|
||||
AlignedStorage* ARROW_RESTRICT src, AlignedStorage* ARROW_RESTRICT dest, size_t n,
|
||||
size_t memcpy_length) noexcept {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
new (dest[i].get()) T(std::move(*src[i].get()));
|
||||
}
|
||||
}
|
||||
|
||||
template <bool CanMemcpy = can_memcpy>
|
||||
static typename std::enable_if<!CanMemcpy>::type
|
||||
move_construct_several_and_destroy_source(AlignedStorage* ARROW_RESTRICT src,
|
||||
AlignedStorage* ARROW_RESTRICT dest, size_t n,
|
||||
size_t memcpy_length) noexcept {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
new (dest[i].get()) T(std::move(*src[i].get()));
|
||||
src[i].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
static void move_construct_several(AlignedStorage* ARROW_RESTRICT src,
|
||||
AlignedStorage* ARROW_RESTRICT dest,
|
||||
size_t n) noexcept {
|
||||
move_construct_several(src, dest, n, n);
|
||||
}
|
||||
|
||||
static void move_construct_several_and_destroy_source(
|
||||
AlignedStorage* ARROW_RESTRICT src, AlignedStorage* ARROW_RESTRICT dest,
|
||||
size_t n) noexcept {
|
||||
move_construct_several_and_destroy_source(src, dest, n, n);
|
||||
}
|
||||
|
||||
static void destroy_several(AlignedStorage* p, size_t n) noexcept {
|
||||
if (!std::is_trivially_destructible<T>::value) {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
p[i].destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
#if !defined(__clang__) && defined(__GNUC__) && defined(__i386__)
|
||||
// Workaround for GCC bug on i386:
|
||||
// alignof(int64 | float64) can give different results depending on the
|
||||
// compilation context, leading to internal ABI mismatch manifesting
|
||||
// in incorrect propagation of Result<int64 | float64> between
|
||||
// compilation units.
|
||||
// (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88115)
|
||||
static constexpr size_t alignment() {
|
||||
if (std::is_integral_v<T> && sizeof(T) == 8) {
|
||||
return 4;
|
||||
} else if (std::is_floating_point_v<T> && sizeof(T) == 8) {
|
||||
return 4;
|
||||
}
|
||||
return alignof(T);
|
||||
}
|
||||
|
||||
typename std::aligned_storage<sizeof(T), alignment()>::type data_;
|
||||
#else
|
||||
typename std::aligned_storage<sizeof(T), alignof(T)>::type data_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "arrow/type_fwd.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
template <typename T>
|
||||
using AsyncGenerator = std::function<Future<T>()>;
|
||||
|
||||
template <typename T, typename V>
|
||||
class MappingGenerator;
|
||||
|
||||
template <typename T, typename ComesAfter, typename IsNext>
|
||||
class SequencingGenerator;
|
||||
|
||||
template <typename T, typename V>
|
||||
class TransformingGenerator;
|
||||
|
||||
template <typename T>
|
||||
class SerialReadaheadGenerator;
|
||||
|
||||
template <typename T>
|
||||
class ReadaheadGenerator;
|
||||
|
||||
template <typename T>
|
||||
class PushGenerator;
|
||||
|
||||
template <typename T>
|
||||
class MergedGenerator;
|
||||
|
||||
template <typename T>
|
||||
struct Enumerated;
|
||||
|
||||
template <typename T>
|
||||
class EnumeratingGenerator;
|
||||
|
||||
template <typename T>
|
||||
class TransferringGenerator;
|
||||
|
||||
template <typename T>
|
||||
class BackgroundGenerator;
|
||||
|
||||
template <typename T>
|
||||
class GeneratorIterator;
|
||||
|
||||
template <typename T>
|
||||
struct CancellableGenerator;
|
||||
|
||||
template <typename T>
|
||||
class DefaultIfEmptyGenerator;
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,410 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/util/cancel.h"
|
||||
#include "arrow/util/functional.h"
|
||||
#include "arrow/util/future.h"
|
||||
#include "arrow/util/iterator.h"
|
||||
#include "arrow/util/mutex.h"
|
||||
#include "arrow/util/thread_pool.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
using internal::FnOnce;
|
||||
|
||||
namespace util {
|
||||
|
||||
/// A utility which keeps tracks of, and schedules, asynchronous tasks
|
||||
///
|
||||
/// An asynchronous task has a synchronous component and an asynchronous component.
|
||||
/// The synchronous component typically schedules some kind of work on an external
|
||||
/// resource (e.g. the I/O thread pool or some kind of kernel-based asynchronous
|
||||
/// resource like io_uring). The asynchronous part represents the work
|
||||
/// done on that external resource. Executing the synchronous part will be referred
|
||||
/// to as "submitting the task" since this usually includes submitting the asynchronous
|
||||
/// portion to the external thread pool.
|
||||
///
|
||||
/// By default the scheduler will submit the task (execute the synchronous part) as
|
||||
/// soon as it is added, assuming the underlying thread pool hasn't terminated or the
|
||||
/// scheduler hasn't aborted. In this mode, the scheduler is simply acting as
|
||||
/// a simple task group.
|
||||
///
|
||||
/// A task scheduler starts with an initial task. That task, and all subsequent tasks
|
||||
/// are free to add subtasks. Once all submitted tasks finish the scheduler will
|
||||
/// finish. Note, it is not an error to add additional tasks after a scheduler has
|
||||
/// aborted. These tasks will be ignored and never submitted. The scheduler returns a
|
||||
/// future which will complete when all submitted tasks have finished executing. Once all
|
||||
/// tasks have been finsihed the scheduler is invalid and should no longer be used.
|
||||
///
|
||||
/// Task failure (either the synchronous portion or the asynchronous portion) will cause
|
||||
/// the scheduler to enter an aborted state. The first such failure will be reported in
|
||||
/// the final task future.
|
||||
class ARROW_EXPORT AsyncTaskScheduler {
|
||||
public:
|
||||
/// Destructor for AsyncTaskScheduler
|
||||
///
|
||||
/// The lifetime of the task scheduled is managed automatically. The scheduler
|
||||
/// will remain valid while any tasks are running (and can always be safely accessed)
|
||||
/// within tasks) and will be destroyed as soon as all tasks have finished.
|
||||
virtual ~AsyncTaskScheduler() = default;
|
||||
/// An interface for a task
|
||||
///
|
||||
/// Users may want to override this, for example, to add priority
|
||||
/// information for use by a queue.
|
||||
class Task {
|
||||
public:
|
||||
virtual ~Task() = default;
|
||||
/// Submit the task
|
||||
///
|
||||
/// This will be called by the scheduler at most once when there
|
||||
/// is space to run the task. This is expected to be a fairly quick
|
||||
/// function that simply submits the actual task work to an external
|
||||
/// resource (e.g. I/O thread pool).
|
||||
///
|
||||
/// If this call fails then the scheduler will enter an aborted state.
|
||||
virtual Result<Future<>> operator()() = 0;
|
||||
/// The cost of the task
|
||||
///
|
||||
/// A ThrottledAsyncTaskScheduler can be used to limit the number of concurrent tasks.
|
||||
/// A custom cost may be used, for example, if you would like to limit the number of
|
||||
/// tasks based on the total expected RAM usage of the tasks (this is done in the
|
||||
/// scanner)
|
||||
virtual int cost() const { return 1; }
|
||||
};
|
||||
|
||||
/// Add a task to the scheduler
|
||||
///
|
||||
/// If the scheduler is in an aborted state this call will return false and the task
|
||||
/// will never be run. This is harmless and does not need to be guarded against.
|
||||
///
|
||||
/// The return value for this call can usually be ignored. There is little harm in
|
||||
/// attempting to add tasks to an aborted scheduler. It is only included for callers
|
||||
/// that want to avoid future task generation to save effort.
|
||||
///
|
||||
/// \param task the task to submit
|
||||
///
|
||||
/// \return true if the task was submitted or queued, false if the task was ignored
|
||||
virtual bool AddTask(std::unique_ptr<Task> task) = 0;
|
||||
|
||||
/// Adds an async generator to the scheduler
|
||||
///
|
||||
/// The async generator will be visited, one item at a time. Submitting a task
|
||||
/// will consist of polling the generator for the next future. The generator's future
|
||||
/// will then represent the task itself.
|
||||
///
|
||||
/// This visits the task serially without readahead. If readahead or parallelism
|
||||
/// is desired then it should be added in the generator itself.
|
||||
///
|
||||
/// The generator itself will be kept alive until all tasks have been completed.
|
||||
/// However, if the scheduler is aborted, the generator will be destroyed as soon as the
|
||||
/// next item would be requested.
|
||||
///
|
||||
/// \param generator the generator to submit to the scheduler
|
||||
/// \param visitor a function which visits each generator future as it completes
|
||||
template <typename T>
|
||||
bool AddAsyncGenerator(std::function<Future<T>()> generator,
|
||||
std::function<Status(const T&)> visitor);
|
||||
|
||||
template <typename Callable>
|
||||
struct SimpleTask : public Task {
|
||||
explicit SimpleTask(Callable callable) : callable(std::move(callable)) {}
|
||||
Result<Future<>> operator()() override { return callable(); }
|
||||
Callable callable;
|
||||
};
|
||||
|
||||
/// Add a task with cost 1 to the scheduler
|
||||
///
|
||||
/// \see AddTask for details
|
||||
template <typename Callable>
|
||||
bool AddSimpleTask(Callable callable) {
|
||||
return AddTask(std::make_unique<SimpleTask<Callable>>(std::move(callable)));
|
||||
}
|
||||
|
||||
/// Construct a scheduler
|
||||
///
|
||||
/// \param initial_task The initial task which is responsible for adding
|
||||
/// the first subtasks to the scheduler.
|
||||
/// \param abort_callback A callback that will be triggered immediately after a task
|
||||
/// fails while other tasks may still be running. Nothing needs to be done here,
|
||||
/// when a task fails the scheduler will stop accepting new tasks and eventually
|
||||
/// return the error. However, this callback can be used to more quickly end
|
||||
/// long running tasks that have already been submitted. Defaults to doing
|
||||
/// nothing.
|
||||
/// \param stop_token An optional stop token that will allow cancellation of the
|
||||
/// scheduler. This will be checked before each task is submitted and, in the
|
||||
/// event of a cancellation, the scheduler will enter an aborted state. This is
|
||||
/// a graceful cancellation and submitted tasks will still complete.
|
||||
/// \return A future that will be completed when the initial task and all subtasks have
|
||||
/// finished.
|
||||
static Future<> Make(
|
||||
FnOnce<Status(AsyncTaskScheduler*)> initial_task,
|
||||
FnOnce<void(const Status&)> abort_callback = [](const Status&) {},
|
||||
StopToken stop_token = StopToken::Unstoppable());
|
||||
};
|
||||
|
||||
class ARROW_EXPORT ThrottledAsyncTaskScheduler : public AsyncTaskScheduler {
|
||||
public:
|
||||
/// An interface for a task queue
|
||||
///
|
||||
/// A queue's methods will not be called concurrently
|
||||
class Queue {
|
||||
public:
|
||||
virtual ~Queue() = default;
|
||||
/// Push a task to the queue
|
||||
///
|
||||
/// \param task the task to enqueue
|
||||
virtual void Push(std::unique_ptr<Task> task) = 0;
|
||||
/// Pop the next task from the queue
|
||||
virtual std::unique_ptr<Task> Pop() = 0;
|
||||
/// Peek the next task in the queue
|
||||
virtual const Task& Peek() = 0;
|
||||
/// Check if the queue is empty
|
||||
virtual bool Empty() = 0;
|
||||
/// Purge the queue of all items
|
||||
virtual void Purge() = 0;
|
||||
};
|
||||
|
||||
class Throttle {
|
||||
public:
|
||||
virtual ~Throttle() = default;
|
||||
/// Acquire amt permits
|
||||
///
|
||||
/// If nullopt is returned then the permits were immediately
|
||||
/// acquired and the caller can proceed. If a future is returned then the caller
|
||||
/// should wait for the future to complete first. When the returned future completes
|
||||
/// the permits have NOT been acquired and the caller must call Acquire again
|
||||
///
|
||||
/// \param amt the number of permits to acquire
|
||||
virtual std::optional<Future<>> TryAcquire(int amt) = 0;
|
||||
/// Release amt permits
|
||||
///
|
||||
/// This will possibly complete waiting futures and should probably not be
|
||||
/// called while holding locks.
|
||||
///
|
||||
/// \param amt the number of permits to release
|
||||
virtual void Release(int amt) = 0;
|
||||
|
||||
/// The size of the largest task that can run
|
||||
///
|
||||
/// Incoming tasks will have their cost latched to this value to ensure
|
||||
/// they can still run (although they will be the only thing allowed to
|
||||
/// run at that time).
|
||||
virtual int Capacity() = 0;
|
||||
|
||||
/// Pause the throttle
|
||||
///
|
||||
/// Any tasks that have been submitted already will continue. However, no new tasks
|
||||
/// will be run until the throttle is resumed.
|
||||
virtual void Pause() = 0;
|
||||
/// Resume the throttle
|
||||
///
|
||||
/// Allows taks to be submitted again. If there is a max_concurrent_cost limit then
|
||||
/// it will still apply.
|
||||
virtual void Resume() = 0;
|
||||
};
|
||||
|
||||
/// Pause the throttle
|
||||
///
|
||||
/// Any tasks that have been submitted already will continue. However, no new tasks
|
||||
/// will be run until the throttle is resumed.
|
||||
virtual void Pause() = 0;
|
||||
/// Resume the throttle
|
||||
///
|
||||
/// Allows taks to be submitted again. If there is a max_concurrent_cost limit then
|
||||
/// it will still apply.
|
||||
virtual void Resume() = 0;
|
||||
|
||||
/// Create a throttled view of a scheduler
|
||||
///
|
||||
/// Tasks added via this view will be subjected to the throttle and, if the tasks cannot
|
||||
/// run immediately, will be placed into a queue.
|
||||
///
|
||||
/// Although a shared_ptr is returned it should generally be assumed that the caller
|
||||
/// is being given exclusive ownership. The shared_ptr is used to share the view with
|
||||
/// queued and submitted tasks and the lifetime of those is unpredictable. It is
|
||||
/// important the caller keep the returned pointer alive for as long as they plan to add
|
||||
/// tasks to the view.
|
||||
///
|
||||
/// \param scheduler a scheduler to submit tasks to after throttling
|
||||
///
|
||||
/// This can be the root scheduler, another throttled scheduler, or a task group. These
|
||||
/// are all composable.
|
||||
///
|
||||
/// \param max_concurrent_cost the maximum amount of cost allowed to run at any one time
|
||||
///
|
||||
/// If a task is added that has a cost greater than max_concurrent_cost then its cost
|
||||
/// will be reduced to max_concurrent_cost so that it is still possible for the task to
|
||||
/// run.
|
||||
///
|
||||
/// \param queue the queue to use when tasks cannot be submitted
|
||||
///
|
||||
/// By default a FIFO queue will be used. However, a custom queue can be provided if
|
||||
/// some tasks have higher priority than other tasks.
|
||||
static std::shared_ptr<ThrottledAsyncTaskScheduler> Make(
|
||||
AsyncTaskScheduler* scheduler, int max_concurrent_cost,
|
||||
std::unique_ptr<Queue> queue = NULLPTR);
|
||||
|
||||
/// @brief Create a ThrottledAsyncTaskScheduler using a custom throttle
|
||||
///
|
||||
/// \see Make
|
||||
static std::shared_ptr<ThrottledAsyncTaskScheduler> MakeWithCustomThrottle(
|
||||
AsyncTaskScheduler* scheduler, std::unique_ptr<Throttle> throttle,
|
||||
std::unique_ptr<Queue> queue = NULLPTR);
|
||||
};
|
||||
|
||||
/// A utility to keep track of a collection of tasks
|
||||
///
|
||||
/// Often it is useful to keep track of some state that only needs to stay alive
|
||||
/// for some small collection of tasks, or to perform some kind of final cleanup
|
||||
/// when a collection of tasks is finished.
|
||||
///
|
||||
/// For example, when scanning, we need to keep the file reader alive while all scan
|
||||
/// tasks run for a given file, and then we can gracefully close it when we finish the
|
||||
/// file.
|
||||
class ARROW_EXPORT AsyncTaskGroup : public AsyncTaskScheduler {
|
||||
public:
|
||||
/// Destructor for the task group
|
||||
///
|
||||
/// The destructor might trigger the finish callback. If the finish callback fails
|
||||
/// then the error will be reported as a task on the scheduler.
|
||||
///
|
||||
/// Failure to destroy the async task group will not prevent the scheduler from
|
||||
/// finishing. If the scheduler finishes before the async task group is done then
|
||||
/// the finish callback will be run immediately when the async task group finishes.
|
||||
///
|
||||
/// If the scheduler has aborted then the finish callback will not run.
|
||||
~AsyncTaskGroup() = default;
|
||||
/// Create an async task group
|
||||
///
|
||||
/// The finish callback will not run until the task group is destroyed and all
|
||||
/// tasks are finished so you will generally want to reset / destroy the returned
|
||||
/// unique_ptr at some point.
|
||||
///
|
||||
/// \param scheduler The underlying scheduler to submit tasks to
|
||||
/// \param finish_callback A callback that will be run only after the task group has
|
||||
/// been destroyed and all tasks added by the group have
|
||||
/// finished.
|
||||
///
|
||||
/// Note: in error scenarios the finish callback may not run. However, it will still,
|
||||
/// of course, be destroyed.
|
||||
static std::unique_ptr<AsyncTaskGroup> Make(AsyncTaskScheduler* scheduler,
|
||||
FnOnce<Status()> finish_callback);
|
||||
};
|
||||
|
||||
/// Create a task group that is also throttled
|
||||
///
|
||||
/// This is a utility factory that creates a throttled view of a scheduler and then
|
||||
/// wraps that throttled view with a task group that destroys the throttle when finished.
|
||||
///
|
||||
/// \see ThrottledAsyncTaskScheduler
|
||||
/// \see AsyncTaskGroup
|
||||
/// \param target the underlying scheduler to submit tasks to
|
||||
/// \param max_concurrent_cost the maximum amount of cost allowed to run at any one time
|
||||
/// \param queue the queue to use when tasks cannot be submitted
|
||||
/// \param finish_callback A callback that will be run only after the task group has
|
||||
/// been destroyed and all tasks added by the group have finished
|
||||
ARROW_EXPORT std::unique_ptr<ThrottledAsyncTaskScheduler> MakeThrottledAsyncTaskGroup(
|
||||
AsyncTaskScheduler* target, int max_concurrent_cost,
|
||||
std::unique_ptr<ThrottledAsyncTaskScheduler::Queue> queue,
|
||||
FnOnce<Status()> finish_callback);
|
||||
|
||||
// Defined down here to avoid circular dependency between AsyncTaskScheduler and
|
||||
// AsyncTaskGroup
|
||||
template <typename T>
|
||||
bool AsyncTaskScheduler::AddAsyncGenerator(std::function<Future<T>()> generator,
|
||||
std::function<Status(const T&)> visitor) {
|
||||
struct State {
|
||||
State(std::function<Future<T>()> generator, std::function<Status(const T&)> visitor,
|
||||
std::unique_ptr<AsyncTaskGroup> task_group)
|
||||
: generator(std::move(generator)),
|
||||
visitor(std::move(visitor)),
|
||||
task_group(std::move(task_group)) {}
|
||||
std::function<Future<T>()> generator;
|
||||
std::function<Status(const T&)> visitor;
|
||||
std::unique_ptr<AsyncTaskGroup> task_group;
|
||||
};
|
||||
struct SubmitTask : public Task {
|
||||
explicit SubmitTask(std::unique_ptr<State> state_holder)
|
||||
: state_holder(std::move(state_holder)) {}
|
||||
|
||||
struct SubmitTaskCallback {
|
||||
SubmitTaskCallback(std::unique_ptr<State> state_holder, Future<> task_completion)
|
||||
: state_holder(std::move(state_holder)),
|
||||
task_completion(std::move(task_completion)) {}
|
||||
void operator()(const Result<T>& maybe_item) {
|
||||
if (!maybe_item.ok()) {
|
||||
task_completion.MarkFinished(maybe_item.status());
|
||||
return;
|
||||
}
|
||||
const auto& item = *maybe_item;
|
||||
if (IsIterationEnd(item)) {
|
||||
task_completion.MarkFinished();
|
||||
return;
|
||||
}
|
||||
Status visit_st = state_holder->visitor(item);
|
||||
if (!visit_st.ok()) {
|
||||
task_completion.MarkFinished(std::move(visit_st));
|
||||
return;
|
||||
}
|
||||
state_holder->task_group->AddTask(
|
||||
std::make_unique<SubmitTask>(std::move(state_holder)));
|
||||
task_completion.MarkFinished();
|
||||
}
|
||||
std::unique_ptr<State> state_holder;
|
||||
Future<> task_completion;
|
||||
};
|
||||
|
||||
Result<Future<>> operator()() {
|
||||
Future<> task = Future<>::Make();
|
||||
// Consume as many items as we can (those that are already finished)
|
||||
// synchronously to avoid recursion / stack overflow.
|
||||
while (true) {
|
||||
Future<T> next = state_holder->generator();
|
||||
if (next.TryAddCallback(
|
||||
[&] { return SubmitTaskCallback(std::move(state_holder), task); })) {
|
||||
return task;
|
||||
}
|
||||
ARROW_ASSIGN_OR_RAISE(T item, next.result());
|
||||
if (IsIterationEnd(item)) {
|
||||
task.MarkFinished();
|
||||
return task;
|
||||
}
|
||||
ARROW_RETURN_NOT_OK(state_holder->visitor(item));
|
||||
}
|
||||
}
|
||||
std::unique_ptr<State> state_holder;
|
||||
};
|
||||
std::unique_ptr<AsyncTaskGroup> task_group =
|
||||
AsyncTaskGroup::Make(this, [] { return Status::OK(); });
|
||||
AsyncTaskGroup* task_group_view = task_group.get();
|
||||
std::unique_ptr<State> state_holder = std::make_unique<State>(
|
||||
std::move(generator), std::move(visitor), std::move(task_group));
|
||||
task_group_view->AddTask(std::make_unique<SubmitTask>(std::move(state_holder)));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
ARROW_EXPORT
|
||||
std::string base64_encode(std::string_view s);
|
||||
|
||||
ARROW_EXPORT
|
||||
std::string base64_decode(std::string_view s);
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,474 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "arrow/util/endian.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/type_traits.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
enum class DecimalStatus {
|
||||
kSuccess,
|
||||
kDivideByZero,
|
||||
kOverflow,
|
||||
kRescaleDataLoss,
|
||||
};
|
||||
|
||||
template <typename Derived, int BIT_WIDTH, int NWORDS = BIT_WIDTH / 64>
|
||||
class ARROW_EXPORT GenericBasicDecimal {
|
||||
protected:
|
||||
struct LittleEndianArrayTag {};
|
||||
|
||||
#if ARROW_LITTLE_ENDIAN
|
||||
static constexpr int kHighWordIndex = NWORDS - 1;
|
||||
#else
|
||||
static constexpr int kHighWordIndex = 0;
|
||||
#endif
|
||||
|
||||
public:
|
||||
static constexpr int kBitWidth = BIT_WIDTH;
|
||||
static constexpr int kByteWidth = kBitWidth / 8;
|
||||
|
||||
// A constructor tag to introduce a little-endian encoded array
|
||||
static constexpr LittleEndianArrayTag LittleEndianArray{};
|
||||
|
||||
using WordArray = std::array<uint64_t, NWORDS>;
|
||||
|
||||
/// \brief Empty constructor creates a decimal with a value of 0.
|
||||
constexpr GenericBasicDecimal() noexcept : array_({0}) {}
|
||||
|
||||
/// \brief Create a decimal from the two's complement representation.
|
||||
///
|
||||
/// Input array is assumed to be in native endianness.
|
||||
constexpr GenericBasicDecimal(
|
||||
const WordArray& array) noexcept // NOLINT(runtime/explicit)
|
||||
: array_(array) {}
|
||||
|
||||
/// \brief Create a decimal from the two's complement representation.
|
||||
///
|
||||
/// Input array is assumed to be in little endianness, with native endian elements.
|
||||
GenericBasicDecimal(LittleEndianArrayTag, const WordArray& array) noexcept
|
||||
: GenericBasicDecimal(bit_util::little_endian::ToNative(array)) {}
|
||||
|
||||
/// \brief Create a decimal from an array of bytes.
|
||||
///
|
||||
/// Bytes are assumed to be in native-endian byte order.
|
||||
explicit GenericBasicDecimal(const uint8_t* bytes) {
|
||||
memcpy(array_.data(), bytes, sizeof(array_));
|
||||
}
|
||||
|
||||
/// \brief Get the bits of the two's complement representation of the number.
|
||||
///
|
||||
/// The elements are in native endian order. The bits within each uint64_t element
|
||||
/// are in native endian order. For example, on a little endian machine,
|
||||
/// BasicDecimal128(123).native_endian_array() = {123, 0};
|
||||
/// but on a big endian machine,
|
||||
/// BasicDecimal128(123).native_endian_array() = {0, 123};
|
||||
constexpr const WordArray& native_endian_array() const { return array_; }
|
||||
|
||||
/// \brief Get the bits of the two's complement representation of the number.
|
||||
///
|
||||
/// The elements are in little endian order. However, the bits within each
|
||||
/// uint64_t element are in native endian order.
|
||||
/// For example, BasicDecimal128(123).little_endian_array() = {123, 0};
|
||||
WordArray little_endian_array() const {
|
||||
return bit_util::little_endian::FromNative(array_);
|
||||
}
|
||||
|
||||
const uint8_t* native_endian_bytes() const {
|
||||
return reinterpret_cast<const uint8_t*>(array_.data());
|
||||
}
|
||||
|
||||
uint8_t* mutable_native_endian_bytes() {
|
||||
return reinterpret_cast<uint8_t*>(array_.data());
|
||||
}
|
||||
|
||||
/// \brief Return the raw bytes of the value in native-endian byte order.
|
||||
std::array<uint8_t, kByteWidth> ToBytes() const {
|
||||
std::array<uint8_t, kByteWidth> out{{0}};
|
||||
memcpy(out.data(), array_.data(), kByteWidth);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// \brief Copy the raw bytes of the value in native-endian byte order.
|
||||
void ToBytes(uint8_t* out) const { memcpy(out, array_.data(), kByteWidth); }
|
||||
|
||||
/// Return 1 if positive or zero, -1 if strictly negative.
|
||||
int64_t Sign() const {
|
||||
return 1 | (static_cast<int64_t>(array_[kHighWordIndex]) >> 63);
|
||||
}
|
||||
|
||||
bool IsNegative() const { return static_cast<int64_t>(array_[kHighWordIndex]) < 0; }
|
||||
|
||||
protected:
|
||||
WordArray array_;
|
||||
};
|
||||
|
||||
/// Represents a signed 128-bit integer in two's complement.
|
||||
///
|
||||
/// This class is also compiled into LLVM IR - so, it should not have cpp references like
|
||||
/// streams and boost.
|
||||
class ARROW_EXPORT BasicDecimal128 : public GenericBasicDecimal<BasicDecimal128, 128> {
|
||||
public:
|
||||
static constexpr int kMaxPrecision = 38;
|
||||
static constexpr int kMaxScale = 38;
|
||||
|
||||
using GenericBasicDecimal::GenericBasicDecimal;
|
||||
|
||||
constexpr BasicDecimal128() noexcept : GenericBasicDecimal() {}
|
||||
|
||||
/// \brief Create a BasicDecimal128 from the two's complement representation.
|
||||
#if ARROW_LITTLE_ENDIAN
|
||||
constexpr BasicDecimal128(int64_t high, uint64_t low) noexcept
|
||||
: BasicDecimal128(WordArray{low, static_cast<uint64_t>(high)}) {}
|
||||
#else
|
||||
constexpr BasicDecimal128(int64_t high, uint64_t low) noexcept
|
||||
: BasicDecimal128(WordArray{static_cast<uint64_t>(high), low}) {}
|
||||
#endif
|
||||
|
||||
/// \brief Convert any integer value into a BasicDecimal128.
|
||||
template <typename T,
|
||||
typename = typename std::enable_if<
|
||||
std::is_integral<T>::value && (sizeof(T) <= sizeof(uint64_t)), T>::type>
|
||||
constexpr BasicDecimal128(T value) noexcept // NOLINT(runtime/explicit)
|
||||
: BasicDecimal128(value >= T{0} ? 0 : -1, static_cast<uint64_t>(value)) { // NOLINT
|
||||
}
|
||||
|
||||
/// \brief Negate the current value (in-place)
|
||||
BasicDecimal128& Negate();
|
||||
|
||||
/// \brief Absolute value (in-place)
|
||||
BasicDecimal128& Abs();
|
||||
|
||||
/// \brief Absolute value
|
||||
static BasicDecimal128 Abs(const BasicDecimal128& left);
|
||||
|
||||
/// \brief Add a number to this one. The result is truncated to 128 bits.
|
||||
BasicDecimal128& operator+=(const BasicDecimal128& right);
|
||||
|
||||
/// \brief Subtract a number from this one. The result is truncated to 128 bits.
|
||||
BasicDecimal128& operator-=(const BasicDecimal128& right);
|
||||
|
||||
/// \brief Multiply this number by another number. The result is truncated to 128 bits.
|
||||
BasicDecimal128& operator*=(const BasicDecimal128& right);
|
||||
|
||||
/// Divide this number by right and return the result.
|
||||
///
|
||||
/// This operation is not destructive.
|
||||
/// The answer rounds to zero. Signs work like:
|
||||
/// 21 / 5 -> 4, 1
|
||||
/// -21 / 5 -> -4, -1
|
||||
/// 21 / -5 -> -4, 1
|
||||
/// -21 / -5 -> 4, -1
|
||||
/// \param[in] divisor the number to divide by
|
||||
/// \param[out] result the quotient
|
||||
/// \param[out] remainder the remainder after the division
|
||||
DecimalStatus Divide(const BasicDecimal128& divisor, BasicDecimal128* result,
|
||||
BasicDecimal128* remainder) const;
|
||||
|
||||
/// \brief In-place division.
|
||||
BasicDecimal128& operator/=(const BasicDecimal128& right);
|
||||
|
||||
/// \brief Bitwise "or" between two BasicDecimal128.
|
||||
BasicDecimal128& operator|=(const BasicDecimal128& right);
|
||||
|
||||
/// \brief Bitwise "and" between two BasicDecimal128.
|
||||
BasicDecimal128& operator&=(const BasicDecimal128& right);
|
||||
|
||||
/// \brief Shift left by the given number of bits.
|
||||
BasicDecimal128& operator<<=(uint32_t bits);
|
||||
|
||||
BasicDecimal128 operator<<(uint32_t bits) const {
|
||||
auto res = *this;
|
||||
res <<= bits;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// \brief Shift right by the given number of bits. Negative values will
|
||||
BasicDecimal128& operator>>=(uint32_t bits);
|
||||
|
||||
BasicDecimal128 operator>>(uint32_t bits) const {
|
||||
auto res = *this;
|
||||
res >>= bits;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// \brief Get the high bits of the two's complement representation of the number.
|
||||
constexpr int64_t high_bits() const {
|
||||
#if ARROW_LITTLE_ENDIAN
|
||||
return static_cast<int64_t>(array_[1]);
|
||||
#else
|
||||
return static_cast<int64_t>(array_[0]);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// \brief Get the low bits of the two's complement representation of the number.
|
||||
constexpr uint64_t low_bits() const {
|
||||
#if ARROW_LITTLE_ENDIAN
|
||||
return array_[0];
|
||||
#else
|
||||
return array_[1];
|
||||
#endif
|
||||
}
|
||||
|
||||
/// \brief separate the integer and fractional parts for the given scale.
|
||||
void GetWholeAndFraction(int32_t scale, BasicDecimal128* whole,
|
||||
BasicDecimal128* fraction) const;
|
||||
|
||||
/// \brief Scale multiplier for given scale value.
|
||||
static const BasicDecimal128& GetScaleMultiplier(int32_t scale);
|
||||
/// \brief Half-scale multiplier for given scale value.
|
||||
static const BasicDecimal128& GetHalfScaleMultiplier(int32_t scale);
|
||||
|
||||
/// \brief Convert BasicDecimal128 from one scale to another
|
||||
DecimalStatus Rescale(int32_t original_scale, int32_t new_scale,
|
||||
BasicDecimal128* out) const;
|
||||
|
||||
/// \brief Scale up.
|
||||
BasicDecimal128 IncreaseScaleBy(int32_t increase_by) const;
|
||||
|
||||
/// \brief Scale down.
|
||||
/// - If 'round' is true, the right-most digits are dropped and the result value is
|
||||
/// rounded up (+1 for +ve, -1 for -ve) based on the value of the dropped digits
|
||||
/// (>= 10^reduce_by / 2).
|
||||
/// - If 'round' is false, the right-most digits are simply dropped.
|
||||
BasicDecimal128 ReduceScaleBy(int32_t reduce_by, bool round = true) const;
|
||||
|
||||
/// \brief Whether this number fits in the given precision
|
||||
///
|
||||
/// Return true if the number of significant digits is less or equal to `precision`.
|
||||
bool FitsInPrecision(int32_t precision) const;
|
||||
|
||||
/// \brief count the number of leading binary zeroes.
|
||||
int32_t CountLeadingBinaryZeros() const;
|
||||
|
||||
/// \brief Get the maximum valid unscaled decimal value.
|
||||
static const BasicDecimal128& GetMaxValue();
|
||||
|
||||
/// \brief Get the maximum valid unscaled decimal value for the given precision.
|
||||
static BasicDecimal128 GetMaxValue(int32_t precision);
|
||||
|
||||
/// \brief Get the maximum decimal value (is not a valid value).
|
||||
static constexpr BasicDecimal128 GetMaxSentinel() {
|
||||
return BasicDecimal128(/*high=*/std::numeric_limits<int64_t>::max(),
|
||||
/*low=*/std::numeric_limits<uint64_t>::max());
|
||||
}
|
||||
/// \brief Get the minimum decimal value (is not a valid value).
|
||||
static constexpr BasicDecimal128 GetMinSentinel() {
|
||||
return BasicDecimal128(/*high=*/std::numeric_limits<int64_t>::min(),
|
||||
/*low=*/std::numeric_limits<uint64_t>::min());
|
||||
}
|
||||
};
|
||||
|
||||
ARROW_EXPORT bool operator==(const BasicDecimal128& left, const BasicDecimal128& right);
|
||||
ARROW_EXPORT bool operator!=(const BasicDecimal128& left, const BasicDecimal128& right);
|
||||
ARROW_EXPORT bool operator<(const BasicDecimal128& left, const BasicDecimal128& right);
|
||||
ARROW_EXPORT bool operator<=(const BasicDecimal128& left, const BasicDecimal128& right);
|
||||
ARROW_EXPORT bool operator>(const BasicDecimal128& left, const BasicDecimal128& right);
|
||||
ARROW_EXPORT bool operator>=(const BasicDecimal128& left, const BasicDecimal128& right);
|
||||
|
||||
ARROW_EXPORT BasicDecimal128 operator-(const BasicDecimal128& operand);
|
||||
ARROW_EXPORT BasicDecimal128 operator~(const BasicDecimal128& operand);
|
||||
ARROW_EXPORT BasicDecimal128 operator+(const BasicDecimal128& left,
|
||||
const BasicDecimal128& right);
|
||||
ARROW_EXPORT BasicDecimal128 operator-(const BasicDecimal128& left,
|
||||
const BasicDecimal128& right);
|
||||
ARROW_EXPORT BasicDecimal128 operator*(const BasicDecimal128& left,
|
||||
const BasicDecimal128& right);
|
||||
ARROW_EXPORT BasicDecimal128 operator/(const BasicDecimal128& left,
|
||||
const BasicDecimal128& right);
|
||||
ARROW_EXPORT BasicDecimal128 operator%(const BasicDecimal128& left,
|
||||
const BasicDecimal128& right);
|
||||
|
||||
class ARROW_EXPORT BasicDecimal256 : public GenericBasicDecimal<BasicDecimal256, 256> {
|
||||
private:
|
||||
// Due to a bug in clang, we have to declare the extend method prior to its
|
||||
// usage.
|
||||
template <typename T>
|
||||
static constexpr uint64_t extend(T low_bits) noexcept {
|
||||
return low_bits >= T() ? uint64_t{0} : ~uint64_t{0};
|
||||
}
|
||||
|
||||
public:
|
||||
using GenericBasicDecimal::GenericBasicDecimal;
|
||||
|
||||
static constexpr int kMaxPrecision = 76;
|
||||
static constexpr int kMaxScale = 76;
|
||||
|
||||
constexpr BasicDecimal256() noexcept : GenericBasicDecimal() {}
|
||||
|
||||
/// \brief Convert any integer value into a BasicDecimal256.
|
||||
template <typename T,
|
||||
typename = typename std::enable_if<
|
||||
std::is_integral<T>::value && (sizeof(T) <= sizeof(uint64_t)), T>::type>
|
||||
constexpr BasicDecimal256(T value) noexcept // NOLINT(runtime/explicit)
|
||||
: BasicDecimal256(bit_util::little_endian::ToNative<uint64_t, 4>(
|
||||
{static_cast<uint64_t>(value), extend(value), extend(value),
|
||||
extend(value)})) {}
|
||||
|
||||
explicit BasicDecimal256(const BasicDecimal128& value) noexcept
|
||||
: BasicDecimal256(bit_util::little_endian::ToNative<uint64_t, 4>(
|
||||
{value.low_bits(), static_cast<uint64_t>(value.high_bits()),
|
||||
extend(value.high_bits()), extend(value.high_bits())})) {}
|
||||
|
||||
/// \brief Negate the current value (in-place)
|
||||
BasicDecimal256& Negate();
|
||||
|
||||
/// \brief Absolute value (in-place)
|
||||
BasicDecimal256& Abs();
|
||||
|
||||
/// \brief Absolute value
|
||||
static BasicDecimal256 Abs(const BasicDecimal256& left);
|
||||
|
||||
/// \brief Add a number to this one. The result is truncated to 256 bits.
|
||||
BasicDecimal256& operator+=(const BasicDecimal256& right);
|
||||
|
||||
/// \brief Subtract a number from this one. The result is truncated to 256 bits.
|
||||
BasicDecimal256& operator-=(const BasicDecimal256& right);
|
||||
|
||||
/// \brief Get the lowest bits of the two's complement representation of the number.
|
||||
uint64_t low_bits() const { return bit_util::little_endian::Make(array_)[0]; }
|
||||
|
||||
/// \brief Scale multiplier for given scale value.
|
||||
static const BasicDecimal256& GetScaleMultiplier(int32_t scale);
|
||||
/// \brief Half-scale multiplier for given scale value.
|
||||
static const BasicDecimal256& GetHalfScaleMultiplier(int32_t scale);
|
||||
|
||||
/// \brief Convert BasicDecimal256 from one scale to another
|
||||
DecimalStatus Rescale(int32_t original_scale, int32_t new_scale,
|
||||
BasicDecimal256* out) const;
|
||||
|
||||
/// \brief Scale up.
|
||||
BasicDecimal256 IncreaseScaleBy(int32_t increase_by) const;
|
||||
|
||||
/// \brief Scale down.
|
||||
/// - If 'round' is true, the right-most digits are dropped and the result value is
|
||||
/// rounded up (+1 for positive, -1 for negative) based on the value of the
|
||||
/// dropped digits (>= 10^reduce_by / 2).
|
||||
/// - If 'round' is false, the right-most digits are simply dropped.
|
||||
BasicDecimal256 ReduceScaleBy(int32_t reduce_by, bool round = true) const;
|
||||
|
||||
/// \brief Whether this number fits in the given precision
|
||||
///
|
||||
/// Return true if the number of significant digits is less or equal to `precision`.
|
||||
bool FitsInPrecision(int32_t precision) const;
|
||||
|
||||
/// \brief Multiply this number by another number. The result is truncated to 256 bits.
|
||||
BasicDecimal256& operator*=(const BasicDecimal256& right);
|
||||
|
||||
/// Divide this number by right and return the result.
|
||||
///
|
||||
/// This operation is not destructive.
|
||||
/// The answer rounds to zero. Signs work like:
|
||||
/// 21 / 5 -> 4, 1
|
||||
/// -21 / 5 -> -4, -1
|
||||
/// 21 / -5 -> -4, 1
|
||||
/// -21 / -5 -> 4, -1
|
||||
/// \param[in] divisor the number to divide by
|
||||
/// \param[out] result the quotient
|
||||
/// \param[out] remainder the remainder after the division
|
||||
DecimalStatus Divide(const BasicDecimal256& divisor, BasicDecimal256* result,
|
||||
BasicDecimal256* remainder) const;
|
||||
|
||||
/// \brief Shift left by the given number of bits.
|
||||
BasicDecimal256& operator<<=(uint32_t bits);
|
||||
|
||||
BasicDecimal256 operator<<(uint32_t bits) const {
|
||||
auto res = *this;
|
||||
res <<= bits;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// \brief In-place division.
|
||||
BasicDecimal256& operator/=(const BasicDecimal256& right);
|
||||
|
||||
/// \brief Get the maximum valid unscaled decimal value for the given precision.
|
||||
static BasicDecimal256 GetMaxValue(int32_t precision);
|
||||
|
||||
/// \brief Get the maximum decimal value (is not a valid value).
|
||||
static constexpr BasicDecimal256 GetMaxSentinel() {
|
||||
#if ARROW_LITTLE_ENDIAN
|
||||
return BasicDecimal256({std::numeric_limits<uint64_t>::max(),
|
||||
std::numeric_limits<uint64_t>::max(),
|
||||
std::numeric_limits<uint64_t>::max(),
|
||||
static_cast<uint64_t>(std::numeric_limits<int64_t>::max())});
|
||||
#else
|
||||
return BasicDecimal256({static_cast<uint64_t>(std::numeric_limits<int64_t>::max()),
|
||||
std::numeric_limits<uint64_t>::max(),
|
||||
std::numeric_limits<uint64_t>::max(),
|
||||
std::numeric_limits<uint64_t>::max()});
|
||||
#endif
|
||||
}
|
||||
/// \brief Get the minimum decimal value (is not a valid value).
|
||||
static constexpr BasicDecimal256 GetMinSentinel() {
|
||||
#if ARROW_LITTLE_ENDIAN
|
||||
return BasicDecimal256(
|
||||
{0, 0, 0, static_cast<uint64_t>(std::numeric_limits<int64_t>::min())});
|
||||
#else
|
||||
return BasicDecimal256(
|
||||
{static_cast<uint64_t>(std::numeric_limits<int64_t>::min()), 0, 0, 0});
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
ARROW_EXPORT inline bool operator==(const BasicDecimal256& left,
|
||||
const BasicDecimal256& right) {
|
||||
return left.native_endian_array() == right.native_endian_array();
|
||||
}
|
||||
|
||||
ARROW_EXPORT inline bool operator!=(const BasicDecimal256& left,
|
||||
const BasicDecimal256& right) {
|
||||
return left.native_endian_array() != right.native_endian_array();
|
||||
}
|
||||
|
||||
ARROW_EXPORT bool operator<(const BasicDecimal256& left, const BasicDecimal256& right);
|
||||
|
||||
ARROW_EXPORT inline bool operator<=(const BasicDecimal256& left,
|
||||
const BasicDecimal256& right) {
|
||||
return !operator<(right, left);
|
||||
}
|
||||
|
||||
ARROW_EXPORT inline bool operator>(const BasicDecimal256& left,
|
||||
const BasicDecimal256& right) {
|
||||
return operator<(right, left);
|
||||
}
|
||||
|
||||
ARROW_EXPORT inline bool operator>=(const BasicDecimal256& left,
|
||||
const BasicDecimal256& right) {
|
||||
return !operator<(left, right);
|
||||
}
|
||||
|
||||
ARROW_EXPORT BasicDecimal256 operator-(const BasicDecimal256& operand);
|
||||
ARROW_EXPORT BasicDecimal256 operator~(const BasicDecimal256& operand);
|
||||
ARROW_EXPORT BasicDecimal256 operator+(const BasicDecimal256& left,
|
||||
const BasicDecimal256& right);
|
||||
ARROW_EXPORT BasicDecimal256 operator*(const BasicDecimal256& left,
|
||||
const BasicDecimal256& right);
|
||||
ARROW_EXPORT BasicDecimal256 operator/(const BasicDecimal256& left,
|
||||
const BasicDecimal256& right);
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,139 @@
|
||||
// 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 <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
|
||||
#include "arrow/util/cpu_info.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
// Benchmark changed its parameter type between releases from
|
||||
// int to int64_t. As it doesn't have version macros, we need
|
||||
// to apply C++ template magic.
|
||||
|
||||
template <typename Func>
|
||||
struct BenchmarkArgsType;
|
||||
|
||||
// Pattern matching that extracts the vector element type of Benchmark::Args()
|
||||
template <typename Values>
|
||||
struct BenchmarkArgsType<benchmark::internal::Benchmark* (
|
||||
benchmark::internal::Benchmark::*)(const std::vector<Values>&)> {
|
||||
using type = Values;
|
||||
};
|
||||
|
||||
using ArgsType =
|
||||
typename BenchmarkArgsType<decltype(&benchmark::internal::Benchmark::Args)>::type;
|
||||
|
||||
using internal::CpuInfo;
|
||||
|
||||
static const CpuInfo* cpu_info = CpuInfo::GetInstance();
|
||||
|
||||
static const int64_t kL1Size = cpu_info->CacheSize(CpuInfo::CacheLevel::L1);
|
||||
static const int64_t kL2Size = cpu_info->CacheSize(CpuInfo::CacheLevel::L2);
|
||||
static const int64_t kL3Size = cpu_info->CacheSize(CpuInfo::CacheLevel::L3);
|
||||
static const int64_t kCantFitInL3Size = kL3Size * 4;
|
||||
static const std::vector<int64_t> kMemorySizes = {kL1Size, kL2Size, kL3Size,
|
||||
kCantFitInL3Size};
|
||||
// 0 is treated as "no nulls"
|
||||
static const std::vector<ArgsType> kInverseNullProportions = {10000, 100, 10, 2, 1, 0};
|
||||
|
||||
struct GenericItemsArgs {
|
||||
// number of items processed per iteration
|
||||
const int64_t size;
|
||||
|
||||
// proportion of nulls in generated arrays
|
||||
double null_proportion;
|
||||
|
||||
explicit GenericItemsArgs(benchmark::State& state)
|
||||
: size(state.range(0)), state_(state) {
|
||||
if (state.range(1) == 0) {
|
||||
this->null_proportion = 0.0;
|
||||
} else {
|
||||
this->null_proportion = std::min(1., 1. / static_cast<double>(state.range(1)));
|
||||
}
|
||||
}
|
||||
|
||||
~GenericItemsArgs() {
|
||||
state_.counters["size"] = static_cast<double>(size);
|
||||
state_.counters["null_percent"] = null_proportion * 100;
|
||||
state_.SetItemsProcessed(state_.iterations() * size);
|
||||
}
|
||||
|
||||
private:
|
||||
benchmark::State& state_;
|
||||
};
|
||||
|
||||
void BenchmarkSetArgsWithSizes(benchmark::internal::Benchmark* bench,
|
||||
const std::vector<int64_t>& sizes = kMemorySizes) {
|
||||
bench->Unit(benchmark::kMicrosecond);
|
||||
|
||||
for (const auto size : sizes) {
|
||||
for (const auto inverse_null_proportion : kInverseNullProportions) {
|
||||
bench->Args({static_cast<ArgsType>(size), inverse_null_proportion});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BenchmarkSetArgs(benchmark::internal::Benchmark* bench) {
|
||||
BenchmarkSetArgsWithSizes(bench, kMemorySizes);
|
||||
}
|
||||
|
||||
void RegressionSetArgs(benchmark::internal::Benchmark* bench) {
|
||||
// Regression do not need to account for cache hierarchy, thus optimize for
|
||||
// the best case.
|
||||
BenchmarkSetArgsWithSizes(bench, {kL1Size});
|
||||
}
|
||||
|
||||
// RAII struct to handle some of the boilerplate in regression benchmarks
|
||||
struct RegressionArgs {
|
||||
// size of memory tested (per iteration) in bytes
|
||||
const int64_t size;
|
||||
|
||||
// proportion of nulls in generated arrays
|
||||
double null_proportion;
|
||||
|
||||
// If size_is_bytes is true, then it's a number of bytes, otherwise it's the
|
||||
// number of items processed (for reporting)
|
||||
explicit RegressionArgs(benchmark::State& state, bool size_is_bytes = true)
|
||||
: size(state.range(0)), state_(state), size_is_bytes_(size_is_bytes) {
|
||||
if (state.range(1) == 0) {
|
||||
this->null_proportion = 0.0;
|
||||
} else {
|
||||
this->null_proportion = std::min(1., 1. / static_cast<double>(state.range(1)));
|
||||
}
|
||||
}
|
||||
|
||||
~RegressionArgs() {
|
||||
state_.counters["size"] = static_cast<double>(size);
|
||||
state_.counters["null_percent"] = null_proportion * 100;
|
||||
if (size_is_bytes_) {
|
||||
state_.SetBytesProcessed(state_.iterations() * size);
|
||||
} else {
|
||||
state_.SetItemsProcessed(state_.iterations() * size);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
benchmark::State& state_;
|
||||
bool size_is_bytes_;
|
||||
};
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,570 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
#include "arrow/buffer.h"
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/util/bit_util.h"
|
||||
#include "arrow/util/endian.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/ubsan.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
|
||||
inline uint64_t LoadWord(const uint8_t* bytes) {
|
||||
return bit_util::ToLittleEndian(util::SafeLoadAs<uint64_t>(bytes));
|
||||
}
|
||||
|
||||
inline uint64_t ShiftWord(uint64_t current, uint64_t next, int64_t shift) {
|
||||
if (shift == 0) {
|
||||
return current;
|
||||
}
|
||||
return (current >> shift) | (next << (64 - shift));
|
||||
}
|
||||
|
||||
// These templates are here to help with unit tests
|
||||
|
||||
template <typename T>
|
||||
constexpr T BitNot(T x) {
|
||||
return ~x;
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr bool BitNot(bool x) {
|
||||
return !x;
|
||||
}
|
||||
|
||||
struct BitBlockAnd {
|
||||
template <typename T>
|
||||
static constexpr T Call(T left, T right) {
|
||||
return left & right;
|
||||
}
|
||||
};
|
||||
|
||||
struct BitBlockAndNot {
|
||||
template <typename T>
|
||||
static constexpr T Call(T left, T right) {
|
||||
return left & BitNot(right);
|
||||
}
|
||||
};
|
||||
|
||||
struct BitBlockOr {
|
||||
template <typename T>
|
||||
static constexpr T Call(T left, T right) {
|
||||
return left | right;
|
||||
}
|
||||
};
|
||||
|
||||
struct BitBlockOrNot {
|
||||
template <typename T>
|
||||
static constexpr T Call(T left, T right) {
|
||||
return left | BitNot(right);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// \brief Return value from bit block counters: the total number of bits and
|
||||
/// the number of set bits.
|
||||
struct BitBlockCount {
|
||||
int16_t length;
|
||||
int16_t popcount;
|
||||
|
||||
bool NoneSet() const { return this->popcount == 0; }
|
||||
bool AllSet() const { return this->length == this->popcount; }
|
||||
};
|
||||
|
||||
/// \brief A class that scans through a true/false bitmap to compute popcounts
|
||||
/// 64 or 256 bits at a time. This is used to accelerate processing of
|
||||
/// mostly-not-null array data.
|
||||
class ARROW_EXPORT BitBlockCounter {
|
||||
public:
|
||||
BitBlockCounter(const uint8_t* bitmap, int64_t start_offset, int64_t length)
|
||||
: bitmap_(util::MakeNonNull(bitmap) + start_offset / 8),
|
||||
bits_remaining_(length),
|
||||
offset_(start_offset % 8) {}
|
||||
|
||||
/// \brief The bit size of each word run
|
||||
static constexpr int64_t kWordBits = 64;
|
||||
|
||||
/// \brief The bit size of four words run
|
||||
static constexpr int64_t kFourWordsBits = kWordBits * 4;
|
||||
|
||||
/// \brief Return the next run of available bits, usually 256. The returned
|
||||
/// pair contains the size of run and the number of true values. The last
|
||||
/// block will have a length less than 256 if the bitmap length is not a
|
||||
/// multiple of 256, and will return 0-length blocks in subsequent
|
||||
/// invocations.
|
||||
BitBlockCount NextFourWords() {
|
||||
using detail::LoadWord;
|
||||
using detail::ShiftWord;
|
||||
|
||||
if (!bits_remaining_) {
|
||||
return {0, 0};
|
||||
}
|
||||
int64_t total_popcount = 0;
|
||||
if (offset_ == 0) {
|
||||
if (bits_remaining_ < kFourWordsBits) {
|
||||
return GetBlockSlow(kFourWordsBits);
|
||||
}
|
||||
total_popcount += bit_util::PopCount(LoadWord(bitmap_));
|
||||
total_popcount += bit_util::PopCount(LoadWord(bitmap_ + 8));
|
||||
total_popcount += bit_util::PopCount(LoadWord(bitmap_ + 16));
|
||||
total_popcount += bit_util::PopCount(LoadWord(bitmap_ + 24));
|
||||
} else {
|
||||
// When the offset is > 0, we need there to be a word beyond the last
|
||||
// aligned word in the bitmap for the bit shifting logic.
|
||||
if (bits_remaining_ < 5 * kFourWordsBits - offset_) {
|
||||
return GetBlockSlow(kFourWordsBits);
|
||||
}
|
||||
auto current = LoadWord(bitmap_);
|
||||
auto next = LoadWord(bitmap_ + 8);
|
||||
total_popcount += bit_util::PopCount(ShiftWord(current, next, offset_));
|
||||
current = next;
|
||||
next = LoadWord(bitmap_ + 16);
|
||||
total_popcount += bit_util::PopCount(ShiftWord(current, next, offset_));
|
||||
current = next;
|
||||
next = LoadWord(bitmap_ + 24);
|
||||
total_popcount += bit_util::PopCount(ShiftWord(current, next, offset_));
|
||||
current = next;
|
||||
next = LoadWord(bitmap_ + 32);
|
||||
total_popcount += bit_util::PopCount(ShiftWord(current, next, offset_));
|
||||
}
|
||||
bitmap_ += bit_util::BytesForBits(kFourWordsBits);
|
||||
bits_remaining_ -= kFourWordsBits;
|
||||
return {256, static_cast<int16_t>(total_popcount)};
|
||||
}
|
||||
|
||||
/// \brief Return the next run of available bits, usually 64. The returned
|
||||
/// pair contains the size of run and the number of true values. The last
|
||||
/// block will have a length less than 64 if the bitmap length is not a
|
||||
/// multiple of 64, and will return 0-length blocks in subsequent
|
||||
/// invocations.
|
||||
BitBlockCount NextWord() {
|
||||
using detail::LoadWord;
|
||||
using detail::ShiftWord;
|
||||
|
||||
if (!bits_remaining_) {
|
||||
return {0, 0};
|
||||
}
|
||||
int64_t popcount = 0;
|
||||
if (offset_ == 0) {
|
||||
if (bits_remaining_ < kWordBits) {
|
||||
return GetBlockSlow(kWordBits);
|
||||
}
|
||||
popcount = bit_util::PopCount(LoadWord(bitmap_));
|
||||
} else {
|
||||
// When the offset is > 0, we need there to be a word beyond the last
|
||||
// aligned word in the bitmap for the bit shifting logic.
|
||||
if (bits_remaining_ < 2 * kWordBits - offset_) {
|
||||
return GetBlockSlow(kWordBits);
|
||||
}
|
||||
popcount = bit_util::PopCount(
|
||||
ShiftWord(LoadWord(bitmap_), LoadWord(bitmap_ + 8), offset_));
|
||||
}
|
||||
bitmap_ += kWordBits / 8;
|
||||
bits_remaining_ -= kWordBits;
|
||||
return {64, static_cast<int16_t>(popcount)};
|
||||
}
|
||||
|
||||
private:
|
||||
/// \brief Return block with the requested size when doing word-wise
|
||||
/// computation is not possible due to inadequate bits remaining.
|
||||
BitBlockCount GetBlockSlow(int64_t block_size) noexcept;
|
||||
|
||||
const uint8_t* bitmap_;
|
||||
int64_t bits_remaining_;
|
||||
int64_t offset_;
|
||||
};
|
||||
|
||||
/// \brief A tool to iterate through a possibly non-existent validity bitmap,
|
||||
/// to allow us to write one code path for both the with-nulls and no-nulls
|
||||
/// cases without giving up a lot of performance.
|
||||
class ARROW_EXPORT OptionalBitBlockCounter {
|
||||
public:
|
||||
// validity_bitmap may be NULLPTR
|
||||
OptionalBitBlockCounter(const uint8_t* validity_bitmap, int64_t offset, int64_t length);
|
||||
|
||||
// validity_bitmap may be null
|
||||
OptionalBitBlockCounter(const std::shared_ptr<Buffer>& validity_bitmap, int64_t offset,
|
||||
int64_t length);
|
||||
|
||||
/// Return block count for next word when the bitmap is available otherwise
|
||||
/// return a block with length up to INT16_MAX when there is no validity
|
||||
/// bitmap (so all the referenced values are not null).
|
||||
BitBlockCount NextBlock() {
|
||||
static constexpr int64_t kMaxBlockSize = std::numeric_limits<int16_t>::max();
|
||||
if (has_bitmap_) {
|
||||
BitBlockCount block = counter_.NextWord();
|
||||
position_ += block.length;
|
||||
return block;
|
||||
} else {
|
||||
int16_t block_size =
|
||||
static_cast<int16_t>(std::min(kMaxBlockSize, length_ - position_));
|
||||
position_ += block_size;
|
||||
// All values are non-null
|
||||
return {block_size, block_size};
|
||||
}
|
||||
}
|
||||
|
||||
// Like NextBlock, but returns a word-sized block even when there is no
|
||||
// validity bitmap
|
||||
BitBlockCount NextWord() {
|
||||
static constexpr int64_t kWordSize = 64;
|
||||
if (has_bitmap_) {
|
||||
BitBlockCount block = counter_.NextWord();
|
||||
position_ += block.length;
|
||||
return block;
|
||||
} else {
|
||||
int16_t block_size = static_cast<int16_t>(std::min(kWordSize, length_ - position_));
|
||||
position_ += block_size;
|
||||
// All values are non-null
|
||||
return {block_size, block_size};
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const bool has_bitmap_;
|
||||
int64_t position_;
|
||||
int64_t length_;
|
||||
BitBlockCounter counter_;
|
||||
};
|
||||
|
||||
/// \brief A class that computes popcounts on the result of bitwise operations
|
||||
/// between two bitmaps, 64 bits at a time. A 64-bit word is loaded from each
|
||||
/// bitmap, then the popcount is computed on e.g. the bitwise-and of the two
|
||||
/// words.
|
||||
class ARROW_EXPORT BinaryBitBlockCounter {
|
||||
public:
|
||||
BinaryBitBlockCounter(const uint8_t* left_bitmap, int64_t left_offset,
|
||||
const uint8_t* right_bitmap, int64_t right_offset, int64_t length)
|
||||
: left_bitmap_(util::MakeNonNull(left_bitmap) + left_offset / 8),
|
||||
left_offset_(left_offset % 8),
|
||||
right_bitmap_(util::MakeNonNull(right_bitmap) + right_offset / 8),
|
||||
right_offset_(right_offset % 8),
|
||||
bits_remaining_(length) {}
|
||||
|
||||
/// \brief Return the popcount of the bitwise-and of the next run of
|
||||
/// available bits, up to 64. The returned pair contains the size of run and
|
||||
/// the number of true values. The last block will have a length less than 64
|
||||
/// if the bitmap length is not a multiple of 64, and will return 0-length
|
||||
/// blocks in subsequent invocations.
|
||||
BitBlockCount NextAndWord() { return NextWord<detail::BitBlockAnd>(); }
|
||||
|
||||
/// \brief Computes "x & ~y" block for each available run of bits.
|
||||
BitBlockCount NextAndNotWord() { return NextWord<detail::BitBlockAndNot>(); }
|
||||
|
||||
/// \brief Computes "x | y" block for each available run of bits.
|
||||
BitBlockCount NextOrWord() { return NextWord<detail::BitBlockOr>(); }
|
||||
|
||||
/// \brief Computes "x | ~y" block for each available run of bits.
|
||||
BitBlockCount NextOrNotWord() { return NextWord<detail::BitBlockOrNot>(); }
|
||||
|
||||
private:
|
||||
template <class Op>
|
||||
BitBlockCount NextWord() {
|
||||
using detail::LoadWord;
|
||||
using detail::ShiftWord;
|
||||
|
||||
if (!bits_remaining_) {
|
||||
return {0, 0};
|
||||
}
|
||||
// When the offset is > 0, we need there to be a word beyond the last aligned
|
||||
// word in the bitmap for the bit shifting logic.
|
||||
constexpr int64_t kWordBits = BitBlockCounter::kWordBits;
|
||||
const int64_t bits_required_to_use_words =
|
||||
std::max(left_offset_ == 0 ? 64 : 64 + (64 - left_offset_),
|
||||
right_offset_ == 0 ? 64 : 64 + (64 - right_offset_));
|
||||
if (bits_remaining_ < bits_required_to_use_words) {
|
||||
const int16_t run_length =
|
||||
static_cast<int16_t>(std::min(bits_remaining_, kWordBits));
|
||||
int16_t popcount = 0;
|
||||
for (int64_t i = 0; i < run_length; ++i) {
|
||||
if (Op::Call(bit_util::GetBit(left_bitmap_, left_offset_ + i),
|
||||
bit_util::GetBit(right_bitmap_, right_offset_ + i))) {
|
||||
++popcount;
|
||||
}
|
||||
}
|
||||
// This code path should trigger _at most_ 2 times. In the "two times"
|
||||
// case, the first time the run length will be a multiple of 8.
|
||||
left_bitmap_ += run_length / 8;
|
||||
right_bitmap_ += run_length / 8;
|
||||
bits_remaining_ -= run_length;
|
||||
return {run_length, popcount};
|
||||
}
|
||||
|
||||
int64_t popcount = 0;
|
||||
if (left_offset_ == 0 && right_offset_ == 0) {
|
||||
popcount =
|
||||
bit_util::PopCount(Op::Call(LoadWord(left_bitmap_), LoadWord(right_bitmap_)));
|
||||
} else {
|
||||
auto left_word =
|
||||
ShiftWord(LoadWord(left_bitmap_), LoadWord(left_bitmap_ + 8), left_offset_);
|
||||
auto right_word =
|
||||
ShiftWord(LoadWord(right_bitmap_), LoadWord(right_bitmap_ + 8), right_offset_);
|
||||
popcount = bit_util::PopCount(Op::Call(left_word, right_word));
|
||||
}
|
||||
left_bitmap_ += kWordBits / 8;
|
||||
right_bitmap_ += kWordBits / 8;
|
||||
bits_remaining_ -= kWordBits;
|
||||
return {64, static_cast<int16_t>(popcount)};
|
||||
}
|
||||
|
||||
const uint8_t* left_bitmap_;
|
||||
int64_t left_offset_;
|
||||
const uint8_t* right_bitmap_;
|
||||
int64_t right_offset_;
|
||||
int64_t bits_remaining_;
|
||||
};
|
||||
|
||||
class ARROW_EXPORT OptionalBinaryBitBlockCounter {
|
||||
public:
|
||||
// Any bitmap may be NULLPTR
|
||||
OptionalBinaryBitBlockCounter(const uint8_t* left_bitmap, int64_t left_offset,
|
||||
const uint8_t* right_bitmap, int64_t right_offset,
|
||||
int64_t length);
|
||||
|
||||
// Any bitmap may be null
|
||||
OptionalBinaryBitBlockCounter(const std::shared_ptr<Buffer>& left_bitmap,
|
||||
int64_t left_offset,
|
||||
const std::shared_ptr<Buffer>& right_bitmap,
|
||||
int64_t right_offset, int64_t length);
|
||||
|
||||
BitBlockCount NextAndBlock() {
|
||||
static constexpr int64_t kMaxBlockSize = std::numeric_limits<int16_t>::max();
|
||||
switch (has_bitmap_) {
|
||||
case HasBitmap::BOTH: {
|
||||
BitBlockCount block = binary_counter_.NextAndWord();
|
||||
position_ += block.length;
|
||||
return block;
|
||||
}
|
||||
case HasBitmap::ONE: {
|
||||
BitBlockCount block = unary_counter_.NextWord();
|
||||
position_ += block.length;
|
||||
return block;
|
||||
}
|
||||
case HasBitmap::NONE:
|
||||
default: {
|
||||
const int16_t block_size =
|
||||
static_cast<int16_t>(std::min(kMaxBlockSize, length_ - position_));
|
||||
position_ += block_size;
|
||||
// All values are non-null
|
||||
return {block_size, block_size};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BitBlockCount NextOrNotBlock() {
|
||||
static constexpr int64_t kMaxBlockSize = std::numeric_limits<int16_t>::max();
|
||||
switch (has_bitmap_) {
|
||||
case HasBitmap::BOTH: {
|
||||
BitBlockCount block = binary_counter_.NextOrNotWord();
|
||||
position_ += block.length;
|
||||
return block;
|
||||
}
|
||||
case HasBitmap::ONE: {
|
||||
BitBlockCount block = unary_counter_.NextWord();
|
||||
position_ += block.length;
|
||||
return block;
|
||||
}
|
||||
case HasBitmap::NONE:
|
||||
default: {
|
||||
const int16_t block_size =
|
||||
static_cast<int16_t>(std::min(kMaxBlockSize, length_ - position_));
|
||||
position_ += block_size;
|
||||
// All values are non-null
|
||||
return {block_size, block_size};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
enum class HasBitmap : int { BOTH, ONE, NONE };
|
||||
|
||||
const HasBitmap has_bitmap_;
|
||||
int64_t position_;
|
||||
int64_t length_;
|
||||
BitBlockCounter unary_counter_;
|
||||
BinaryBitBlockCounter binary_counter_;
|
||||
|
||||
static HasBitmap HasBitmapFromBitmaps(bool has_left, bool has_right) {
|
||||
switch (static_cast<int>(has_left) + static_cast<int>(has_right)) {
|
||||
case 0:
|
||||
return HasBitmap::NONE;
|
||||
case 1:
|
||||
return HasBitmap::ONE;
|
||||
default: // 2
|
||||
return HasBitmap::BOTH;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Functional-style bit block visitors.
|
||||
|
||||
template <typename VisitNotNull, typename VisitNull>
|
||||
static Status VisitBitBlocks(const uint8_t* bitmap, int64_t offset, int64_t length,
|
||||
VisitNotNull&& visit_not_null, VisitNull&& visit_null) {
|
||||
internal::OptionalBitBlockCounter bit_counter(bitmap, offset, length);
|
||||
int64_t position = 0;
|
||||
while (position < length) {
|
||||
internal::BitBlockCount block = bit_counter.NextBlock();
|
||||
if (block.AllSet()) {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
ARROW_RETURN_NOT_OK(visit_not_null(position));
|
||||
}
|
||||
} else if (block.NoneSet()) {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
ARROW_RETURN_NOT_OK(visit_null());
|
||||
}
|
||||
} else {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
if (bit_util::GetBit(bitmap, offset + position)) {
|
||||
ARROW_RETURN_NOT_OK(visit_not_null(position));
|
||||
} else {
|
||||
ARROW_RETURN_NOT_OK(visit_null());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
template <typename VisitNotNull, typename VisitNull>
|
||||
static void VisitBitBlocksVoid(const uint8_t* bitmap, int64_t offset, int64_t length,
|
||||
VisitNotNull&& visit_not_null, VisitNull&& visit_null) {
|
||||
internal::OptionalBitBlockCounter bit_counter(bitmap, offset, length);
|
||||
int64_t position = 0;
|
||||
while (position < length) {
|
||||
internal::BitBlockCount block = bit_counter.NextBlock();
|
||||
if (block.AllSet()) {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
visit_not_null(position);
|
||||
}
|
||||
} else if (block.NoneSet()) {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
visit_null();
|
||||
}
|
||||
} else {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
if (bit_util::GetBit(bitmap, offset + position)) {
|
||||
visit_not_null(position);
|
||||
} else {
|
||||
visit_null();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VisitNotNull, typename VisitNull>
|
||||
static Status VisitTwoBitBlocks(const uint8_t* left_bitmap, int64_t left_offset,
|
||||
const uint8_t* right_bitmap, int64_t right_offset,
|
||||
int64_t length, VisitNotNull&& visit_not_null,
|
||||
VisitNull&& visit_null) {
|
||||
if (left_bitmap == NULLPTR || right_bitmap == NULLPTR) {
|
||||
// At most one bitmap is present
|
||||
if (left_bitmap == NULLPTR) {
|
||||
return VisitBitBlocks(right_bitmap, right_offset, length,
|
||||
std::forward<VisitNotNull>(visit_not_null),
|
||||
std::forward<VisitNull>(visit_null));
|
||||
} else {
|
||||
return VisitBitBlocks(left_bitmap, left_offset, length,
|
||||
std::forward<VisitNotNull>(visit_not_null),
|
||||
std::forward<VisitNull>(visit_null));
|
||||
}
|
||||
}
|
||||
BinaryBitBlockCounter bit_counter(left_bitmap, left_offset, right_bitmap, right_offset,
|
||||
length);
|
||||
int64_t position = 0;
|
||||
while (position < length) {
|
||||
BitBlockCount block = bit_counter.NextAndWord();
|
||||
if (block.AllSet()) {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
ARROW_RETURN_NOT_OK(visit_not_null(position));
|
||||
}
|
||||
} else if (block.NoneSet()) {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
ARROW_RETURN_NOT_OK(visit_null());
|
||||
}
|
||||
} else {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
if (bit_util::GetBit(left_bitmap, left_offset + position) &&
|
||||
bit_util::GetBit(right_bitmap, right_offset + position)) {
|
||||
ARROW_RETURN_NOT_OK(visit_not_null(position));
|
||||
} else {
|
||||
ARROW_RETURN_NOT_OK(visit_null());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
template <typename VisitNotNull, typename VisitNull>
|
||||
static void VisitTwoBitBlocksVoid(const uint8_t* left_bitmap, int64_t left_offset,
|
||||
const uint8_t* right_bitmap, int64_t right_offset,
|
||||
int64_t length, VisitNotNull&& visit_not_null,
|
||||
VisitNull&& visit_null) {
|
||||
if (left_bitmap == NULLPTR || right_bitmap == NULLPTR) {
|
||||
// At most one bitmap is present
|
||||
if (left_bitmap == NULLPTR) {
|
||||
return VisitBitBlocksVoid(right_bitmap, right_offset, length,
|
||||
std::forward<VisitNotNull>(visit_not_null),
|
||||
std::forward<VisitNull>(visit_null));
|
||||
} else {
|
||||
return VisitBitBlocksVoid(left_bitmap, left_offset, length,
|
||||
std::forward<VisitNotNull>(visit_not_null),
|
||||
std::forward<VisitNull>(visit_null));
|
||||
}
|
||||
}
|
||||
BinaryBitBlockCounter bit_counter(left_bitmap, left_offset, right_bitmap, right_offset,
|
||||
length);
|
||||
int64_t position = 0;
|
||||
while (position < length) {
|
||||
BitBlockCount block = bit_counter.NextAndWord();
|
||||
if (block.AllSet()) {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
visit_not_null(position);
|
||||
}
|
||||
} else if (block.NoneSet()) {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
visit_null();
|
||||
}
|
||||
} else {
|
||||
for (int64_t i = 0; i < block.length; ++i, ++position) {
|
||||
if (bit_util::GetBit(left_bitmap, left_offset + position) &&
|
||||
bit_util::GetBit(right_bitmap, right_offset + position)) {
|
||||
visit_not_null(position);
|
||||
} else {
|
||||
visit_null();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,515 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "arrow/util/bit_util.h"
|
||||
#include "arrow/util/bitmap_reader.h"
|
||||
#include "arrow/util/endian.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
struct BitRun {
|
||||
int64_t length;
|
||||
// Whether bits are set at this point.
|
||||
bool set;
|
||||
|
||||
std::string ToString() const {
|
||||
return std::string("{Length: ") + std::to_string(length) +
|
||||
", set=" + std::to_string(set) + "}";
|
||||
}
|
||||
};
|
||||
|
||||
inline bool operator==(const BitRun& lhs, const BitRun& rhs) {
|
||||
return lhs.length == rhs.length && lhs.set == rhs.set;
|
||||
}
|
||||
|
||||
inline bool operator!=(const BitRun& lhs, const BitRun& rhs) {
|
||||
return lhs.length != rhs.length || lhs.set != rhs.set;
|
||||
}
|
||||
|
||||
class BitRunReaderLinear {
|
||||
public:
|
||||
BitRunReaderLinear(const uint8_t* bitmap, int64_t start_offset, int64_t length)
|
||||
: reader_(bitmap, start_offset, length) {}
|
||||
|
||||
BitRun NextRun() {
|
||||
BitRun rl = {/*length=*/0, reader_.IsSet()};
|
||||
// Advance while the values are equal and not at the end of list.
|
||||
while (reader_.position() < reader_.length() && reader_.IsSet() == rl.set) {
|
||||
rl.length++;
|
||||
reader_.Next();
|
||||
}
|
||||
return rl;
|
||||
}
|
||||
|
||||
private:
|
||||
BitmapReader reader_;
|
||||
};
|
||||
|
||||
#if ARROW_LITTLE_ENDIAN
|
||||
/// A convenience class for counting the number of contiguous set/unset bits
|
||||
/// in a bitmap.
|
||||
class ARROW_EXPORT BitRunReader {
|
||||
public:
|
||||
/// \brief Constructs new BitRunReader.
|
||||
///
|
||||
/// \param[in] bitmap source data
|
||||
/// \param[in] start_offset bit offset into the source data
|
||||
/// \param[in] length number of bits to copy
|
||||
BitRunReader(const uint8_t* bitmap, int64_t start_offset, int64_t length);
|
||||
|
||||
/// Returns a new BitRun containing the number of contiguous
|
||||
/// bits with the same value. length == 0 indicates the
|
||||
/// end of the bitmap.
|
||||
BitRun NextRun() {
|
||||
if (ARROW_PREDICT_FALSE(position_ >= length_)) {
|
||||
return {/*length=*/0, false};
|
||||
}
|
||||
// This implementation relies on a efficient implementations of
|
||||
// CountTrailingZeros and assumes that runs are more often then
|
||||
// not. The logic is to incrementally find the next bit change
|
||||
// from the current position. This is done by zeroing all
|
||||
// bits in word_ up to position_ and using the TrailingZeroCount
|
||||
// to find the index of the next set bit.
|
||||
|
||||
// The runs alternate on each call, so flip the bit.
|
||||
current_run_bit_set_ = !current_run_bit_set_;
|
||||
|
||||
int64_t start_position = position_;
|
||||
int64_t start_bit_offset = start_position & 63;
|
||||
// Invert the word for proper use of CountTrailingZeros and
|
||||
// clear bits so CountTrailingZeros can do it magic.
|
||||
word_ = ~word_ & ~bit_util::LeastSignificantBitMask(start_bit_offset);
|
||||
|
||||
// Go forward until the next change from unset to set.
|
||||
int64_t new_bits = bit_util::CountTrailingZeros(word_) - start_bit_offset;
|
||||
position_ += new_bits;
|
||||
|
||||
if (ARROW_PREDICT_FALSE(bit_util::IsMultipleOf64(position_)) &&
|
||||
ARROW_PREDICT_TRUE(position_ < length_)) {
|
||||
// Continue extending position while we can advance an entire word.
|
||||
// (updates position_ accordingly).
|
||||
AdvanceUntilChange();
|
||||
}
|
||||
|
||||
return {/*length=*/position_ - start_position, current_run_bit_set_};
|
||||
}
|
||||
|
||||
private:
|
||||
void AdvanceUntilChange() {
|
||||
int64_t new_bits = 0;
|
||||
do {
|
||||
// Advance the position of the bitmap for loading.
|
||||
bitmap_ += sizeof(uint64_t);
|
||||
LoadNextWord();
|
||||
new_bits = bit_util::CountTrailingZeros(word_);
|
||||
// Continue calculating run length.
|
||||
position_ += new_bits;
|
||||
} while (ARROW_PREDICT_FALSE(bit_util::IsMultipleOf64(position_)) &&
|
||||
ARROW_PREDICT_TRUE(position_ < length_) && new_bits > 0);
|
||||
}
|
||||
|
||||
void LoadNextWord() { return LoadWord(length_ - position_); }
|
||||
|
||||
// Helper method for Loading the next word.
|
||||
void LoadWord(int64_t bits_remaining) {
|
||||
word_ = 0;
|
||||
// we need at least an extra byte in this case.
|
||||
if (ARROW_PREDICT_TRUE(bits_remaining >= 64)) {
|
||||
std::memcpy(&word_, bitmap_, 8);
|
||||
} else {
|
||||
int64_t bytes_to_load = bit_util::BytesForBits(bits_remaining);
|
||||
auto word_ptr = reinterpret_cast<uint8_t*>(&word_);
|
||||
std::memcpy(word_ptr, bitmap_, bytes_to_load);
|
||||
// Ensure stoppage at last bit in bitmap by reversing the next higher
|
||||
// order bit.
|
||||
bit_util::SetBitTo(word_ptr, bits_remaining,
|
||||
!bit_util::GetBit(word_ptr, bits_remaining - 1));
|
||||
}
|
||||
|
||||
// Two cases:
|
||||
// 1. For unset, CountTrailingZeros works naturally so we don't
|
||||
// invert the word.
|
||||
// 2. Otherwise invert so we can use CountTrailingZeros.
|
||||
if (current_run_bit_set_) {
|
||||
word_ = ~word_;
|
||||
}
|
||||
}
|
||||
const uint8_t* bitmap_;
|
||||
int64_t position_;
|
||||
int64_t length_;
|
||||
uint64_t word_;
|
||||
bool current_run_bit_set_;
|
||||
};
|
||||
#else
|
||||
using BitRunReader = BitRunReaderLinear;
|
||||
#endif
|
||||
|
||||
struct SetBitRun {
|
||||
int64_t position;
|
||||
int64_t length;
|
||||
|
||||
bool AtEnd() const { return length == 0; }
|
||||
|
||||
std::string ToString() const {
|
||||
return std::string("{pos=") + std::to_string(position) +
|
||||
", len=" + std::to_string(length) + "}";
|
||||
}
|
||||
|
||||
bool operator==(const SetBitRun& other) const {
|
||||
return position == other.position && length == other.length;
|
||||
}
|
||||
bool operator!=(const SetBitRun& other) const {
|
||||
return position != other.position || length != other.length;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool Reverse>
|
||||
class BaseSetBitRunReader {
|
||||
public:
|
||||
/// \brief Constructs new SetBitRunReader.
|
||||
///
|
||||
/// \param[in] bitmap source data
|
||||
/// \param[in] start_offset bit offset into the source data
|
||||
/// \param[in] length number of bits to copy
|
||||
ARROW_NOINLINE
|
||||
BaseSetBitRunReader(const uint8_t* bitmap, int64_t start_offset, int64_t length)
|
||||
: bitmap_(util::MakeNonNull(bitmap)),
|
||||
length_(length),
|
||||
remaining_(length_),
|
||||
current_word_(0),
|
||||
current_num_bits_(0) {
|
||||
if (Reverse) {
|
||||
bitmap_ += (start_offset + length) / 8;
|
||||
const int8_t end_bit_offset = static_cast<int8_t>((start_offset + length) % 8);
|
||||
if (length > 0 && end_bit_offset) {
|
||||
// Get LSBs from last byte
|
||||
++bitmap_;
|
||||
current_num_bits_ =
|
||||
std::min(static_cast<int32_t>(length), static_cast<int32_t>(end_bit_offset));
|
||||
current_word_ = LoadPartialWord(8 - end_bit_offset, current_num_bits_);
|
||||
}
|
||||
} else {
|
||||
bitmap_ += start_offset / 8;
|
||||
const int8_t bit_offset = static_cast<int8_t>(start_offset % 8);
|
||||
if (length > 0 && bit_offset) {
|
||||
// Get MSBs from first byte
|
||||
current_num_bits_ =
|
||||
std::min(static_cast<int32_t>(length), static_cast<int32_t>(8 - bit_offset));
|
||||
current_word_ = LoadPartialWord(bit_offset, current_num_bits_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ARROW_NOINLINE
|
||||
SetBitRun NextRun() {
|
||||
int64_t pos = 0;
|
||||
int64_t len = 0;
|
||||
if (current_num_bits_) {
|
||||
const auto run = FindCurrentRun();
|
||||
assert(remaining_ >= 0);
|
||||
if (run.length && current_num_bits_) {
|
||||
// The run ends in current_word_
|
||||
return AdjustRun(run);
|
||||
}
|
||||
pos = run.position;
|
||||
len = run.length;
|
||||
}
|
||||
if (!len) {
|
||||
// We didn't get any ones in current_word_, so we can skip any zeros
|
||||
// in the following words
|
||||
SkipNextZeros();
|
||||
if (remaining_ == 0) {
|
||||
return {0, 0};
|
||||
}
|
||||
assert(current_num_bits_);
|
||||
pos = position();
|
||||
} else if (!current_num_bits_) {
|
||||
if (ARROW_PREDICT_TRUE(remaining_ >= 64)) {
|
||||
current_word_ = LoadFullWord();
|
||||
current_num_bits_ = 64;
|
||||
} else if (remaining_ > 0) {
|
||||
current_word_ = LoadPartialWord(/*bit_offset=*/0, remaining_);
|
||||
current_num_bits_ = static_cast<int32_t>(remaining_);
|
||||
} else {
|
||||
// No bits remaining, perhaps we found a run?
|
||||
return AdjustRun({pos, len});
|
||||
}
|
||||
// If current word starts with a zero, we got a full run
|
||||
if (!(current_word_ & kFirstBit)) {
|
||||
return AdjustRun({pos, len});
|
||||
}
|
||||
}
|
||||
// Current word should now start with a set bit
|
||||
len += CountNextOnes();
|
||||
return AdjustRun({pos, len});
|
||||
}
|
||||
|
||||
protected:
|
||||
int64_t position() const {
|
||||
if (Reverse) {
|
||||
return remaining_;
|
||||
} else {
|
||||
return length_ - remaining_;
|
||||
}
|
||||
}
|
||||
|
||||
SetBitRun AdjustRun(SetBitRun run) {
|
||||
if (Reverse) {
|
||||
assert(run.position >= run.length);
|
||||
run.position -= run.length;
|
||||
}
|
||||
return run;
|
||||
}
|
||||
|
||||
uint64_t LoadFullWord() {
|
||||
uint64_t word;
|
||||
if (Reverse) {
|
||||
bitmap_ -= 8;
|
||||
}
|
||||
memcpy(&word, bitmap_, 8);
|
||||
if (!Reverse) {
|
||||
bitmap_ += 8;
|
||||
}
|
||||
return bit_util::ToLittleEndian(word);
|
||||
}
|
||||
|
||||
uint64_t LoadPartialWord(int8_t bit_offset, int64_t num_bits) {
|
||||
assert(num_bits > 0);
|
||||
uint64_t word = 0;
|
||||
const int64_t num_bytes = bit_util::BytesForBits(num_bits);
|
||||
if (Reverse) {
|
||||
// Read in the most significant bytes of the word
|
||||
bitmap_ -= num_bytes;
|
||||
memcpy(reinterpret_cast<char*>(&word) + 8 - num_bytes, bitmap_, num_bytes);
|
||||
// XXX MostSignificantBitmask
|
||||
return (bit_util::ToLittleEndian(word) << bit_offset) &
|
||||
~bit_util::LeastSignificantBitMask(64 - num_bits);
|
||||
} else {
|
||||
memcpy(&word, bitmap_, num_bytes);
|
||||
bitmap_ += num_bytes;
|
||||
return (bit_util::ToLittleEndian(word) >> bit_offset) &
|
||||
bit_util::LeastSignificantBitMask(num_bits);
|
||||
}
|
||||
}
|
||||
|
||||
void SkipNextZeros() {
|
||||
assert(current_num_bits_ == 0);
|
||||
while (ARROW_PREDICT_TRUE(remaining_ >= 64)) {
|
||||
current_word_ = LoadFullWord();
|
||||
const auto num_zeros = CountFirstZeros(current_word_);
|
||||
if (num_zeros < 64) {
|
||||
// Run of zeros ends here
|
||||
current_word_ = ConsumeBits(current_word_, num_zeros);
|
||||
current_num_bits_ = 64 - num_zeros;
|
||||
remaining_ -= num_zeros;
|
||||
assert(remaining_ >= 0);
|
||||
assert(current_num_bits_ >= 0);
|
||||
return;
|
||||
}
|
||||
remaining_ -= 64;
|
||||
}
|
||||
// Run of zeros continues in last bitmap word
|
||||
if (remaining_ > 0) {
|
||||
current_word_ = LoadPartialWord(/*bit_offset=*/0, remaining_);
|
||||
current_num_bits_ = static_cast<int32_t>(remaining_);
|
||||
const auto num_zeros =
|
||||
std::min<int32_t>(current_num_bits_, CountFirstZeros(current_word_));
|
||||
current_word_ = ConsumeBits(current_word_, num_zeros);
|
||||
current_num_bits_ -= num_zeros;
|
||||
remaining_ -= num_zeros;
|
||||
assert(remaining_ >= 0);
|
||||
assert(current_num_bits_ >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t CountNextOnes() {
|
||||
assert(current_word_ & kFirstBit);
|
||||
|
||||
int64_t len;
|
||||
if (~current_word_) {
|
||||
const auto num_ones = CountFirstZeros(~current_word_);
|
||||
assert(num_ones <= current_num_bits_);
|
||||
assert(num_ones <= remaining_);
|
||||
remaining_ -= num_ones;
|
||||
current_word_ = ConsumeBits(current_word_, num_ones);
|
||||
current_num_bits_ -= num_ones;
|
||||
if (current_num_bits_) {
|
||||
// Run of ones ends here
|
||||
return num_ones;
|
||||
}
|
||||
len = num_ones;
|
||||
} else {
|
||||
// current_word_ is all ones
|
||||
remaining_ -= 64;
|
||||
current_num_bits_ = 0;
|
||||
len = 64;
|
||||
}
|
||||
|
||||
while (ARROW_PREDICT_TRUE(remaining_ >= 64)) {
|
||||
current_word_ = LoadFullWord();
|
||||
const auto num_ones = CountFirstZeros(~current_word_);
|
||||
len += num_ones;
|
||||
remaining_ -= num_ones;
|
||||
if (num_ones < 64) {
|
||||
// Run of ones ends here
|
||||
current_word_ = ConsumeBits(current_word_, num_ones);
|
||||
current_num_bits_ = 64 - num_ones;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
// Run of ones continues in last bitmap word
|
||||
if (remaining_ > 0) {
|
||||
current_word_ = LoadPartialWord(/*bit_offset=*/0, remaining_);
|
||||
current_num_bits_ = static_cast<int32_t>(remaining_);
|
||||
const auto num_ones = CountFirstZeros(~current_word_);
|
||||
assert(num_ones <= current_num_bits_);
|
||||
assert(num_ones <= remaining_);
|
||||
current_word_ = ConsumeBits(current_word_, num_ones);
|
||||
current_num_bits_ -= num_ones;
|
||||
remaining_ -= num_ones;
|
||||
len += num_ones;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
SetBitRun FindCurrentRun() {
|
||||
// Skip any pending zeros
|
||||
const auto num_zeros = CountFirstZeros(current_word_);
|
||||
if (num_zeros >= current_num_bits_) {
|
||||
remaining_ -= current_num_bits_;
|
||||
current_word_ = 0;
|
||||
current_num_bits_ = 0;
|
||||
return {0, 0};
|
||||
}
|
||||
assert(num_zeros <= remaining_);
|
||||
current_word_ = ConsumeBits(current_word_, num_zeros);
|
||||
current_num_bits_ -= num_zeros;
|
||||
remaining_ -= num_zeros;
|
||||
const int64_t pos = position();
|
||||
// Count any ones
|
||||
const auto num_ones = CountFirstZeros(~current_word_);
|
||||
assert(num_ones <= current_num_bits_);
|
||||
assert(num_ones <= remaining_);
|
||||
current_word_ = ConsumeBits(current_word_, num_ones);
|
||||
current_num_bits_ -= num_ones;
|
||||
remaining_ -= num_ones;
|
||||
return {pos, num_ones};
|
||||
}
|
||||
|
||||
inline int CountFirstZeros(uint64_t word);
|
||||
inline uint64_t ConsumeBits(uint64_t word, int32_t num_bits);
|
||||
|
||||
const uint8_t* bitmap_;
|
||||
const int64_t length_;
|
||||
int64_t remaining_;
|
||||
uint64_t current_word_;
|
||||
int32_t current_num_bits_;
|
||||
|
||||
static constexpr uint64_t kFirstBit = Reverse ? 0x8000000000000000ULL : 1;
|
||||
};
|
||||
|
||||
template <>
|
||||
inline int BaseSetBitRunReader<false>::CountFirstZeros(uint64_t word) {
|
||||
return bit_util::CountTrailingZeros(word);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int BaseSetBitRunReader<true>::CountFirstZeros(uint64_t word) {
|
||||
return bit_util::CountLeadingZeros(word);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint64_t BaseSetBitRunReader<false>::ConsumeBits(uint64_t word, int32_t num_bits) {
|
||||
return word >> num_bits;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint64_t BaseSetBitRunReader<true>::ConsumeBits(uint64_t word, int32_t num_bits) {
|
||||
return word << num_bits;
|
||||
}
|
||||
|
||||
using SetBitRunReader = BaseSetBitRunReader</*Reverse=*/false>;
|
||||
using ReverseSetBitRunReader = BaseSetBitRunReader</*Reverse=*/true>;
|
||||
|
||||
// Functional-style bit run visitors.
|
||||
|
||||
// XXX: Try to make this function small so the compiler can inline and optimize
|
||||
// the `visit` function, which is normally a hot loop with vectorizable code.
|
||||
// - don't inline SetBitRunReader constructor, it doesn't hurt performance
|
||||
// - un-inline NextRun hurts 'many null' cases a bit, but improves normal cases
|
||||
template <typename Visit>
|
||||
inline Status VisitSetBitRuns(const uint8_t* bitmap, int64_t offset, int64_t length,
|
||||
Visit&& visit) {
|
||||
if (bitmap == NULLPTR) {
|
||||
// Assuming all set (as in a null bitmap)
|
||||
return visit(static_cast<int64_t>(0), static_cast<int64_t>(length));
|
||||
}
|
||||
SetBitRunReader reader(bitmap, offset, length);
|
||||
while (true) {
|
||||
const auto run = reader.NextRun();
|
||||
if (run.length == 0) {
|
||||
break;
|
||||
}
|
||||
ARROW_RETURN_NOT_OK(visit(run.position, run.length));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
template <typename Visit>
|
||||
inline void VisitSetBitRunsVoid(const uint8_t* bitmap, int64_t offset, int64_t length,
|
||||
Visit&& visit) {
|
||||
if (bitmap == NULLPTR) {
|
||||
// Assuming all set (as in a null bitmap)
|
||||
visit(static_cast<int64_t>(0), static_cast<int64_t>(length));
|
||||
return;
|
||||
}
|
||||
SetBitRunReader reader(bitmap, offset, length);
|
||||
while (true) {
|
||||
const auto run = reader.NextRun();
|
||||
if (run.length == 0) {
|
||||
break;
|
||||
}
|
||||
visit(run.position, run.length);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Visit>
|
||||
inline Status VisitSetBitRuns(const std::shared_ptr<Buffer>& bitmap, int64_t offset,
|
||||
int64_t length, Visit&& visit) {
|
||||
return VisitSetBitRuns(bitmap ? bitmap->data() : NULLPTR, offset, length,
|
||||
std::forward<Visit>(visit));
|
||||
}
|
||||
|
||||
template <typename Visit>
|
||||
inline void VisitSetBitRunsVoid(const std::shared_ptr<Buffer>& bitmap, int64_t offset,
|
||||
int64_t length, Visit&& visit) {
|
||||
VisitSetBitRunsVoid(bitmap ? bitmap->data() : NULLPTR, offset, length,
|
||||
std::forward<Visit>(visit));
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,534 @@
|
||||
// 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.
|
||||
|
||||
// From Apache Impala (incubating) as of 2016-01-29
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "arrow/util/bit_util.h"
|
||||
#include "arrow/util/bpacking.h"
|
||||
#include "arrow/util/logging.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/ubsan.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace bit_util {
|
||||
|
||||
/// Utility class to write bit/byte streams. This class can write data to either be
|
||||
/// bit packed or byte aligned (and a single stream that has a mix of both).
|
||||
/// This class does not allocate memory.
|
||||
class BitWriter {
|
||||
public:
|
||||
/// buffer: buffer to write bits to. Buffer should be preallocated with
|
||||
/// 'buffer_len' bytes.
|
||||
BitWriter(uint8_t* buffer, int buffer_len) : buffer_(buffer), max_bytes_(buffer_len) {
|
||||
Clear();
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
buffered_values_ = 0;
|
||||
byte_offset_ = 0;
|
||||
bit_offset_ = 0;
|
||||
}
|
||||
|
||||
/// The number of current bytes written, including the current byte (i.e. may include a
|
||||
/// fraction of a byte). Includes buffered values.
|
||||
int bytes_written() const {
|
||||
return byte_offset_ + static_cast<int>(bit_util::BytesForBits(bit_offset_));
|
||||
}
|
||||
uint8_t* buffer() const { return buffer_; }
|
||||
int buffer_len() const { return max_bytes_; }
|
||||
|
||||
/// Writes a value to buffered_values_, flushing to buffer_ if necessary. This is bit
|
||||
/// packed. Returns false if there was not enough space. num_bits must be <= 32.
|
||||
bool PutValue(uint64_t v, int num_bits);
|
||||
|
||||
/// Writes v to the next aligned byte using num_bytes. If T is larger than
|
||||
/// num_bytes, the extra high-order bytes will be ignored. Returns false if
|
||||
/// there was not enough space.
|
||||
/// Assume the v is stored in buffer_ as a litte-endian format
|
||||
template <typename T>
|
||||
bool PutAligned(T v, int num_bytes);
|
||||
|
||||
/// Write a Vlq encoded int to the buffer. Returns false if there was not enough
|
||||
/// room. The value is written byte aligned.
|
||||
/// For more details on vlq:
|
||||
/// en.wikipedia.org/wiki/Variable-length_quantity
|
||||
bool PutVlqInt(uint32_t v);
|
||||
|
||||
// Writes an int zigzag encoded.
|
||||
bool PutZigZagVlqInt(int32_t v);
|
||||
|
||||
/// Write a Vlq encoded int64 to the buffer. Returns false if there was not enough
|
||||
/// room. The value is written byte aligned.
|
||||
/// For more details on vlq:
|
||||
/// en.wikipedia.org/wiki/Variable-length_quantity
|
||||
bool PutVlqInt(uint64_t v);
|
||||
|
||||
// Writes an int64 zigzag encoded.
|
||||
bool PutZigZagVlqInt(int64_t v);
|
||||
|
||||
/// Get a pointer to the next aligned byte and advance the underlying buffer
|
||||
/// by num_bytes.
|
||||
/// Returns NULL if there was not enough space.
|
||||
uint8_t* GetNextBytePtr(int num_bytes = 1);
|
||||
|
||||
/// Flushes all buffered values to the buffer. Call this when done writing to
|
||||
/// the buffer. If 'align' is true, buffered_values_ is reset and any future
|
||||
/// writes will be written to the next byte boundary.
|
||||
void Flush(bool align = false);
|
||||
|
||||
private:
|
||||
uint8_t* buffer_;
|
||||
int max_bytes_;
|
||||
|
||||
/// Bit-packed values are initially written to this variable before being memcpy'd to
|
||||
/// buffer_. This is faster than writing values byte by byte directly to buffer_.
|
||||
uint64_t buffered_values_;
|
||||
|
||||
int byte_offset_; // Offset in buffer_
|
||||
int bit_offset_; // Offset in buffered_values_
|
||||
};
|
||||
|
||||
/// Utility class to read bit/byte stream. This class can read bits or bytes
|
||||
/// that are either byte aligned or not. It also has utilities to read multiple
|
||||
/// bytes in one read (e.g. encoded int).
|
||||
class BitReader {
|
||||
public:
|
||||
/// 'buffer' is the buffer to read from. The buffer's length is 'buffer_len'.
|
||||
BitReader(const uint8_t* buffer, int buffer_len)
|
||||
: buffer_(buffer), max_bytes_(buffer_len), byte_offset_(0), bit_offset_(0) {
|
||||
int num_bytes = std::min(8, max_bytes_ - byte_offset_);
|
||||
memcpy(&buffered_values_, buffer_ + byte_offset_, num_bytes);
|
||||
buffered_values_ = arrow::bit_util::FromLittleEndian(buffered_values_);
|
||||
}
|
||||
|
||||
BitReader()
|
||||
: buffer_(NULL),
|
||||
max_bytes_(0),
|
||||
buffered_values_(0),
|
||||
byte_offset_(0),
|
||||
bit_offset_(0) {}
|
||||
|
||||
void Reset(const uint8_t* buffer, int buffer_len) {
|
||||
buffer_ = buffer;
|
||||
max_bytes_ = buffer_len;
|
||||
byte_offset_ = 0;
|
||||
bit_offset_ = 0;
|
||||
int num_bytes = std::min(8, max_bytes_ - byte_offset_);
|
||||
memcpy(&buffered_values_, buffer_ + byte_offset_, num_bytes);
|
||||
buffered_values_ = arrow::bit_util::FromLittleEndian(buffered_values_);
|
||||
}
|
||||
|
||||
/// Gets the next value from the buffer. Returns true if 'v' could be read or false if
|
||||
/// there are not enough bytes left.
|
||||
template <typename T>
|
||||
bool GetValue(int num_bits, T* v);
|
||||
|
||||
/// Get a number of values from the buffer. Return the number of values actually read.
|
||||
template <typename T>
|
||||
int GetBatch(int num_bits, T* v, int batch_size);
|
||||
|
||||
/// Reads a 'num_bytes'-sized value from the buffer and stores it in 'v'. T
|
||||
/// needs to be a little-endian native type and big enough to store
|
||||
/// 'num_bytes'. The value is assumed to be byte-aligned so the stream will
|
||||
/// be advanced to the start of the next byte before 'v' is read. Returns
|
||||
/// false if there are not enough bytes left.
|
||||
/// Assume the v was stored in buffer_ as a litte-endian format
|
||||
template <typename T>
|
||||
bool GetAligned(int num_bytes, T* v);
|
||||
|
||||
/// Advances the stream by a number of bits. Returns true if succeed or false if there
|
||||
/// are not enough bits left.
|
||||
bool Advance(int64_t num_bits);
|
||||
|
||||
/// Reads a vlq encoded int from the stream. The encoded int must start at
|
||||
/// the beginning of a byte. Return false if there were not enough bytes in
|
||||
/// the buffer.
|
||||
bool GetVlqInt(uint32_t* v);
|
||||
|
||||
// Reads a zigzag encoded int `into` v.
|
||||
bool GetZigZagVlqInt(int32_t* v);
|
||||
|
||||
/// Reads a vlq encoded int64 from the stream. The encoded int must start at
|
||||
/// the beginning of a byte. Return false if there were not enough bytes in
|
||||
/// the buffer.
|
||||
bool GetVlqInt(uint64_t* v);
|
||||
|
||||
// Reads a zigzag encoded int64 `into` v.
|
||||
bool GetZigZagVlqInt(int64_t* v);
|
||||
|
||||
/// Returns the number of bytes left in the stream, not including the current
|
||||
/// byte (i.e., there may be an additional fraction of a byte).
|
||||
int bytes_left() {
|
||||
return max_bytes_ -
|
||||
(byte_offset_ + static_cast<int>(bit_util::BytesForBits(bit_offset_)));
|
||||
}
|
||||
|
||||
/// Maximum byte length of a vlq encoded int
|
||||
static constexpr int kMaxVlqByteLength = 5;
|
||||
|
||||
/// Maximum byte length of a vlq encoded int64
|
||||
static constexpr int kMaxVlqByteLengthForInt64 = 10;
|
||||
|
||||
private:
|
||||
const uint8_t* buffer_;
|
||||
int max_bytes_;
|
||||
|
||||
/// Bytes are memcpy'd from buffer_ and values are read from this variable. This is
|
||||
/// faster than reading values byte by byte directly from buffer_.
|
||||
uint64_t buffered_values_;
|
||||
|
||||
int byte_offset_; // Offset in buffer_
|
||||
int bit_offset_; // Offset in buffered_values_
|
||||
};
|
||||
|
||||
inline bool BitWriter::PutValue(uint64_t v, int num_bits) {
|
||||
DCHECK_LE(num_bits, 64);
|
||||
if (num_bits < 64) {
|
||||
DCHECK_EQ(v >> num_bits, 0) << "v = " << v << ", num_bits = " << num_bits;
|
||||
}
|
||||
|
||||
if (ARROW_PREDICT_FALSE(byte_offset_ * 8 + bit_offset_ + num_bits > max_bytes_ * 8))
|
||||
return false;
|
||||
|
||||
buffered_values_ |= v << bit_offset_;
|
||||
bit_offset_ += num_bits;
|
||||
|
||||
if (ARROW_PREDICT_FALSE(bit_offset_ >= 64)) {
|
||||
// Flush buffered_values_ and write out bits of v that did not fit
|
||||
buffered_values_ = arrow::bit_util::ToLittleEndian(buffered_values_);
|
||||
memcpy(buffer_ + byte_offset_, &buffered_values_, 8);
|
||||
buffered_values_ = 0;
|
||||
byte_offset_ += 8;
|
||||
bit_offset_ -= 64;
|
||||
buffered_values_ =
|
||||
(num_bits - bit_offset_ == 64) ? 0 : (v >> (num_bits - bit_offset_));
|
||||
}
|
||||
DCHECK_LT(bit_offset_, 64);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void BitWriter::Flush(bool align) {
|
||||
int num_bytes = static_cast<int>(bit_util::BytesForBits(bit_offset_));
|
||||
DCHECK_LE(byte_offset_ + num_bytes, max_bytes_);
|
||||
auto buffered_values = arrow::bit_util::ToLittleEndian(buffered_values_);
|
||||
memcpy(buffer_ + byte_offset_, &buffered_values, num_bytes);
|
||||
|
||||
if (align) {
|
||||
buffered_values_ = 0;
|
||||
byte_offset_ += num_bytes;
|
||||
bit_offset_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline uint8_t* BitWriter::GetNextBytePtr(int num_bytes) {
|
||||
Flush(/* align */ true);
|
||||
DCHECK_LE(byte_offset_, max_bytes_);
|
||||
if (byte_offset_ + num_bytes > max_bytes_) return NULL;
|
||||
uint8_t* ptr = buffer_ + byte_offset_;
|
||||
byte_offset_ += num_bytes;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool BitWriter::PutAligned(T val, int num_bytes) {
|
||||
uint8_t* ptr = GetNextBytePtr(num_bytes);
|
||||
if (ptr == NULL) return false;
|
||||
val = arrow::bit_util::ToLittleEndian(val);
|
||||
memcpy(ptr, &val, num_bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline void ResetBufferedValues_(const uint8_t* buffer, int byte_offset,
|
||||
int bytes_remaining, uint64_t* buffered_values) {
|
||||
if (ARROW_PREDICT_TRUE(bytes_remaining >= 8)) {
|
||||
memcpy(buffered_values, buffer + byte_offset, 8);
|
||||
} else {
|
||||
memcpy(buffered_values, buffer + byte_offset, bytes_remaining);
|
||||
}
|
||||
*buffered_values = arrow::bit_util::FromLittleEndian(*buffered_values);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void GetValue_(int num_bits, T* v, int max_bytes, const uint8_t* buffer,
|
||||
int* bit_offset, int* byte_offset, uint64_t* buffered_values) {
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4800)
|
||||
#endif
|
||||
*v = static_cast<T>(bit_util::TrailingBits(*buffered_values, *bit_offset + num_bits) >>
|
||||
*bit_offset);
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
*bit_offset += num_bits;
|
||||
if (*bit_offset >= 64) {
|
||||
*byte_offset += 8;
|
||||
*bit_offset -= 64;
|
||||
|
||||
ResetBufferedValues_(buffer, *byte_offset, max_bytes - *byte_offset, buffered_values);
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4800 4805)
|
||||
#endif
|
||||
// Read bits of v that crossed into new buffered_values_
|
||||
if (ARROW_PREDICT_TRUE(num_bits - *bit_offset < static_cast<int>(8 * sizeof(T)))) {
|
||||
// if shift exponent(num_bits - *bit_offset) is not less than sizeof(T), *v will not
|
||||
// change and the following code may cause a runtime error that the shift exponent
|
||||
// is too large
|
||||
*v = *v | static_cast<T>(bit_util::TrailingBits(*buffered_values, *bit_offset)
|
||||
<< (num_bits - *bit_offset));
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
DCHECK_LE(*bit_offset, 64);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
inline bool BitReader::GetValue(int num_bits, T* v) {
|
||||
return GetBatch(num_bits, v, 1) == 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline int BitReader::GetBatch(int num_bits, T* v, int batch_size) {
|
||||
DCHECK(buffer_ != NULL);
|
||||
DCHECK_LE(num_bits, static_cast<int>(sizeof(T) * 8));
|
||||
|
||||
int bit_offset = bit_offset_;
|
||||
int byte_offset = byte_offset_;
|
||||
uint64_t buffered_values = buffered_values_;
|
||||
int max_bytes = max_bytes_;
|
||||
const uint8_t* buffer = buffer_;
|
||||
|
||||
const int64_t needed_bits = num_bits * static_cast<int64_t>(batch_size);
|
||||
constexpr uint64_t kBitsPerByte = 8;
|
||||
const int64_t remaining_bits =
|
||||
static_cast<int64_t>(max_bytes - byte_offset) * kBitsPerByte - bit_offset;
|
||||
if (remaining_bits < needed_bits) {
|
||||
batch_size = static_cast<int>(remaining_bits / num_bits);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
if (ARROW_PREDICT_FALSE(bit_offset != 0)) {
|
||||
for (; i < batch_size && bit_offset != 0; ++i) {
|
||||
detail::GetValue_(num_bits, &v[i], max_bytes, buffer, &bit_offset, &byte_offset,
|
||||
&buffered_values);
|
||||
}
|
||||
}
|
||||
|
||||
if (sizeof(T) == 4) {
|
||||
int num_unpacked =
|
||||
internal::unpack32(reinterpret_cast<const uint32_t*>(buffer + byte_offset),
|
||||
reinterpret_cast<uint32_t*>(v + i), batch_size - i, num_bits);
|
||||
i += num_unpacked;
|
||||
byte_offset += num_unpacked * num_bits / 8;
|
||||
} else if (sizeof(T) == 8 && num_bits > 32) {
|
||||
// Use unpack64 only if num_bits is larger than 32
|
||||
// TODO (ARROW-13677): improve the performance of internal::unpack64
|
||||
// and remove the restriction of num_bits
|
||||
int num_unpacked =
|
||||
internal::unpack64(buffer + byte_offset, reinterpret_cast<uint64_t*>(v + i),
|
||||
batch_size - i, num_bits);
|
||||
i += num_unpacked;
|
||||
byte_offset += num_unpacked * num_bits / 8;
|
||||
} else {
|
||||
// TODO: revisit this limit if necessary
|
||||
DCHECK_LE(num_bits, 32);
|
||||
const int buffer_size = 1024;
|
||||
uint32_t unpack_buffer[buffer_size];
|
||||
while (i < batch_size) {
|
||||
int unpack_size = std::min(buffer_size, batch_size - i);
|
||||
int num_unpacked =
|
||||
internal::unpack32(reinterpret_cast<const uint32_t*>(buffer + byte_offset),
|
||||
unpack_buffer, unpack_size, num_bits);
|
||||
if (num_unpacked == 0) {
|
||||
break;
|
||||
}
|
||||
for (int k = 0; k < num_unpacked; ++k) {
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4800)
|
||||
#endif
|
||||
v[i + k] = static_cast<T>(unpack_buffer[k]);
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
}
|
||||
i += num_unpacked;
|
||||
byte_offset += num_unpacked * num_bits / 8;
|
||||
}
|
||||
}
|
||||
|
||||
detail::ResetBufferedValues_(buffer, byte_offset, max_bytes - byte_offset,
|
||||
&buffered_values);
|
||||
|
||||
for (; i < batch_size; ++i) {
|
||||
detail::GetValue_(num_bits, &v[i], max_bytes, buffer, &bit_offset, &byte_offset,
|
||||
&buffered_values);
|
||||
}
|
||||
|
||||
bit_offset_ = bit_offset;
|
||||
byte_offset_ = byte_offset;
|
||||
buffered_values_ = buffered_values;
|
||||
|
||||
return batch_size;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool BitReader::GetAligned(int num_bytes, T* v) {
|
||||
if (ARROW_PREDICT_FALSE(num_bytes > static_cast<int>(sizeof(T)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int bytes_read = static_cast<int>(bit_util::BytesForBits(bit_offset_));
|
||||
if (ARROW_PREDICT_FALSE(byte_offset_ + bytes_read + num_bytes > max_bytes_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Advance byte_offset to next unread byte and read num_bytes
|
||||
byte_offset_ += bytes_read;
|
||||
if constexpr (std::is_same_v<T, bool>) {
|
||||
// ARROW-18031: if we're trying to get an aligned bool, just check
|
||||
// the LSB of the next byte and move on. If we memcpy + FromLittleEndian
|
||||
// as usual, we have potential undefined behavior for bools if the value
|
||||
// isn't 0 or 1
|
||||
*v = *(buffer_ + byte_offset_) & 1;
|
||||
} else {
|
||||
memcpy(v, buffer_ + byte_offset_, num_bytes);
|
||||
*v = arrow::bit_util::FromLittleEndian(*v);
|
||||
}
|
||||
byte_offset_ += num_bytes;
|
||||
|
||||
bit_offset_ = 0;
|
||||
detail::ResetBufferedValues_(buffer_, byte_offset_, max_bytes_ - byte_offset_,
|
||||
&buffered_values_);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool BitReader::Advance(int64_t num_bits) {
|
||||
int64_t bits_required = bit_offset_ + num_bits;
|
||||
int64_t bytes_required = bit_util::BytesForBits(bits_required);
|
||||
if (ARROW_PREDICT_FALSE(bytes_required > max_bytes_ - byte_offset_)) {
|
||||
return false;
|
||||
}
|
||||
byte_offset_ += static_cast<int>(bits_required >> 3);
|
||||
bit_offset_ = static_cast<int>(bits_required & 7);
|
||||
detail::ResetBufferedValues_(buffer_, byte_offset_, max_bytes_ - byte_offset_,
|
||||
&buffered_values_);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool BitWriter::PutVlqInt(uint32_t v) {
|
||||
bool result = true;
|
||||
while ((v & 0xFFFFFF80UL) != 0UL) {
|
||||
result &= PutAligned<uint8_t>(static_cast<uint8_t>((v & 0x7F) | 0x80), 1);
|
||||
v >>= 7;
|
||||
}
|
||||
result &= PutAligned<uint8_t>(static_cast<uint8_t>(v & 0x7F), 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool BitReader::GetVlqInt(uint32_t* v) {
|
||||
uint32_t tmp = 0;
|
||||
|
||||
for (int i = 0; i < kMaxVlqByteLength; i++) {
|
||||
uint8_t byte = 0;
|
||||
if (ARROW_PREDICT_FALSE(!GetAligned<uint8_t>(1, &byte))) {
|
||||
return false;
|
||||
}
|
||||
tmp |= static_cast<uint32_t>(byte & 0x7F) << (7 * i);
|
||||
|
||||
if ((byte & 0x80) == 0) {
|
||||
*v = tmp;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool BitWriter::PutZigZagVlqInt(int32_t v) {
|
||||
uint32_t u_v = ::arrow::util::SafeCopy<uint32_t>(v);
|
||||
u_v = (u_v << 1) ^ static_cast<uint32_t>(v >> 31);
|
||||
return PutVlqInt(u_v);
|
||||
}
|
||||
|
||||
inline bool BitReader::GetZigZagVlqInt(int32_t* v) {
|
||||
uint32_t u;
|
||||
if (!GetVlqInt(&u)) return false;
|
||||
u = (u >> 1) ^ (~(u & 1) + 1);
|
||||
*v = ::arrow::util::SafeCopy<int32_t>(u);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool BitWriter::PutVlqInt(uint64_t v) {
|
||||
bool result = true;
|
||||
while ((v & 0xFFFFFFFFFFFFFF80ULL) != 0ULL) {
|
||||
result &= PutAligned<uint8_t>(static_cast<uint8_t>((v & 0x7F) | 0x80), 1);
|
||||
v >>= 7;
|
||||
}
|
||||
result &= PutAligned<uint8_t>(static_cast<uint8_t>(v & 0x7F), 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool BitReader::GetVlqInt(uint64_t* v) {
|
||||
uint64_t tmp = 0;
|
||||
|
||||
for (int i = 0; i < kMaxVlqByteLengthForInt64; i++) {
|
||||
uint8_t byte = 0;
|
||||
if (ARROW_PREDICT_FALSE(!GetAligned<uint8_t>(1, &byte))) {
|
||||
return false;
|
||||
}
|
||||
tmp |= static_cast<uint64_t>(byte & 0x7F) << (7 * i);
|
||||
|
||||
if ((byte & 0x80) == 0) {
|
||||
*v = tmp;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool BitWriter::PutZigZagVlqInt(int64_t v) {
|
||||
uint64_t u_v = ::arrow::util::SafeCopy<uint64_t>(v);
|
||||
u_v = (u_v << 1) ^ static_cast<uint64_t>(v >> 63);
|
||||
return PutVlqInt(u_v);
|
||||
}
|
||||
|
||||
inline bool BitReader::GetZigZagVlqInt(int64_t* v) {
|
||||
uint64_t u;
|
||||
if (!GetVlqInt(&u)) return false;
|
||||
u = (u >> 1) ^ (~(u & 1) + 1);
|
||||
*v = ::arrow::util::SafeCopy<int64_t>(u);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace bit_util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,367 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#if defined(_M_AMD64) || defined(_M_X64)
|
||||
#include <intrin.h> // IWYU pragma: keep
|
||||
#include <nmmintrin.h>
|
||||
#endif
|
||||
|
||||
#pragma intrinsic(_BitScanReverse)
|
||||
#pragma intrinsic(_BitScanForward)
|
||||
#define ARROW_POPCOUNT64 __popcnt64
|
||||
#define ARROW_POPCOUNT32 __popcnt
|
||||
#else
|
||||
#define ARROW_POPCOUNT64 __builtin_popcountll
|
||||
#define ARROW_POPCOUNT32 __builtin_popcount
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace detail {
|
||||
|
||||
template <typename Integer>
|
||||
typename std::make_unsigned<Integer>::type as_unsigned(Integer x) {
|
||||
return static_cast<typename std::make_unsigned<Integer>::type>(x);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
namespace bit_util {
|
||||
|
||||
// The number of set bits in a given unsigned byte value, pre-computed
|
||||
//
|
||||
// Generated with the following Python code
|
||||
// output = 'static constexpr uint8_t kBytePopcount[] = {{{0}}};'
|
||||
// popcounts = [str(bin(i).count('1')) for i in range(0, 256)]
|
||||
// print(output.format(', '.join(popcounts)))
|
||||
static constexpr uint8_t kBytePopcount[] = {
|
||||
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3,
|
||||
4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4,
|
||||
4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4,
|
||||
5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5,
|
||||
4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2,
|
||||
3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5,
|
||||
5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4,
|
||||
5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
|
||||
|
||||
static inline uint64_t PopCount(uint64_t bitmap) { return ARROW_POPCOUNT64(bitmap); }
|
||||
static inline uint32_t PopCount(uint32_t bitmap) { return ARROW_POPCOUNT32(bitmap); }
|
||||
|
||||
//
|
||||
// Bit-related computations on integer values
|
||||
//
|
||||
|
||||
// Returns the ceil of value/divisor
|
||||
constexpr int64_t CeilDiv(int64_t value, int64_t divisor) {
|
||||
return (value == 0) ? 0 : 1 + (value - 1) / divisor;
|
||||
}
|
||||
|
||||
// Return the number of bytes needed to fit the given number of bits
|
||||
constexpr int64_t BytesForBits(int64_t bits) {
|
||||
// This formula avoids integer overflow on very large `bits`
|
||||
return (bits >> 3) + ((bits & 7) != 0);
|
||||
}
|
||||
|
||||
constexpr bool IsPowerOf2(int64_t value) {
|
||||
return value > 0 && (value & (value - 1)) == 0;
|
||||
}
|
||||
|
||||
constexpr bool IsPowerOf2(uint64_t value) {
|
||||
return value > 0 && (value & (value - 1)) == 0;
|
||||
}
|
||||
|
||||
// Returns the smallest power of two that contains v. If v is already a
|
||||
// power of two, it is returned as is.
|
||||
static inline int64_t NextPower2(int64_t n) {
|
||||
// Taken from
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
n--;
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
n |= n >> 16;
|
||||
n |= n >> 32;
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
constexpr bool IsMultipleOf64(int64_t n) { return (n & 63) == 0; }
|
||||
|
||||
constexpr bool IsMultipleOf8(int64_t n) { return (n & 7) == 0; }
|
||||
|
||||
// Returns a mask for the bit_index lower order bits.
|
||||
// Only valid for bit_index in the range [0, 64).
|
||||
constexpr uint64_t LeastSignificantBitMask(int64_t bit_index) {
|
||||
return (static_cast<uint64_t>(1) << bit_index) - 1;
|
||||
}
|
||||
|
||||
// Returns 'value' rounded up to the nearest multiple of 'factor'
|
||||
constexpr int64_t RoundUp(int64_t value, int64_t factor) {
|
||||
return CeilDiv(value, factor) * factor;
|
||||
}
|
||||
|
||||
// Returns 'value' rounded down to the nearest multiple of 'factor'
|
||||
constexpr int64_t RoundDown(int64_t value, int64_t factor) {
|
||||
return (value / factor) * factor;
|
||||
}
|
||||
|
||||
// Returns 'value' rounded up to the nearest multiple of 'factor' when factor
|
||||
// is a power of two.
|
||||
// The result is undefined on overflow, i.e. if `value > 2**64 - factor`,
|
||||
// since we cannot return the correct result which would be 2**64.
|
||||
constexpr int64_t RoundUpToPowerOf2(int64_t value, int64_t factor) {
|
||||
// DCHECK(value >= 0);
|
||||
// DCHECK(IsPowerOf2(factor));
|
||||
return (value + (factor - 1)) & ~(factor - 1);
|
||||
}
|
||||
|
||||
constexpr uint64_t RoundUpToPowerOf2(uint64_t value, uint64_t factor) {
|
||||
// DCHECK(IsPowerOf2(factor));
|
||||
return (value + (factor - 1)) & ~(factor - 1);
|
||||
}
|
||||
|
||||
constexpr int64_t RoundUpToMultipleOf8(int64_t num) { return RoundUpToPowerOf2(num, 8); }
|
||||
|
||||
constexpr int64_t RoundUpToMultipleOf64(int64_t num) {
|
||||
return RoundUpToPowerOf2(num, 64);
|
||||
}
|
||||
|
||||
// Returns the number of bytes covering a sliced bitmap. Find the length
|
||||
// rounded to cover full bytes on both extremities.
|
||||
//
|
||||
// The following example represents a slice (offset=10, length=9)
|
||||
//
|
||||
// 0 8 16 24
|
||||
// |-------|-------|------|
|
||||
// [ ] (slice)
|
||||
// [ ] (same slice aligned to bytes bounds, length=16)
|
||||
//
|
||||
// The covering bytes is the length (in bytes) of this new aligned slice.
|
||||
constexpr int64_t CoveringBytes(int64_t offset, int64_t length) {
|
||||
return (bit_util::RoundUp(length + offset, 8) - bit_util::RoundDown(offset, 8)) / 8;
|
||||
}
|
||||
|
||||
// Returns the 'num_bits' least-significant bits of 'v'.
|
||||
static inline uint64_t TrailingBits(uint64_t v, int num_bits) {
|
||||
if (ARROW_PREDICT_FALSE(num_bits == 0)) return 0;
|
||||
if (ARROW_PREDICT_FALSE(num_bits >= 64)) return v;
|
||||
int n = 64 - num_bits;
|
||||
return (v << n) >> n;
|
||||
}
|
||||
|
||||
/// \brief Count the number of leading zeros in an unsigned integer.
|
||||
static inline int CountLeadingZeros(uint32_t value) {
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
if (value == 0) return 32;
|
||||
return static_cast<int>(__builtin_clz(value));
|
||||
#elif defined(_MSC_VER)
|
||||
unsigned long index; // NOLINT
|
||||
if (_BitScanReverse(&index, static_cast<unsigned long>(value))) { // NOLINT
|
||||
return 31 - static_cast<int>(index);
|
||||
} else {
|
||||
return 32;
|
||||
}
|
||||
#else
|
||||
int bitpos = 0;
|
||||
while (value != 0) {
|
||||
value >>= 1;
|
||||
++bitpos;
|
||||
}
|
||||
return 32 - bitpos;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int CountLeadingZeros(uint64_t value) {
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
if (value == 0) return 64;
|
||||
return static_cast<int>(__builtin_clzll(value));
|
||||
#elif defined(_MSC_VER)
|
||||
unsigned long index; // NOLINT
|
||||
if (_BitScanReverse64(&index, value)) { // NOLINT
|
||||
return 63 - static_cast<int>(index);
|
||||
} else {
|
||||
return 64;
|
||||
}
|
||||
#else
|
||||
int bitpos = 0;
|
||||
while (value != 0) {
|
||||
value >>= 1;
|
||||
++bitpos;
|
||||
}
|
||||
return 64 - bitpos;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int CountTrailingZeros(uint32_t value) {
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
if (value == 0) return 32;
|
||||
return static_cast<int>(__builtin_ctzl(value));
|
||||
#elif defined(_MSC_VER)
|
||||
unsigned long index; // NOLINT
|
||||
if (_BitScanForward(&index, value)) {
|
||||
return static_cast<int>(index);
|
||||
} else {
|
||||
return 32;
|
||||
}
|
||||
#else
|
||||
int bitpos = 0;
|
||||
if (value) {
|
||||
while (value & 1 == 0) {
|
||||
value >>= 1;
|
||||
++bitpos;
|
||||
}
|
||||
} else {
|
||||
bitpos = 32;
|
||||
}
|
||||
return bitpos;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int CountTrailingZeros(uint64_t value) {
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
if (value == 0) return 64;
|
||||
return static_cast<int>(__builtin_ctzll(value));
|
||||
#elif defined(_MSC_VER)
|
||||
unsigned long index; // NOLINT
|
||||
if (_BitScanForward64(&index, value)) {
|
||||
return static_cast<int>(index);
|
||||
} else {
|
||||
return 64;
|
||||
}
|
||||
#else
|
||||
int bitpos = 0;
|
||||
if (value) {
|
||||
while (value & 1 == 0) {
|
||||
value >>= 1;
|
||||
++bitpos;
|
||||
}
|
||||
} else {
|
||||
bitpos = 64;
|
||||
}
|
||||
return bitpos;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns the minimum number of bits needed to represent an unsigned value
|
||||
static inline int NumRequiredBits(uint64_t x) { return 64 - CountLeadingZeros(x); }
|
||||
|
||||
// Returns ceil(log2(x)).
|
||||
static inline int Log2(uint64_t x) {
|
||||
// DCHECK_GT(x, 0);
|
||||
return NumRequiredBits(x - 1);
|
||||
}
|
||||
|
||||
//
|
||||
// Utilities for reading and writing individual bits by their index
|
||||
// in a memory area.
|
||||
//
|
||||
|
||||
// Bitmask selecting the k-th bit in a byte
|
||||
static constexpr uint8_t kBitmask[] = {1, 2, 4, 8, 16, 32, 64, 128};
|
||||
|
||||
// the bitwise complement version of kBitmask
|
||||
static constexpr uint8_t kFlippedBitmask[] = {254, 253, 251, 247, 239, 223, 191, 127};
|
||||
|
||||
// Bitmask selecting the (k - 1) preceding bits in a byte
|
||||
static constexpr uint8_t kPrecedingBitmask[] = {0, 1, 3, 7, 15, 31, 63, 127};
|
||||
static constexpr uint8_t kPrecedingWrappingBitmask[] = {255, 1, 3, 7, 15, 31, 63, 127};
|
||||
|
||||
// the bitwise complement version of kPrecedingBitmask
|
||||
static constexpr uint8_t kTrailingBitmask[] = {255, 254, 252, 248, 240, 224, 192, 128};
|
||||
|
||||
static constexpr bool GetBit(const uint8_t* bits, uint64_t i) {
|
||||
return (bits[i >> 3] >> (i & 0x07)) & 1;
|
||||
}
|
||||
|
||||
// Gets the i-th bit from a byte. Should only be used with i <= 7.
|
||||
static constexpr bool GetBitFromByte(uint8_t byte, uint8_t i) {
|
||||
return byte & kBitmask[i];
|
||||
}
|
||||
|
||||
static inline void ClearBit(uint8_t* bits, int64_t i) {
|
||||
bits[i / 8] &= kFlippedBitmask[i % 8];
|
||||
}
|
||||
|
||||
static inline void SetBit(uint8_t* bits, int64_t i) { bits[i / 8] |= kBitmask[i % 8]; }
|
||||
|
||||
static inline void SetBitTo(uint8_t* bits, int64_t i, bool bit_is_set) {
|
||||
// https://graphics.stanford.edu/~seander/bithacks.html
|
||||
// "Conditionally set or clear bits without branching"
|
||||
// NOTE: this seems to confuse Valgrind as it reads from potentially
|
||||
// uninitialized memory
|
||||
bits[i / 8] ^= static_cast<uint8_t>(-static_cast<uint8_t>(bit_is_set) ^ bits[i / 8]) &
|
||||
kBitmask[i % 8];
|
||||
}
|
||||
|
||||
/// \brief set or clear a range of bits quickly
|
||||
ARROW_EXPORT
|
||||
void SetBitsTo(uint8_t* bits, int64_t start_offset, int64_t length, bool bits_are_set);
|
||||
|
||||
/// \brief Sets all bits in the bitmap to true
|
||||
ARROW_EXPORT
|
||||
void SetBitmap(uint8_t* data, int64_t offset, int64_t length);
|
||||
|
||||
/// \brief Clears all bits in the bitmap (set to false)
|
||||
ARROW_EXPORT
|
||||
void ClearBitmap(uint8_t* data, int64_t offset, int64_t length);
|
||||
|
||||
/// Returns a mask with lower i bits set to 1. If i >= sizeof(Word)*8, all-ones will be
|
||||
/// returned
|
||||
/// ex:
|
||||
/// ref: https://stackoverflow.com/a/59523400
|
||||
template <typename Word>
|
||||
constexpr Word PrecedingWordBitmask(unsigned int const i) {
|
||||
return (static_cast<Word>(i < sizeof(Word) * 8) << (i & (sizeof(Word) * 8 - 1))) - 1;
|
||||
}
|
||||
static_assert(PrecedingWordBitmask<uint8_t>(0) == 0x00, "");
|
||||
static_assert(PrecedingWordBitmask<uint8_t>(4) == 0x0f, "");
|
||||
static_assert(PrecedingWordBitmask<uint8_t>(8) == 0xff, "");
|
||||
static_assert(PrecedingWordBitmask<uint16_t>(8) == 0x00ff, "");
|
||||
|
||||
/// \brief Create a word with low `n` bits from `low` and high `sizeof(Word)-n` bits
|
||||
/// from `high`.
|
||||
/// Word ret
|
||||
/// for (i = 0; i < sizeof(Word)*8; i++){
|
||||
/// ret[i]= i < n ? low[i]: high[i];
|
||||
/// }
|
||||
template <typename Word>
|
||||
constexpr Word SpliceWord(int n, Word low, Word high) {
|
||||
return (high & ~PrecedingWordBitmask<Word>(n)) | (low & PrecedingWordBitmask<Word>(n));
|
||||
}
|
||||
|
||||
/// \brief Pack integers into a bitmap in batches of 8
|
||||
template <int batch_size>
|
||||
void PackBits(const uint32_t* values, uint8_t* out) {
|
||||
for (int i = 0; i < batch_size / 8; ++i) {
|
||||
*out++ = (values[0] | values[1] << 1 | values[2] << 2 | values[3] << 3 |
|
||||
values[4] << 4 | values[5] << 5 | values[6] << 6 | values[7] << 7);
|
||||
values += 8;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace bit_util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,469 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/buffer.h"
|
||||
#include "arrow/util/bit_util.h"
|
||||
#include "arrow/util/bitmap_ops.h"
|
||||
#include "arrow/util/bitmap_reader.h"
|
||||
#include "arrow/util/bitmap_writer.h"
|
||||
#include "arrow/util/bytes_view.h"
|
||||
#include "arrow/util/compare.h"
|
||||
#include "arrow/util/endian.h"
|
||||
#include "arrow/util/functional.h"
|
||||
#include "arrow/util/string_builder.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
class BooleanArray;
|
||||
|
||||
namespace internal {
|
||||
|
||||
class ARROW_EXPORT Bitmap : public util::ToStringOstreamable<Bitmap>,
|
||||
public util::EqualityComparable<Bitmap> {
|
||||
public:
|
||||
template <typename Word>
|
||||
using View = std::basic_string_view<Word>;
|
||||
|
||||
Bitmap() = default;
|
||||
|
||||
Bitmap(const std::shared_ptr<Buffer>& buffer, int64_t offset, int64_t length)
|
||||
: data_(buffer->data()), offset_(offset), length_(length) {
|
||||
if (buffer->is_mutable()) {
|
||||
mutable_data_ = buffer->mutable_data();
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap(const void* data, int64_t offset, int64_t length)
|
||||
: data_(reinterpret_cast<const uint8_t*>(data)), offset_(offset), length_(length) {}
|
||||
|
||||
Bitmap(void* data, int64_t offset, int64_t length)
|
||||
: data_(reinterpret_cast<const uint8_t*>(data)),
|
||||
mutable_data_(reinterpret_cast<uint8_t*>(data)),
|
||||
offset_(offset),
|
||||
length_(length) {}
|
||||
|
||||
Bitmap Slice(int64_t offset) const {
|
||||
if (mutable_data_ != NULLPTR) {
|
||||
return Bitmap(mutable_data_, offset_ + offset, length_ - offset);
|
||||
} else {
|
||||
return Bitmap(data_, offset_ + offset, length_ - offset);
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap Slice(int64_t offset, int64_t length) const {
|
||||
if (mutable_data_ != NULLPTR) {
|
||||
return Bitmap(mutable_data_, offset_ + offset, length);
|
||||
} else {
|
||||
return Bitmap(data_, offset_ + offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
bool Equals(const Bitmap& other) const;
|
||||
|
||||
std::string Diff(const Bitmap& other) const;
|
||||
|
||||
bool GetBit(int64_t i) const { return bit_util::GetBit(data_, i + offset_); }
|
||||
|
||||
bool operator[](int64_t i) const { return GetBit(i); }
|
||||
|
||||
void SetBitTo(int64_t i, bool v) const {
|
||||
bit_util::SetBitTo(mutable_data_, i + offset_, v);
|
||||
}
|
||||
|
||||
void SetBitsTo(bool v) { bit_util::SetBitsTo(mutable_data_, offset_, length_, v); }
|
||||
|
||||
void CopyFrom(const Bitmap& other);
|
||||
void CopyFromInverted(const Bitmap& other);
|
||||
|
||||
/// \brief Visit bits from each bitmap as bitset<N>
|
||||
///
|
||||
/// All bitmaps must have identical length.
|
||||
template <size_t N, typename Visitor>
|
||||
static void VisitBits(const Bitmap (&bitmaps)[N], Visitor&& visitor) {
|
||||
int64_t bit_length = BitLength(bitmaps, N);
|
||||
std::bitset<N> bits;
|
||||
for (int64_t bit_i = 0; bit_i < bit_length; ++bit_i) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
bits[i] = bitmaps[i].GetBit(bit_i);
|
||||
}
|
||||
visitor(bits);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Visit bits from each bitmap as bitset<N>
|
||||
///
|
||||
/// All bitmaps must have identical length.
|
||||
template <size_t N, typename Visitor>
|
||||
static void VisitBits(const std::array<Bitmap, N>& bitmaps, Visitor&& visitor) {
|
||||
int64_t bit_length = BitLength(bitmaps);
|
||||
std::bitset<N> bits;
|
||||
for (int64_t bit_i = 0; bit_i < bit_length; ++bit_i) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
bits[i] = bitmaps[i].GetBit(bit_i);
|
||||
}
|
||||
visitor(bits);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Visit words of bits from each bitmap as array<Word, N>
|
||||
///
|
||||
/// All bitmaps must have identical length. The first bit in a visited bitmap
|
||||
/// may be offset within the first visited word, but words will otherwise contain
|
||||
/// densely packed bits loaded from the bitmap. That offset within the first word is
|
||||
/// returned.
|
||||
///
|
||||
/// TODO(bkietz) allow for early termination
|
||||
// NOTE: this function is efficient on 3+ sufficiently large bitmaps.
|
||||
// It also has a large prolog / epilog overhead and should be used
|
||||
// carefully in other cases.
|
||||
// For 2 bitmaps or less, and/or smaller bitmaps, see also VisitTwoBitBlocksVoid
|
||||
// and BitmapUInt64Reader.
|
||||
template <size_t N, typename Visitor,
|
||||
typename Word = typename std::decay<
|
||||
internal::call_traits::argument_type<0, Visitor&&>>::type::value_type>
|
||||
static int64_t VisitWords(const Bitmap (&bitmaps_arg)[N], Visitor&& visitor) {
|
||||
constexpr int64_t kBitWidth = sizeof(Word) * 8;
|
||||
|
||||
// local, mutable variables which will be sliced/decremented to represent consumption:
|
||||
Bitmap bitmaps[N];
|
||||
int64_t offsets[N];
|
||||
int64_t bit_length = BitLength(bitmaps_arg, N);
|
||||
View<Word> words[N];
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
bitmaps[i] = bitmaps_arg[i];
|
||||
offsets[i] = bitmaps[i].template word_offset<Word>();
|
||||
assert(offsets[i] >= 0 && offsets[i] < kBitWidth);
|
||||
words[i] = bitmaps[i].template words<Word>();
|
||||
}
|
||||
|
||||
auto consume = [&](int64_t consumed_bits) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
bitmaps[i] = bitmaps[i].Slice(consumed_bits, bit_length - consumed_bits);
|
||||
offsets[i] = bitmaps[i].template word_offset<Word>();
|
||||
assert(offsets[i] >= 0 && offsets[i] < kBitWidth);
|
||||
words[i] = bitmaps[i].template words<Word>();
|
||||
}
|
||||
bit_length -= consumed_bits;
|
||||
};
|
||||
|
||||
std::array<Word, N> visited_words;
|
||||
visited_words.fill(0);
|
||||
|
||||
if (bit_length <= kBitWidth * 2) {
|
||||
// bitmaps fit into one or two words so don't bother with optimization
|
||||
while (bit_length > 0) {
|
||||
auto leading_bits = std::min(bit_length, kBitWidth);
|
||||
SafeLoadWords(bitmaps, 0, leading_bits, false, &visited_words);
|
||||
visitor(visited_words);
|
||||
consume(leading_bits);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t max_offset = *std::max_element(offsets, offsets + N);
|
||||
int64_t min_offset = *std::min_element(offsets, offsets + N);
|
||||
if (max_offset > 0) {
|
||||
// consume leading bits
|
||||
auto leading_bits = kBitWidth - min_offset;
|
||||
SafeLoadWords(bitmaps, 0, leading_bits, true, &visited_words);
|
||||
visitor(visited_words);
|
||||
consume(leading_bits);
|
||||
}
|
||||
assert(*std::min_element(offsets, offsets + N) == 0);
|
||||
|
||||
int64_t whole_word_count = bit_length / kBitWidth;
|
||||
assert(whole_word_count >= 1);
|
||||
|
||||
if (min_offset == max_offset) {
|
||||
// all offsets were identical, all leading bits have been consumed
|
||||
assert(
|
||||
std::all_of(offsets, offsets + N, [](int64_t offset) { return offset == 0; }));
|
||||
|
||||
for (int64_t word_i = 0; word_i < whole_word_count; ++word_i) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
visited_words[i] = words[i][word_i];
|
||||
}
|
||||
visitor(visited_words);
|
||||
}
|
||||
consume(whole_word_count * kBitWidth);
|
||||
} else {
|
||||
// leading bits from potentially incomplete words have been consumed
|
||||
|
||||
// word_i such that words[i][word_i] and words[i][word_i + 1] are lie entirely
|
||||
// within the bitmap for all i
|
||||
for (int64_t word_i = 0; word_i < whole_word_count - 1; ++word_i) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
if (offsets[i] == 0) {
|
||||
visited_words[i] = words[i][word_i];
|
||||
} else {
|
||||
auto words0 = bit_util::ToLittleEndian(words[i][word_i]);
|
||||
auto words1 = bit_util::ToLittleEndian(words[i][word_i + 1]);
|
||||
visited_words[i] = bit_util::FromLittleEndian(
|
||||
(words0 >> offsets[i]) | (words1 << (kBitWidth - offsets[i])));
|
||||
}
|
||||
}
|
||||
visitor(visited_words);
|
||||
}
|
||||
consume((whole_word_count - 1) * kBitWidth);
|
||||
|
||||
SafeLoadWords(bitmaps, 0, kBitWidth, false, &visited_words);
|
||||
|
||||
visitor(visited_words);
|
||||
consume(kBitWidth);
|
||||
}
|
||||
|
||||
// load remaining bits
|
||||
if (bit_length > 0) {
|
||||
SafeLoadWords(bitmaps, 0, bit_length, false, &visited_words);
|
||||
visitor(visited_words);
|
||||
}
|
||||
|
||||
return min_offset;
|
||||
}
|
||||
|
||||
template <size_t N, size_t M, typename ReaderT, typename WriterT, typename Visitor,
|
||||
typename Word = typename std::decay<
|
||||
internal::call_traits::argument_type<0, Visitor&&>>::type::value_type>
|
||||
static void RunVisitWordsAndWriteLoop(int64_t bit_length,
|
||||
std::array<ReaderT, N>& readers,
|
||||
std::array<WriterT, M>& writers,
|
||||
Visitor&& visitor) {
|
||||
constexpr int64_t kBitWidth = sizeof(Word) * 8;
|
||||
|
||||
std::array<Word, N> visited_words;
|
||||
std::array<Word, M> output_words;
|
||||
|
||||
// every reader will have same number of words, since they are same length'ed
|
||||
// TODO($JIRA) this will be inefficient in some cases. When there are offsets beyond
|
||||
// Word boundary, every Word would have to be created from 2 adjoining Words
|
||||
auto n_words = readers[0].words();
|
||||
bit_length -= n_words * kBitWidth;
|
||||
while (n_words--) {
|
||||
// first collect all words to visited_words array
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
visited_words[i] = readers[i].NextWord();
|
||||
}
|
||||
visitor(visited_words, &output_words);
|
||||
for (size_t i = 0; i < M; i++) {
|
||||
writers[i].PutNextWord(output_words[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// every reader will have same number of trailing bytes, because of the above reason
|
||||
// tailing portion could be more than one word! (ref: BitmapWordReader constructor)
|
||||
// remaining full/ partial words to write
|
||||
|
||||
if (bit_length) {
|
||||
// convert the word visitor lambda to a byte_visitor
|
||||
auto byte_visitor = [&](const std::array<uint8_t, N>& in,
|
||||
std::array<uint8_t, M>* out) {
|
||||
std::array<Word, N> in_words;
|
||||
std::array<Word, M> out_words;
|
||||
std::copy(in.begin(), in.end(), in_words.begin());
|
||||
visitor(in_words, &out_words);
|
||||
for (size_t i = 0; i < M; i++) {
|
||||
out->at(i) = static_cast<uint8_t>(out_words[i]);
|
||||
}
|
||||
};
|
||||
|
||||
std::array<uint8_t, N> visited_bytes;
|
||||
std::array<uint8_t, M> output_bytes;
|
||||
int n_bytes = readers[0].trailing_bytes();
|
||||
while (n_bytes--) {
|
||||
visited_bytes.fill(0);
|
||||
output_bytes.fill(0);
|
||||
int valid_bits;
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
visited_bytes[i] = readers[i].NextTrailingByte(valid_bits);
|
||||
}
|
||||
byte_visitor(visited_bytes, &output_bytes);
|
||||
for (size_t i = 0; i < M; i++) {
|
||||
writers[i].PutNextTrailingByte(output_bytes[i], valid_bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Visit words of bits from each input bitmap as array<Word, N> and collects
|
||||
/// outputs to an array<Word, M>, to be written into the output bitmaps accordingly.
|
||||
///
|
||||
/// All bitmaps must have identical length. The first bit in a visited bitmap
|
||||
/// may be offset within the first visited word, but words will otherwise contain
|
||||
/// densely packed bits loaded from the bitmap. That offset within the first word is
|
||||
/// returned.
|
||||
/// Visitor is expected to have the following signature
|
||||
/// [](const std::array<Word, N>& in_words, std::array<Word, M>* out_words){...}
|
||||
///
|
||||
// NOTE: this function is efficient on 3+ sufficiently large bitmaps.
|
||||
// It also has a large prolog / epilog overhead and should be used
|
||||
// carefully in other cases.
|
||||
// For 2 bitmaps or less, and/or smaller bitmaps, see also VisitTwoBitBlocksVoid
|
||||
// and BitmapUInt64Reader.
|
||||
template <size_t N, size_t M, typename Visitor,
|
||||
typename Word = typename std::decay<
|
||||
internal::call_traits::argument_type<0, Visitor&&>>::type::value_type>
|
||||
static void VisitWordsAndWrite(const std::array<Bitmap, N>& bitmaps_arg,
|
||||
std::array<Bitmap, M>* out_bitmaps_arg,
|
||||
Visitor&& visitor) {
|
||||
int64_t bit_length = BitLength(bitmaps_arg);
|
||||
assert(bit_length == BitLength(*out_bitmaps_arg));
|
||||
|
||||
// if both input and output bitmaps have no byte offset, then use special template
|
||||
if (std::all_of(bitmaps_arg.begin(), bitmaps_arg.end(),
|
||||
[](const Bitmap& b) { return b.offset_ % 8 == 0; }) &&
|
||||
std::all_of(out_bitmaps_arg->begin(), out_bitmaps_arg->end(),
|
||||
[](const Bitmap& b) { return b.offset_ % 8 == 0; })) {
|
||||
std::array<BitmapWordReader<Word, /*may_have_byte_offset=*/false>, N> readers;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
const Bitmap& in_bitmap = bitmaps_arg[i];
|
||||
readers[i] = BitmapWordReader<Word, /*may_have_byte_offset=*/false>(
|
||||
in_bitmap.data_, in_bitmap.offset_, in_bitmap.length_);
|
||||
}
|
||||
|
||||
std::array<BitmapWordWriter<Word, /*may_have_byte_offset=*/false>, M> writers;
|
||||
for (size_t i = 0; i < M; ++i) {
|
||||
const Bitmap& out_bitmap = out_bitmaps_arg->at(i);
|
||||
writers[i] = BitmapWordWriter<Word, /*may_have_byte_offset=*/false>(
|
||||
out_bitmap.mutable_data_, out_bitmap.offset_, out_bitmap.length_);
|
||||
}
|
||||
|
||||
RunVisitWordsAndWriteLoop(bit_length, readers, writers, visitor);
|
||||
} else {
|
||||
std::array<BitmapWordReader<Word>, N> readers;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
const Bitmap& in_bitmap = bitmaps_arg[i];
|
||||
readers[i] =
|
||||
BitmapWordReader<Word>(in_bitmap.data_, in_bitmap.offset_, in_bitmap.length_);
|
||||
}
|
||||
|
||||
std::array<BitmapWordWriter<Word>, M> writers;
|
||||
for (size_t i = 0; i < M; ++i) {
|
||||
const Bitmap& out_bitmap = out_bitmaps_arg->at(i);
|
||||
writers[i] = BitmapWordWriter<Word>(out_bitmap.mutable_data_, out_bitmap.offset_,
|
||||
out_bitmap.length_);
|
||||
}
|
||||
|
||||
RunVisitWordsAndWriteLoop(bit_length, readers, writers, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t* data() const { return data_; }
|
||||
uint8_t* mutable_data() { return mutable_data_; }
|
||||
|
||||
/// offset of first bit relative to buffer().data()
|
||||
int64_t offset() const { return offset_; }
|
||||
|
||||
/// number of bits in this Bitmap
|
||||
int64_t length() const { return length_; }
|
||||
|
||||
/// string_view of all bytes which contain any bit in this Bitmap
|
||||
util::bytes_view bytes() const {
|
||||
auto byte_offset = offset_ / 8;
|
||||
auto byte_count = bit_util::CeilDiv(offset_ + length_, 8) - byte_offset;
|
||||
return util::bytes_view(data_ + byte_offset, byte_count);
|
||||
}
|
||||
|
||||
private:
|
||||
/// string_view of all Words which contain any bit in this Bitmap
|
||||
///
|
||||
/// For example, given Word=uint16_t and a bitmap spanning bits [20, 36)
|
||||
/// words() would span bits [16, 48).
|
||||
///
|
||||
/// 0 16 32 48 64
|
||||
/// |-------|-------|------|------| (buffer)
|
||||
/// [ ] (bitmap)
|
||||
/// |-------|------| (returned words)
|
||||
///
|
||||
/// \warning The words may contain bytes which lie outside the buffer or are
|
||||
/// uninitialized.
|
||||
template <typename Word>
|
||||
View<Word> words() const {
|
||||
auto bytes_addr = reinterpret_cast<intptr_t>(bytes().data());
|
||||
auto words_addr = bytes_addr - bytes_addr % sizeof(Word);
|
||||
auto word_byte_count =
|
||||
bit_util::RoundUpToPowerOf2(static_cast<int64_t>(bytes_addr + bytes().size()),
|
||||
static_cast<int64_t>(sizeof(Word))) -
|
||||
words_addr;
|
||||
return View<Word>(reinterpret_cast<const Word*>(words_addr),
|
||||
word_byte_count / sizeof(Word));
|
||||
}
|
||||
|
||||
/// offset of first bit relative to words<Word>().data()
|
||||
template <typename Word>
|
||||
int64_t word_offset() const {
|
||||
return offset_ + 8 * (reinterpret_cast<intptr_t>(data_) -
|
||||
reinterpret_cast<intptr_t>(words<Word>().data()));
|
||||
}
|
||||
|
||||
/// load words from bitmaps bitwise
|
||||
template <size_t N, typename Word>
|
||||
static void SafeLoadWords(const Bitmap (&bitmaps)[N], int64_t offset,
|
||||
int64_t out_length, bool set_trailing_bits,
|
||||
std::array<Word, N>* out) {
|
||||
out->fill(0);
|
||||
|
||||
int64_t out_offset = set_trailing_bits ? sizeof(Word) * 8 - out_length : 0;
|
||||
|
||||
Bitmap slices[N], out_bitmaps[N];
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
slices[i] = bitmaps[i].Slice(offset, out_length);
|
||||
out_bitmaps[i] = Bitmap(&out->at(i), out_offset, out_length);
|
||||
}
|
||||
|
||||
int64_t bit_i = 0;
|
||||
Bitmap::VisitBits(slices, [&](std::bitset<N> bits) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
out_bitmaps[i].SetBitTo(bit_i, bits[i]);
|
||||
}
|
||||
++bit_i;
|
||||
});
|
||||
}
|
||||
|
||||
/// assert bitmaps have identical length and return that length
|
||||
static int64_t BitLength(const Bitmap* bitmaps, size_t N);
|
||||
|
||||
template <size_t N>
|
||||
static int64_t BitLength(const std::array<Bitmap, N>& bitmaps) {
|
||||
for (size_t i = 1; i < N; ++i) {
|
||||
assert(bitmaps[i].length() == bitmaps[0].length());
|
||||
}
|
||||
return bitmaps[0].length();
|
||||
}
|
||||
|
||||
const uint8_t* data_ = NULLPTR;
|
||||
uint8_t* mutable_data_ = NULLPTR;
|
||||
int64_t offset_ = 0, length_ = 0;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/type_fwd.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
/// \brief Generate Bitmap with all position to `value` except for one found
|
||||
/// at `straggler_pos`.
|
||||
ARROW_EXPORT
|
||||
Result<std::shared_ptr<Buffer>> BitmapAllButOne(MemoryPool* pool, int64_t length,
|
||||
int64_t straggler_pos, bool value = true);
|
||||
|
||||
/// \brief Convert vector of bytes to bitmap buffer
|
||||
ARROW_EXPORT
|
||||
Result<std::shared_ptr<Buffer>> BytesToBits(const std::vector<uint8_t>&,
|
||||
MemoryPool* pool = default_memory_pool());
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,111 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "arrow/buffer.h"
|
||||
#include "arrow/memory_pool.h"
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/util/bit_util.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
// A std::generate() like function to write sequential bits into a bitmap area.
|
||||
// Bits preceding the bitmap area are preserved, bits following the bitmap
|
||||
// area may be clobbered.
|
||||
|
||||
template <class Generator>
|
||||
void GenerateBits(uint8_t* bitmap, int64_t start_offset, int64_t length, Generator&& g) {
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
uint8_t* cur = bitmap + start_offset / 8;
|
||||
uint8_t bit_mask = bit_util::kBitmask[start_offset % 8];
|
||||
uint8_t current_byte = *cur & bit_util::kPrecedingBitmask[start_offset % 8];
|
||||
|
||||
for (int64_t index = 0; index < length; ++index) {
|
||||
const bool bit = g();
|
||||
current_byte = bit ? (current_byte | bit_mask) : current_byte;
|
||||
bit_mask = static_cast<uint8_t>(bit_mask << 1);
|
||||
if (bit_mask == 0) {
|
||||
bit_mask = 1;
|
||||
*cur++ = current_byte;
|
||||
current_byte = 0;
|
||||
}
|
||||
}
|
||||
if (bit_mask != 1) {
|
||||
*cur++ = current_byte;
|
||||
}
|
||||
}
|
||||
|
||||
// Like GenerateBits(), but unrolls its main loop for higher performance.
|
||||
|
||||
template <class Generator>
|
||||
void GenerateBitsUnrolled(uint8_t* bitmap, int64_t start_offset, int64_t length,
|
||||
Generator&& g) {
|
||||
static_assert(std::is_same<decltype(std::declval<Generator>()()), bool>::value,
|
||||
"Functor passed to GenerateBitsUnrolled must return bool");
|
||||
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
uint8_t current_byte;
|
||||
uint8_t* cur = bitmap + start_offset / 8;
|
||||
const uint64_t start_bit_offset = start_offset % 8;
|
||||
uint8_t bit_mask = bit_util::kBitmask[start_bit_offset];
|
||||
int64_t remaining = length;
|
||||
|
||||
if (bit_mask != 0x01) {
|
||||
current_byte = *cur & bit_util::kPrecedingBitmask[start_bit_offset];
|
||||
while (bit_mask != 0 && remaining > 0) {
|
||||
current_byte |= g() * bit_mask;
|
||||
bit_mask = static_cast<uint8_t>(bit_mask << 1);
|
||||
--remaining;
|
||||
}
|
||||
*cur++ = current_byte;
|
||||
}
|
||||
|
||||
int64_t remaining_bytes = remaining / 8;
|
||||
uint8_t out_results[8];
|
||||
while (remaining_bytes-- > 0) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
out_results[i] = g();
|
||||
}
|
||||
*cur++ = (out_results[0] | out_results[1] << 1 | out_results[2] << 2 |
|
||||
out_results[3] << 3 | out_results[4] << 4 | out_results[5] << 5 |
|
||||
out_results[6] << 6 | out_results[7] << 7);
|
||||
}
|
||||
|
||||
int64_t remaining_bits = remaining % 8;
|
||||
if (remaining_bits) {
|
||||
current_byte = 0;
|
||||
bit_mask = 0x01;
|
||||
while (remaining_bits-- > 0) {
|
||||
current_byte |= g() * bit_mask;
|
||||
bit_mask = static_cast<uint8_t>(bit_mask << 1);
|
||||
}
|
||||
*cur++ = current_byte;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,244 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
class Buffer;
|
||||
class MemoryPool;
|
||||
|
||||
namespace internal {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Bitmap utilities
|
||||
|
||||
/// Copy a bit range of an existing bitmap
|
||||
///
|
||||
/// \param[in] pool memory pool to allocate memory from
|
||||
/// \param[in] bitmap source data
|
||||
/// \param[in] offset bit offset into the source data
|
||||
/// \param[in] length number of bits to copy
|
||||
///
|
||||
/// \return Status message
|
||||
ARROW_EXPORT
|
||||
Result<std::shared_ptr<Buffer>> CopyBitmap(MemoryPool* pool, const uint8_t* bitmap,
|
||||
int64_t offset, int64_t length);
|
||||
|
||||
/// Copy a bit range of an existing bitmap into an existing bitmap
|
||||
///
|
||||
/// \param[in] bitmap source data
|
||||
/// \param[in] offset bit offset into the source data
|
||||
/// \param[in] length number of bits to copy
|
||||
/// \param[in] dest_offset bit offset into the destination
|
||||
/// \param[out] dest the destination buffer, must have at least space for
|
||||
/// (offset + length) bits
|
||||
ARROW_EXPORT
|
||||
void CopyBitmap(const uint8_t* bitmap, int64_t offset, int64_t length, uint8_t* dest,
|
||||
int64_t dest_offset);
|
||||
|
||||
/// Invert a bit range of an existing bitmap into an existing bitmap
|
||||
///
|
||||
/// \param[in] bitmap source data
|
||||
/// \param[in] offset bit offset into the source data
|
||||
/// \param[in] length number of bits to copy
|
||||
/// \param[in] dest_offset bit offset into the destination
|
||||
/// \param[out] dest the destination buffer, must have at least space for
|
||||
/// (offset + length) bits
|
||||
ARROW_EXPORT
|
||||
void InvertBitmap(const uint8_t* bitmap, int64_t offset, int64_t length, uint8_t* dest,
|
||||
int64_t dest_offset);
|
||||
|
||||
/// Invert a bit range of an existing bitmap
|
||||
///
|
||||
/// \param[in] pool memory pool to allocate memory from
|
||||
/// \param[in] bitmap source data
|
||||
/// \param[in] offset bit offset into the source data
|
||||
/// \param[in] length number of bits to copy
|
||||
///
|
||||
/// \return Status message
|
||||
ARROW_EXPORT
|
||||
Result<std::shared_ptr<Buffer>> InvertBitmap(MemoryPool* pool, const uint8_t* bitmap,
|
||||
int64_t offset, int64_t length);
|
||||
|
||||
/// Reverse a bit range of an existing bitmap into an existing bitmap
|
||||
///
|
||||
/// \param[in] bitmap source data
|
||||
/// \param[in] offset bit offset into the source data
|
||||
/// \param[in] length number of bits to reverse
|
||||
/// \param[in] dest_offset bit offset into the destination
|
||||
/// \param[out] dest the destination buffer, must have at least space for
|
||||
/// (offset + length) bits
|
||||
ARROW_EXPORT
|
||||
void ReverseBitmap(const uint8_t* bitmap, int64_t offset, int64_t length, uint8_t* dest,
|
||||
int64_t dest_offset);
|
||||
|
||||
/// Reverse a bit range of an existing bitmap
|
||||
///
|
||||
/// \param[in] pool memory pool to allocate memory from
|
||||
/// \param[in] bitmap source data
|
||||
/// \param[in] offset bit offset into the source data
|
||||
/// \param[in] length number of bits to reverse
|
||||
///
|
||||
/// \return Status message
|
||||
ARROW_EXPORT
|
||||
Result<std::shared_ptr<Buffer>> ReverseBitmap(MemoryPool* pool, const uint8_t* bitmap,
|
||||
int64_t offset, int64_t length);
|
||||
|
||||
/// Compute the number of 1's in the given data array
|
||||
///
|
||||
/// \param[in] data a packed LSB-ordered bitmap as a byte array
|
||||
/// \param[in] bit_offset a bitwise offset into the bitmap
|
||||
/// \param[in] length the number of bits to inspect in the bitmap relative to
|
||||
/// the offset
|
||||
///
|
||||
/// \return The number of set (1) bits in the range
|
||||
ARROW_EXPORT
|
||||
int64_t CountSetBits(const uint8_t* data, int64_t bit_offset, int64_t length);
|
||||
|
||||
/// Compute the number of 1's in the result of an "and" (&) of two bitmaps
|
||||
///
|
||||
/// \param[in] left_bitmap a packed LSB-ordered bitmap as a byte array
|
||||
/// \param[in] left_offset a bitwise offset into the left bitmap
|
||||
/// \param[in] right_bitmap a packed LSB-ordered bitmap as a byte array
|
||||
/// \param[in] right_offset a bitwise offset into the right bitmap
|
||||
/// \param[in] length the length of the bitmaps (must be the same)
|
||||
///
|
||||
/// \return The number of set (1) bits in the "and" of the two bitmaps
|
||||
ARROW_EXPORT
|
||||
int64_t CountAndSetBits(const uint8_t* left_bitmap, int64_t left_offset,
|
||||
const uint8_t* right_bitmap, int64_t right_offset,
|
||||
int64_t length);
|
||||
|
||||
ARROW_EXPORT
|
||||
bool BitmapEquals(const uint8_t* left, int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length);
|
||||
|
||||
// Same as BitmapEquals, but considers a NULL bitmap pointer the same as an
|
||||
// all-ones bitmap.
|
||||
ARROW_EXPORT
|
||||
bool OptionalBitmapEquals(const uint8_t* left, int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length);
|
||||
|
||||
ARROW_EXPORT
|
||||
bool OptionalBitmapEquals(const std::shared_ptr<Buffer>& left, int64_t left_offset,
|
||||
const std::shared_ptr<Buffer>& right, int64_t right_offset,
|
||||
int64_t length);
|
||||
|
||||
/// \brief Do a "bitmap and" on right and left buffers starting at
|
||||
/// their respective bit-offsets for the given bit-length and put
|
||||
/// the results in out_buffer starting at the given bit-offset.
|
||||
///
|
||||
/// out_buffer will be allocated and initialized to zeros using pool before
|
||||
/// the operation.
|
||||
ARROW_EXPORT
|
||||
Result<std::shared_ptr<Buffer>> BitmapAnd(MemoryPool* pool, const uint8_t* left,
|
||||
int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length,
|
||||
int64_t out_offset);
|
||||
|
||||
/// \brief Do a "bitmap and" on right and left buffers starting at
|
||||
/// their respective bit-offsets for the given bit-length and put
|
||||
/// the results in out starting at the given bit-offset.
|
||||
ARROW_EXPORT
|
||||
void BitmapAnd(const uint8_t* left, int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length, int64_t out_offset, uint8_t* out);
|
||||
|
||||
/// \brief Do a "bitmap or" for the given bit length on right and left buffers
|
||||
/// starting at their respective bit-offsets and put the results in out_buffer
|
||||
/// starting at the given bit-offset.
|
||||
///
|
||||
/// out_buffer will be allocated and initialized to zeros using pool before
|
||||
/// the operation.
|
||||
ARROW_EXPORT
|
||||
Result<std::shared_ptr<Buffer>> BitmapOr(MemoryPool* pool, const uint8_t* left,
|
||||
int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length,
|
||||
int64_t out_offset);
|
||||
|
||||
/// \brief Do a "bitmap or" for the given bit length on right and left buffers
|
||||
/// starting at their respective bit-offsets and put the results in out
|
||||
/// starting at the given bit-offset.
|
||||
ARROW_EXPORT
|
||||
void BitmapOr(const uint8_t* left, int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length, int64_t out_offset, uint8_t* out);
|
||||
|
||||
/// \brief Do a "bitmap xor" for the given bit-length on right and left
|
||||
/// buffers starting at their respective bit-offsets and put the results in
|
||||
/// out_buffer starting at the given bit offset.
|
||||
///
|
||||
/// out_buffer will be allocated and initialized to zeros using pool before
|
||||
/// the operation.
|
||||
ARROW_EXPORT
|
||||
Result<std::shared_ptr<Buffer>> BitmapXor(MemoryPool* pool, const uint8_t* left,
|
||||
int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length,
|
||||
int64_t out_offset);
|
||||
|
||||
/// \brief Do a "bitmap xor" for the given bit-length on right and left
|
||||
/// buffers starting at their respective bit-offsets and put the results in
|
||||
/// out starting at the given bit offset.
|
||||
ARROW_EXPORT
|
||||
void BitmapXor(const uint8_t* left, int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length, int64_t out_offset, uint8_t* out);
|
||||
|
||||
/// \brief Do a "bitmap and not" on right and left buffers starting at
|
||||
/// their respective bit-offsets for the given bit-length and put
|
||||
/// the results in out_buffer starting at the given bit-offset.
|
||||
///
|
||||
/// out_buffer will be allocated and initialized to zeros using pool before
|
||||
/// the operation.
|
||||
ARROW_EXPORT
|
||||
Result<std::shared_ptr<Buffer>> BitmapAndNot(MemoryPool* pool, const uint8_t* left,
|
||||
int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length,
|
||||
int64_t out_offset);
|
||||
|
||||
/// \brief Do a "bitmap and not" on right and left buffers starting at
|
||||
/// their respective bit-offsets for the given bit-length and put
|
||||
/// the results in out starting at the given bit-offset.
|
||||
ARROW_EXPORT
|
||||
void BitmapAndNot(const uint8_t* left, int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length, int64_t out_offset, uint8_t* out);
|
||||
|
||||
/// \brief Do a "bitmap or not" on right and left buffers starting at
|
||||
/// their respective bit-offsets for the given bit-length and put
|
||||
/// the results in out_buffer starting at the given bit-offset.
|
||||
///
|
||||
/// out_buffer will be allocated and initialized to zeros using pool before
|
||||
/// the operation.
|
||||
ARROW_EXPORT
|
||||
Result<std::shared_ptr<Buffer>> BitmapOrNot(MemoryPool* pool, const uint8_t* left,
|
||||
int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length,
|
||||
int64_t out_offset);
|
||||
|
||||
/// \brief Do a "bitmap or not" on right and left buffers starting at
|
||||
/// their respective bit-offsets for the given bit-length and put
|
||||
/// the results in out starting at the given bit-offset.
|
||||
ARROW_EXPORT
|
||||
void BitmapOrNot(const uint8_t* left, int64_t left_offset, const uint8_t* right,
|
||||
int64_t right_offset, int64_t length, int64_t out_offset, uint8_t* out);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,273 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "arrow/buffer.h"
|
||||
#include "arrow/util/bit_util.h"
|
||||
#include "arrow/util/endian.h"
|
||||
#include "arrow/util/macros.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
class BitmapReader {
|
||||
public:
|
||||
BitmapReader(const uint8_t* bitmap, int64_t start_offset, int64_t length)
|
||||
: bitmap_(bitmap), position_(0), length_(length) {
|
||||
current_byte_ = 0;
|
||||
byte_offset_ = start_offset / 8;
|
||||
bit_offset_ = start_offset % 8;
|
||||
if (length > 0) {
|
||||
current_byte_ = bitmap[byte_offset_];
|
||||
}
|
||||
}
|
||||
|
||||
bool IsSet() const { return (current_byte_ & (1 << bit_offset_)) != 0; }
|
||||
|
||||
bool IsNotSet() const { return (current_byte_ & (1 << bit_offset_)) == 0; }
|
||||
|
||||
void Next() {
|
||||
++bit_offset_;
|
||||
++position_;
|
||||
if (ARROW_PREDICT_FALSE(bit_offset_ == 8)) {
|
||||
bit_offset_ = 0;
|
||||
++byte_offset_;
|
||||
if (ARROW_PREDICT_TRUE(position_ < length_)) {
|
||||
current_byte_ = bitmap_[byte_offset_];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t position() const { return position_; }
|
||||
|
||||
int64_t length() const { return length_; }
|
||||
|
||||
private:
|
||||
const uint8_t* bitmap_;
|
||||
int64_t position_;
|
||||
int64_t length_;
|
||||
|
||||
uint8_t current_byte_;
|
||||
int64_t byte_offset_;
|
||||
int64_t bit_offset_;
|
||||
};
|
||||
|
||||
// XXX Cannot name it BitmapWordReader because the name is already used
|
||||
// in bitmap_ops.cc
|
||||
|
||||
class BitmapUInt64Reader {
|
||||
public:
|
||||
BitmapUInt64Reader(const uint8_t* bitmap, int64_t start_offset, int64_t length)
|
||||
: bitmap_(util::MakeNonNull(bitmap) + start_offset / 8),
|
||||
num_carry_bits_(8 - start_offset % 8),
|
||||
length_(length),
|
||||
remaining_length_(length_),
|
||||
carry_bits_(0) {
|
||||
if (length_ > 0) {
|
||||
// Load carry bits from the first byte's MSBs
|
||||
if (length_ >= num_carry_bits_) {
|
||||
carry_bits_ =
|
||||
LoadPartialWord(static_cast<int8_t>(8 - num_carry_bits_), num_carry_bits_);
|
||||
} else {
|
||||
carry_bits_ = LoadPartialWord(static_cast<int8_t>(8 - num_carry_bits_), length_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t NextWord() {
|
||||
if (ARROW_PREDICT_TRUE(remaining_length_ >= 64 + num_carry_bits_)) {
|
||||
// We can load a full word
|
||||
uint64_t next_word = LoadFullWord();
|
||||
// Carry bits come first, then the (64 - num_carry_bits_) LSBs from next_word
|
||||
uint64_t word = carry_bits_ | (next_word << num_carry_bits_);
|
||||
carry_bits_ = next_word >> (64 - num_carry_bits_);
|
||||
remaining_length_ -= 64;
|
||||
return word;
|
||||
} else if (remaining_length_ > num_carry_bits_) {
|
||||
// We can load a partial word
|
||||
uint64_t next_word =
|
||||
LoadPartialWord(/*bit_offset=*/0, remaining_length_ - num_carry_bits_);
|
||||
uint64_t word = carry_bits_ | (next_word << num_carry_bits_);
|
||||
carry_bits_ = next_word >> (64 - num_carry_bits_);
|
||||
remaining_length_ = std::max<int64_t>(remaining_length_ - 64, 0);
|
||||
return word;
|
||||
} else {
|
||||
remaining_length_ = 0;
|
||||
return carry_bits_;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t position() const { return length_ - remaining_length_; }
|
||||
|
||||
int64_t length() const { return length_; }
|
||||
|
||||
private:
|
||||
uint64_t LoadFullWord() {
|
||||
uint64_t word;
|
||||
memcpy(&word, bitmap_, 8);
|
||||
bitmap_ += 8;
|
||||
return bit_util::ToLittleEndian(word);
|
||||
}
|
||||
|
||||
uint64_t LoadPartialWord(int8_t bit_offset, int64_t num_bits) {
|
||||
uint64_t word = 0;
|
||||
const int64_t num_bytes = bit_util::BytesForBits(num_bits);
|
||||
memcpy(&word, bitmap_, num_bytes);
|
||||
bitmap_ += num_bytes;
|
||||
return (bit_util::ToLittleEndian(word) >> bit_offset) &
|
||||
bit_util::LeastSignificantBitMask(num_bits);
|
||||
}
|
||||
|
||||
const uint8_t* bitmap_;
|
||||
const int64_t num_carry_bits_; // in [1, 8]
|
||||
const int64_t length_;
|
||||
int64_t remaining_length_;
|
||||
uint64_t carry_bits_;
|
||||
};
|
||||
|
||||
// BitmapWordReader here is faster than BitmapUInt64Reader (in bitmap_reader.h)
|
||||
// on sufficiently large inputs. However, it has a larger prolog / epilog overhead
|
||||
// and should probably not be used for small bitmaps.
|
||||
|
||||
template <typename Word, bool may_have_byte_offset = true>
|
||||
class BitmapWordReader {
|
||||
public:
|
||||
BitmapWordReader() = default;
|
||||
BitmapWordReader(const uint8_t* bitmap, int64_t offset, int64_t length)
|
||||
: offset_(static_cast<int64_t>(may_have_byte_offset) * (offset % 8)),
|
||||
bitmap_(bitmap + offset / 8),
|
||||
bitmap_end_(bitmap_ + bit_util::BytesForBits(offset_ + length)) {
|
||||
// decrement word count by one as we may touch two adjacent words in one iteration
|
||||
nwords_ = length / (sizeof(Word) * 8) - 1;
|
||||
if (nwords_ < 0) {
|
||||
nwords_ = 0;
|
||||
}
|
||||
trailing_bits_ = static_cast<int>(length - nwords_ * sizeof(Word) * 8);
|
||||
trailing_bytes_ = static_cast<int>(bit_util::BytesForBits(trailing_bits_));
|
||||
|
||||
if (nwords_ > 0) {
|
||||
current_data.word_ = load<Word>(bitmap_);
|
||||
} else if (length > 0) {
|
||||
current_data.epi.byte_ = load<uint8_t>(bitmap_);
|
||||
}
|
||||
}
|
||||
|
||||
Word NextWord() {
|
||||
bitmap_ += sizeof(Word);
|
||||
const Word next_word = load<Word>(bitmap_);
|
||||
Word word = current_data.word_;
|
||||
if (may_have_byte_offset && offset_) {
|
||||
// combine two adjacent words into one word
|
||||
// |<------ next ----->|<---- current ---->|
|
||||
// +-------------+-----+-------------+-----+
|
||||
// | --- | A | B | --- |
|
||||
// +-------------+-----+-------------+-----+
|
||||
// | | offset
|
||||
// v v
|
||||
// +-----+-------------+
|
||||
// | A | B |
|
||||
// +-----+-------------+
|
||||
// |<------ word ----->|
|
||||
word >>= offset_;
|
||||
word |= next_word << (sizeof(Word) * 8 - offset_);
|
||||
}
|
||||
current_data.word_ = next_word;
|
||||
return word;
|
||||
}
|
||||
|
||||
uint8_t NextTrailingByte(int& valid_bits) {
|
||||
uint8_t byte;
|
||||
assert(trailing_bits_ > 0);
|
||||
|
||||
if (trailing_bits_ <= 8) {
|
||||
// last byte
|
||||
valid_bits = trailing_bits_;
|
||||
trailing_bits_ = 0;
|
||||
byte = 0;
|
||||
internal::BitmapReader reader(bitmap_, offset_, valid_bits);
|
||||
for (int i = 0; i < valid_bits; ++i) {
|
||||
byte >>= 1;
|
||||
if (reader.IsSet()) {
|
||||
byte |= 0x80;
|
||||
}
|
||||
reader.Next();
|
||||
}
|
||||
byte >>= (8 - valid_bits);
|
||||
} else {
|
||||
++bitmap_;
|
||||
const uint8_t next_byte = load<uint8_t>(bitmap_);
|
||||
byte = current_data.epi.byte_;
|
||||
if (may_have_byte_offset && offset_) {
|
||||
byte >>= offset_;
|
||||
byte |= next_byte << (8 - offset_);
|
||||
}
|
||||
current_data.epi.byte_ = next_byte;
|
||||
trailing_bits_ -= 8;
|
||||
trailing_bytes_--;
|
||||
valid_bits = 8;
|
||||
}
|
||||
return byte;
|
||||
}
|
||||
|
||||
int64_t words() const { return nwords_; }
|
||||
int trailing_bytes() const { return trailing_bytes_; }
|
||||
|
||||
private:
|
||||
int64_t offset_;
|
||||
const uint8_t* bitmap_;
|
||||
|
||||
const uint8_t* bitmap_end_;
|
||||
int64_t nwords_;
|
||||
int trailing_bits_;
|
||||
int trailing_bytes_;
|
||||
union {
|
||||
Word word_;
|
||||
struct {
|
||||
#if ARROW_LITTLE_ENDIAN == 0
|
||||
uint8_t padding_bytes_[sizeof(Word) - 1];
|
||||
#endif
|
||||
uint8_t byte_;
|
||||
} epi;
|
||||
} current_data;
|
||||
|
||||
template <typename DType>
|
||||
DType load(const uint8_t* bitmap) {
|
||||
assert(bitmap + sizeof(DType) <= bitmap_end_);
|
||||
return bit_util::ToLittleEndian(util::SafeLoadAs<DType>(bitmap));
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Index into a possibly non-existent bitmap
|
||||
struct OptionalBitIndexer {
|
||||
const uint8_t* bitmap;
|
||||
const int64_t offset;
|
||||
|
||||
explicit OptionalBitIndexer(const uint8_t* buffer = NULLPTR, int64_t offset = 0)
|
||||
: bitmap(buffer), offset(offset) {}
|
||||
|
||||
bool operator[](int64_t i) const {
|
||||
return bitmap == NULLPTR || bit_util::GetBit(bitmap, offset + i);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "arrow/util/bit_util.h"
|
||||
#include "arrow/util/bitmap_reader.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
// A function that visits each bit in a bitmap and calls a visitor function with a
|
||||
// boolean representation of that bit. This is intended to be analogous to
|
||||
// GenerateBits.
|
||||
template <class Visitor>
|
||||
void VisitBits(const uint8_t* bitmap, int64_t start_offset, int64_t length,
|
||||
Visitor&& visit) {
|
||||
BitmapReader reader(bitmap, start_offset, length);
|
||||
for (int64_t index = 0; index < length; ++index) {
|
||||
visit(reader.IsSet());
|
||||
reader.Next();
|
||||
}
|
||||
}
|
||||
|
||||
// Like VisitBits(), but unrolls its main loop for better performance.
|
||||
template <class Visitor>
|
||||
void VisitBitsUnrolled(const uint8_t* bitmap, int64_t start_offset, int64_t length,
|
||||
Visitor&& visit) {
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start by visiting any bits preceding the first full byte.
|
||||
int64_t num_bits_before_full_bytes =
|
||||
bit_util::RoundUpToMultipleOf8(start_offset) - start_offset;
|
||||
// Truncate num_bits_before_full_bytes if it is greater than length.
|
||||
if (num_bits_before_full_bytes > length) {
|
||||
num_bits_before_full_bytes = length;
|
||||
}
|
||||
// Use the non loop-unrolled VisitBits since we don't want to add branches
|
||||
VisitBits<Visitor>(bitmap, start_offset, num_bits_before_full_bytes, visit);
|
||||
|
||||
// Shift the start pointer to the first full byte and compute the
|
||||
// number of full bytes to be read.
|
||||
const uint8_t* first_full_byte = bitmap + bit_util::CeilDiv(start_offset, 8);
|
||||
const int64_t num_full_bytes = (length - num_bits_before_full_bytes) / 8;
|
||||
|
||||
// Iterate over each full byte of the input bitmap and call the visitor in
|
||||
// a loop-unrolled manner.
|
||||
for (int64_t byte_index = 0; byte_index < num_full_bytes; ++byte_index) {
|
||||
// Get the current bit-packed byte value from the bitmap.
|
||||
const uint8_t byte = *(first_full_byte + byte_index);
|
||||
|
||||
// Execute the visitor function on each bit of the current byte.
|
||||
visit(bit_util::GetBitFromByte(byte, 0));
|
||||
visit(bit_util::GetBitFromByte(byte, 1));
|
||||
visit(bit_util::GetBitFromByte(byte, 2));
|
||||
visit(bit_util::GetBitFromByte(byte, 3));
|
||||
visit(bit_util::GetBitFromByte(byte, 4));
|
||||
visit(bit_util::GetBitFromByte(byte, 5));
|
||||
visit(bit_util::GetBitFromByte(byte, 6));
|
||||
visit(bit_util::GetBitFromByte(byte, 7));
|
||||
}
|
||||
|
||||
// Write any leftover bits in the last byte.
|
||||
const int64_t num_bits_after_full_bytes = (length - num_bits_before_full_bytes) % 8;
|
||||
VisitBits<Visitor>(first_full_byte + num_full_bytes, 0, num_bits_after_full_bytes,
|
||||
visit);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,286 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "arrow/util/bit_util.h"
|
||||
#include "arrow/util/endian.h"
|
||||
#include "arrow/util/macros.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
class BitmapWriter {
|
||||
// A sequential bitwise writer that preserves surrounding bit values.
|
||||
|
||||
public:
|
||||
BitmapWriter(uint8_t* bitmap, int64_t start_offset, int64_t length)
|
||||
: bitmap_(bitmap), position_(0), length_(length) {
|
||||
byte_offset_ = start_offset / 8;
|
||||
bit_mask_ = bit_util::kBitmask[start_offset % 8];
|
||||
if (length > 0) {
|
||||
current_byte_ = bitmap[byte_offset_];
|
||||
} else {
|
||||
current_byte_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Set() { current_byte_ |= bit_mask_; }
|
||||
|
||||
void Clear() { current_byte_ &= bit_mask_ ^ 0xFF; }
|
||||
|
||||
void Next() {
|
||||
bit_mask_ = static_cast<uint8_t>(bit_mask_ << 1);
|
||||
++position_;
|
||||
if (bit_mask_ == 0) {
|
||||
// Finished this byte, need advancing
|
||||
bit_mask_ = 0x01;
|
||||
bitmap_[byte_offset_++] = current_byte_;
|
||||
if (ARROW_PREDICT_TRUE(position_ < length_)) {
|
||||
current_byte_ = bitmap_[byte_offset_];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Finish() {
|
||||
// Store current byte if we didn't went past bitmap storage
|
||||
if (length_ > 0 && (bit_mask_ != 0x01 || position_ < length_)) {
|
||||
bitmap_[byte_offset_] = current_byte_;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t position() const { return position_; }
|
||||
|
||||
private:
|
||||
uint8_t* bitmap_;
|
||||
int64_t position_;
|
||||
int64_t length_;
|
||||
|
||||
uint8_t current_byte_;
|
||||
uint8_t bit_mask_;
|
||||
int64_t byte_offset_;
|
||||
};
|
||||
|
||||
class FirstTimeBitmapWriter {
|
||||
// Like BitmapWriter, but any bit values *following* the bits written
|
||||
// might be clobbered. It is hence faster than BitmapWriter, and can
|
||||
// also avoid false positives with Valgrind.
|
||||
|
||||
public:
|
||||
FirstTimeBitmapWriter(uint8_t* bitmap, int64_t start_offset, int64_t length)
|
||||
: bitmap_(bitmap), position_(0), length_(length) {
|
||||
current_byte_ = 0;
|
||||
byte_offset_ = start_offset / 8;
|
||||
bit_mask_ = bit_util::kBitmask[start_offset % 8];
|
||||
if (length > 0) {
|
||||
current_byte_ =
|
||||
bitmap[byte_offset_] & bit_util::kPrecedingBitmask[start_offset % 8];
|
||||
} else {
|
||||
current_byte_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends number_of_bits from word to valid_bits and valid_bits_offset.
|
||||
///
|
||||
/// \param[in] word The LSB bitmap to append. Any bits past number_of_bits are assumed
|
||||
/// to be unset (i.e. 0).
|
||||
/// \param[in] number_of_bits The number of bits to append from word.
|
||||
void AppendWord(uint64_t word, int64_t number_of_bits) {
|
||||
if (ARROW_PREDICT_FALSE(number_of_bits == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Location that the first byte needs to be written to.
|
||||
uint8_t* append_position = bitmap_ + byte_offset_;
|
||||
|
||||
// Update state variables except for current_byte_ here.
|
||||
position_ += number_of_bits;
|
||||
int64_t bit_offset = bit_util::CountTrailingZeros(static_cast<uint32_t>(bit_mask_));
|
||||
bit_mask_ = bit_util::kBitmask[(bit_offset + number_of_bits) % 8];
|
||||
byte_offset_ += (bit_offset + number_of_bits) / 8;
|
||||
|
||||
if (bit_offset != 0) {
|
||||
// We are in the middle of the byte. This code updates the byte and shifts
|
||||
// bits appropriately within word so it can be memcpy'd below.
|
||||
int64_t bits_to_carry = 8 - bit_offset;
|
||||
// Carry over bits from word to current_byte_. We assume any extra bits in word
|
||||
// unset so no additional accounting is needed for when number_of_bits <
|
||||
// bits_to_carry.
|
||||
current_byte_ |= (word & bit_util::kPrecedingBitmask[bits_to_carry]) << bit_offset;
|
||||
// Check if everything is transfered into current_byte_.
|
||||
if (ARROW_PREDICT_FALSE(number_of_bits < bits_to_carry)) {
|
||||
return;
|
||||
}
|
||||
*append_position = current_byte_;
|
||||
append_position++;
|
||||
// Move the carry bits off of word.
|
||||
word = word >> bits_to_carry;
|
||||
number_of_bits -= bits_to_carry;
|
||||
}
|
||||
word = bit_util::ToLittleEndian(word);
|
||||
int64_t bytes_for_word = ::arrow::bit_util::BytesForBits(number_of_bits);
|
||||
std::memcpy(append_position, &word, bytes_for_word);
|
||||
// At this point, the previous current_byte_ has been written to bitmap_.
|
||||
// The new current_byte_ is either the last relevant byte in 'word'
|
||||
// or cleared if the new position is byte aligned (i.e. a fresh byte).
|
||||
if (bit_mask_ == 0x1) {
|
||||
current_byte_ = 0;
|
||||
} else {
|
||||
current_byte_ = *(append_position + bytes_for_word - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Set() { current_byte_ |= bit_mask_; }
|
||||
|
||||
void Clear() {}
|
||||
|
||||
void Next() {
|
||||
bit_mask_ = static_cast<uint8_t>(bit_mask_ << 1);
|
||||
++position_;
|
||||
if (bit_mask_ == 0) {
|
||||
// Finished this byte, need advancing
|
||||
bit_mask_ = 0x01;
|
||||
bitmap_[byte_offset_++] = current_byte_;
|
||||
current_byte_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Finish() {
|
||||
// Store current byte if we didn't went go bitmap storage
|
||||
if (length_ > 0 && (bit_mask_ != 0x01 || position_ < length_)) {
|
||||
bitmap_[byte_offset_] = current_byte_;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t position() const { return position_; }
|
||||
|
||||
private:
|
||||
uint8_t* bitmap_;
|
||||
int64_t position_;
|
||||
int64_t length_;
|
||||
|
||||
uint8_t current_byte_;
|
||||
uint8_t bit_mask_;
|
||||
int64_t byte_offset_;
|
||||
};
|
||||
|
||||
template <typename Word, bool may_have_byte_offset = true>
|
||||
class BitmapWordWriter {
|
||||
public:
|
||||
BitmapWordWriter() = default;
|
||||
BitmapWordWriter(uint8_t* bitmap, int64_t offset, int64_t length)
|
||||
: offset_(static_cast<int64_t>(may_have_byte_offset) * (offset % 8)),
|
||||
bitmap_(bitmap + offset / 8),
|
||||
bitmap_end_(bitmap_ + bit_util::BytesForBits(offset_ + length)),
|
||||
mask_((1U << offset_) - 1) {
|
||||
if (offset_) {
|
||||
if (length >= static_cast<int>(sizeof(Word) * 8)) {
|
||||
current_data.word_ = load<Word>(bitmap_);
|
||||
} else if (length > 0) {
|
||||
current_data.epi.byte_ = load<uint8_t>(bitmap_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PutNextWord(Word word) {
|
||||
if (may_have_byte_offset && offset_) {
|
||||
// split one word into two adjacent words, don't touch unused bits
|
||||
// |<------ word ----->|
|
||||
// +-----+-------------+
|
||||
// | A | B |
|
||||
// +-----+-------------+
|
||||
// | |
|
||||
// v v offset
|
||||
// +-------------+-----+-------------+-----+
|
||||
// | --- | A | B | --- |
|
||||
// +-------------+-----+-------------+-----+
|
||||
// |<------ next ----->|<---- current ---->|
|
||||
word = (word << offset_) | (word >> (sizeof(Word) * 8 - offset_));
|
||||
Word next_word = load<Word>(bitmap_ + sizeof(Word));
|
||||
current_data.word_ = (current_data.word_ & mask_) | (word & ~mask_);
|
||||
next_word = (next_word & ~mask_) | (word & mask_);
|
||||
store<Word>(bitmap_, current_data.word_);
|
||||
store<Word>(bitmap_ + sizeof(Word), next_word);
|
||||
current_data.word_ = next_word;
|
||||
} else {
|
||||
store<Word>(bitmap_, word);
|
||||
}
|
||||
bitmap_ += sizeof(Word);
|
||||
}
|
||||
|
||||
void PutNextTrailingByte(uint8_t byte, int valid_bits) {
|
||||
if (valid_bits == 8) {
|
||||
if (may_have_byte_offset && offset_) {
|
||||
byte = (byte << offset_) | (byte >> (8 - offset_));
|
||||
uint8_t next_byte = load<uint8_t>(bitmap_ + 1);
|
||||
current_data.epi.byte_ = (current_data.epi.byte_ & mask_) | (byte & ~mask_);
|
||||
next_byte = (next_byte & ~mask_) | (byte & mask_);
|
||||
store<uint8_t>(bitmap_, current_data.epi.byte_);
|
||||
store<uint8_t>(bitmap_ + 1, next_byte);
|
||||
current_data.epi.byte_ = next_byte;
|
||||
} else {
|
||||
store<uint8_t>(bitmap_, byte);
|
||||
}
|
||||
++bitmap_;
|
||||
} else {
|
||||
assert(valid_bits > 0);
|
||||
assert(valid_bits < 8);
|
||||
assert(bitmap_ + bit_util::BytesForBits(offset_ + valid_bits) <= bitmap_end_);
|
||||
internal::BitmapWriter writer(bitmap_, offset_, valid_bits);
|
||||
for (int i = 0; i < valid_bits; ++i) {
|
||||
(byte & 0x01) ? writer.Set() : writer.Clear();
|
||||
writer.Next();
|
||||
byte >>= 1;
|
||||
}
|
||||
writer.Finish();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t offset_;
|
||||
uint8_t* bitmap_;
|
||||
|
||||
const uint8_t* bitmap_end_;
|
||||
uint64_t mask_;
|
||||
union {
|
||||
Word word_;
|
||||
struct {
|
||||
#if ARROW_LITTLE_ENDIAN == 0
|
||||
uint8_t padding_bytes_[sizeof(Word) - 1];
|
||||
#endif
|
||||
uint8_t byte_;
|
||||
} epi;
|
||||
} current_data;
|
||||
|
||||
template <typename DType>
|
||||
DType load(const uint8_t* bitmap) {
|
||||
assert(bitmap + sizeof(DType) <= bitmap_end_);
|
||||
return bit_util::ToLittleEndian(util::SafeLoadAs<DType>(bitmap));
|
||||
}
|
||||
|
||||
template <typename DType>
|
||||
void store(uint8_t* bitmap, DType data) {
|
||||
assert(bitmap + sizeof(DType) <= bitmap_end_);
|
||||
util::SafeStore(bitmap, bit_util::FromLittleEndian(data));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/buffer.h"
|
||||
#include "arrow/memory_pool.h"
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/type_fwd.h"
|
||||
#include "arrow/util/bit_util.h"
|
||||
#include "arrow/util/compare.h"
|
||||
#include "arrow/util/functional.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/string_builder.h"
|
||||
#include "arrow/util/type_traits.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
/// \brief Store a stack of bitsets efficiently. The top bitset may be
|
||||
/// accessed and its bits may be modified, but it may not be resized.
|
||||
class BitsetStack {
|
||||
public:
|
||||
using reference = typename std::vector<bool>::reference;
|
||||
|
||||
/// \brief push a bitset onto the stack
|
||||
/// \param size number of bits in the next bitset
|
||||
/// \param value initial value for bits in the pushed bitset
|
||||
void Push(int size, bool value) {
|
||||
offsets_.push_back(bit_count());
|
||||
bits_.resize(bit_count() + size, value);
|
||||
}
|
||||
|
||||
/// \brief number of bits in the bitset at the top of the stack
|
||||
int TopSize() const {
|
||||
if (offsets_.size() == 0) return 0;
|
||||
return bit_count() - offsets_.back();
|
||||
}
|
||||
|
||||
/// \brief pop a bitset off the stack
|
||||
void Pop() {
|
||||
bits_.resize(offsets_.back());
|
||||
offsets_.pop_back();
|
||||
}
|
||||
|
||||
/// \brief get the value of a bit in the top bitset
|
||||
/// \param i index of the bit to access
|
||||
bool operator[](int i) const { return bits_[offsets_.back() + i]; }
|
||||
|
||||
/// \brief get a mutable reference to a bit in the top bitset
|
||||
/// \param i index of the bit to access
|
||||
reference operator[](int i) { return bits_[offsets_.back() + i]; }
|
||||
|
||||
private:
|
||||
int bit_count() const { return static_cast<int>(bits_.size()); }
|
||||
std::vector<bool> bits_;
|
||||
std::vector<int> offsets_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arrow/util/endian.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
ARROW_EXPORT
|
||||
int unpack32(const uint32_t* in, uint32_t* out, int batch_size, int num_bits);
|
||||
ARROW_EXPORT
|
||||
int unpack64(const uint8_t* in, uint64_t* out, int batch_size, int num_bits);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
int unpack32_avx2(const uint32_t* in, uint32_t* out, int batch_size, int num_bits);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
int unpack32_avx512(const uint32_t* in, uint32_t* out, int batch_size, int num_bits);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
int unpack32_neon(const uint32_t* in, uint32_t* out, int batch_size, int num_bits);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,836 @@
|
||||
// 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.
|
||||
|
||||
// Automatically generated file; DO NOT EDIT.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include <xsimd/xsimd.hpp>
|
||||
|
||||
#include "arrow/util/dispatch.h"
|
||||
#include "arrow/util/ubsan.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
namespace {
|
||||
|
||||
using ::arrow::util::SafeLoad;
|
||||
|
||||
template <DispatchLevel level>
|
||||
struct UnpackBits512 {
|
||||
|
||||
using simd_batch = xsimd::make_sized_batch_t<uint32_t, 16>;
|
||||
|
||||
inline static const uint32_t* unpack0_32(const uint32_t* in, uint32_t* out) {
|
||||
memset(out, 0x0, 32 * sizeof(*out));
|
||||
out += 32;
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack1_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x1;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 1-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) };
|
||||
shifts = simd_batch{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 1-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) };
|
||||
shifts = simd_batch{ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 1;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack2_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x3;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 2-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) };
|
||||
shifts = simd_batch{ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 2-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) };
|
||||
shifts = simd_batch{ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 2;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack3_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x7;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 3-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 30 | SafeLoad<uint32_t>(in + 1) << 2, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) };
|
||||
shifts = simd_batch{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 0, 1, 4, 7, 10, 13 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 3-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 31 | SafeLoad<uint32_t>(in + 2) << 1, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) };
|
||||
shifts = simd_batch{ 16, 19, 22, 25, 28, 0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 29 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 3;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack4_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0xf;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 4-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) };
|
||||
shifts = simd_batch{ 0, 4, 8, 12, 16, 20, 24, 28, 0, 4, 8, 12, 16, 20, 24, 28 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 4-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) };
|
||||
shifts = simd_batch{ 0, 4, 8, 12, 16, 20, 24, 28, 0, 4, 8, 12, 16, 20, 24, 28 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 4;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack5_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x1f;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 5-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 30 | SafeLoad<uint32_t>(in + 1) << 2, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 28 | SafeLoad<uint32_t>(in + 2) << 4, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) };
|
||||
shifts = simd_batch{ 0, 5, 10, 15, 20, 25, 0, 3, 8, 13, 18, 23, 0, 1, 6, 11 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 5-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 31 | SafeLoad<uint32_t>(in + 3) << 1, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 29 | SafeLoad<uint32_t>(in + 4) << 3, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) };
|
||||
shifts = simd_batch{ 16, 21, 26, 0, 4, 9, 14, 19, 24, 0, 2, 7, 12, 17, 22, 27 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 5;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack6_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x3f;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 6-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 30 | SafeLoad<uint32_t>(in + 1) << 2, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 28 | SafeLoad<uint32_t>(in + 2) << 4, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) };
|
||||
shifts = simd_batch{ 0, 6, 12, 18, 24, 0, 4, 10, 16, 22, 0, 2, 8, 14, 20, 26 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 6-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 30 | SafeLoad<uint32_t>(in + 4) << 2, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 28 | SafeLoad<uint32_t>(in + 5) << 4, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) };
|
||||
shifts = simd_batch{ 0, 6, 12, 18, 24, 0, 4, 10, 16, 22, 0, 2, 8, 14, 20, 26 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 6;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack7_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x7f;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 7-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 28 | SafeLoad<uint32_t>(in + 1) << 4, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 31 | SafeLoad<uint32_t>(in + 2) << 1, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 27 | SafeLoad<uint32_t>(in + 3) << 5, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) };
|
||||
shifts = simd_batch{ 0, 7, 14, 21, 0, 3, 10, 17, 24, 0, 6, 13, 20, 0, 2, 9 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 7-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 30 | SafeLoad<uint32_t>(in + 4) << 2, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 26 | SafeLoad<uint32_t>(in + 5) << 6, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 29 | SafeLoad<uint32_t>(in + 6) << 3, SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) };
|
||||
shifts = simd_batch{ 16, 23, 0, 5, 12, 19, 0, 1, 8, 15, 22, 0, 4, 11, 18, 25 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 7;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack8_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0xff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 8-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) };
|
||||
shifts = simd_batch{ 0, 8, 16, 24, 0, 8, 16, 24, 0, 8, 16, 24, 0, 8, 16, 24 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 8-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) };
|
||||
shifts = simd_batch{ 0, 8, 16, 24, 0, 8, 16, 24, 0, 8, 16, 24, 0, 8, 16, 24 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 8;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack9_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x1ff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 9-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 27 | SafeLoad<uint32_t>(in + 1) << 5, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 31 | SafeLoad<uint32_t>(in + 2) << 1, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 26 | SafeLoad<uint32_t>(in + 3) << 6, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 30 | SafeLoad<uint32_t>(in + 4) << 2, SafeLoad<uint32_t>(in + 4) };
|
||||
shifts = simd_batch{ 0, 9, 18, 0, 4, 13, 22, 0, 8, 17, 0, 3, 12, 21, 0, 7 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 9-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 25 | SafeLoad<uint32_t>(in + 5) << 7, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 29 | SafeLoad<uint32_t>(in + 6) << 3, SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) >> 24 | SafeLoad<uint32_t>(in + 7) << 8, SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 28 | SafeLoad<uint32_t>(in + 8) << 4, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8) };
|
||||
shifts = simd_batch{ 16, 0, 2, 11, 20, 0, 6, 15, 0, 1, 10, 19, 0, 5, 14, 23 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 9;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack10_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x3ff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 10-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 30 | SafeLoad<uint32_t>(in + 1) << 2, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 28 | SafeLoad<uint32_t>(in + 2) << 4, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 26 | SafeLoad<uint32_t>(in + 3) << 6, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 24 | SafeLoad<uint32_t>(in + 4) << 8, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) };
|
||||
shifts = simd_batch{ 0, 10, 20, 0, 8, 18, 0, 6, 16, 0, 4, 14, 0, 2, 12, 22 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 10-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 30 | SafeLoad<uint32_t>(in + 6) << 2, SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) >> 28 | SafeLoad<uint32_t>(in + 7) << 4, SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 26 | SafeLoad<uint32_t>(in + 8) << 6, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8) >> 24 | SafeLoad<uint32_t>(in + 9) << 8, SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9) };
|
||||
shifts = simd_batch{ 0, 10, 20, 0, 8, 18, 0, 6, 16, 0, 4, 14, 0, 2, 12, 22 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 10;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack11_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x7ff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 11-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 22 | SafeLoad<uint32_t>(in + 1) << 10, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 23 | SafeLoad<uint32_t>(in + 2) << 9, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 24 | SafeLoad<uint32_t>(in + 3) << 8, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 25 | SafeLoad<uint32_t>(in + 4) << 7, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 26 | SafeLoad<uint32_t>(in + 5) << 6, SafeLoad<uint32_t>(in + 5) };
|
||||
shifts = simd_batch{ 0, 11, 0, 1, 12, 0, 2, 13, 0, 3, 14, 0, 4, 15, 0, 5 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 11-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 27 | SafeLoad<uint32_t>(in + 6) << 5, SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) >> 28 | SafeLoad<uint32_t>(in + 7) << 4, SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 29 | SafeLoad<uint32_t>(in + 8) << 3, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8) >> 30 | SafeLoad<uint32_t>(in + 9) << 2, SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9) >> 31 | SafeLoad<uint32_t>(in + 10) << 1, SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) };
|
||||
shifts = simd_batch{ 16, 0, 6, 17, 0, 7, 18, 0, 8, 19, 0, 9, 20, 0, 10, 21 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 11;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack12_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0xfff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 12-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 24 | SafeLoad<uint32_t>(in + 1) << 8, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 28 | SafeLoad<uint32_t>(in + 2) << 4, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 24 | SafeLoad<uint32_t>(in + 4) << 8, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 28 | SafeLoad<uint32_t>(in + 5) << 4, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) };
|
||||
shifts = simd_batch{ 0, 12, 0, 4, 16, 0, 8, 20, 0, 12, 0, 4, 16, 0, 8, 20 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 12-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) >> 24 | SafeLoad<uint32_t>(in + 7) << 8, SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 28 | SafeLoad<uint32_t>(in + 8) << 4, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9) >> 24 | SafeLoad<uint32_t>(in + 10) << 8, SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) >> 28 | SafeLoad<uint32_t>(in + 11) << 4, SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 11) };
|
||||
shifts = simd_batch{ 0, 12, 0, 4, 16, 0, 8, 20, 0, 12, 0, 4, 16, 0, 8, 20 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 12;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack13_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x1fff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 13-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 26 | SafeLoad<uint32_t>(in + 1) << 6, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 20 | SafeLoad<uint32_t>(in + 2) << 12, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 27 | SafeLoad<uint32_t>(in + 3) << 5, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 21 | SafeLoad<uint32_t>(in + 4) << 11, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 28 | SafeLoad<uint32_t>(in + 5) << 4, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 22 | SafeLoad<uint32_t>(in + 6) << 10, SafeLoad<uint32_t>(in + 6) };
|
||||
shifts = simd_batch{ 0, 13, 0, 7, 0, 1, 14, 0, 8, 0, 2, 15, 0, 9, 0, 3 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 13-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) >> 29 | SafeLoad<uint32_t>(in + 7) << 3, SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 23 | SafeLoad<uint32_t>(in + 8) << 9, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8) >> 30 | SafeLoad<uint32_t>(in + 9) << 2, SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9) >> 24 | SafeLoad<uint32_t>(in + 10) << 8, SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) >> 31 | SafeLoad<uint32_t>(in + 11) << 1, SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 11) >> 25 | SafeLoad<uint32_t>(in + 12) << 7, SafeLoad<uint32_t>(in + 12), SafeLoad<uint32_t>(in + 12) };
|
||||
shifts = simd_batch{ 16, 0, 10, 0, 4, 17, 0, 11, 0, 5, 18, 0, 12, 0, 6, 19 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 13;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack14_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x3fff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 14-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 28 | SafeLoad<uint32_t>(in + 1) << 4, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 24 | SafeLoad<uint32_t>(in + 2) << 8, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 20 | SafeLoad<uint32_t>(in + 3) << 12, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 30 | SafeLoad<uint32_t>(in + 4) << 2, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 26 | SafeLoad<uint32_t>(in + 5) << 6, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 22 | SafeLoad<uint32_t>(in + 6) << 10, SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) };
|
||||
shifts = simd_batch{ 0, 14, 0, 10, 0, 6, 0, 2, 16, 0, 12, 0, 8, 0, 4, 18 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 14-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 28 | SafeLoad<uint32_t>(in + 8) << 4, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8) >> 24 | SafeLoad<uint32_t>(in + 9) << 8, SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9) >> 20 | SafeLoad<uint32_t>(in + 10) << 12, SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) >> 30 | SafeLoad<uint32_t>(in + 11) << 2, SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 11) >> 26 | SafeLoad<uint32_t>(in + 12) << 6, SafeLoad<uint32_t>(in + 12), SafeLoad<uint32_t>(in + 12) >> 22 | SafeLoad<uint32_t>(in + 13) << 10, SafeLoad<uint32_t>(in + 13), SafeLoad<uint32_t>(in + 13) };
|
||||
shifts = simd_batch{ 0, 14, 0, 10, 0, 6, 0, 2, 16, 0, 12, 0, 8, 0, 4, 18 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 14;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack15_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x7fff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 15-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 30 | SafeLoad<uint32_t>(in + 1) << 2, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 28 | SafeLoad<uint32_t>(in + 2) << 4, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 26 | SafeLoad<uint32_t>(in + 3) << 6, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 24 | SafeLoad<uint32_t>(in + 4) << 8, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 22 | SafeLoad<uint32_t>(in + 5) << 10, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 20 | SafeLoad<uint32_t>(in + 6) << 12, SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) >> 18 | SafeLoad<uint32_t>(in + 7) << 14, SafeLoad<uint32_t>(in + 7) };
|
||||
shifts = simd_batch{ 0, 15, 0, 13, 0, 11, 0, 9, 0, 7, 0, 5, 0, 3, 0, 1 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 15-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 31 | SafeLoad<uint32_t>(in + 8) << 1, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8) >> 29 | SafeLoad<uint32_t>(in + 9) << 3, SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9) >> 27 | SafeLoad<uint32_t>(in + 10) << 5, SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) >> 25 | SafeLoad<uint32_t>(in + 11) << 7, SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 11) >> 23 | SafeLoad<uint32_t>(in + 12) << 9, SafeLoad<uint32_t>(in + 12), SafeLoad<uint32_t>(in + 12) >> 21 | SafeLoad<uint32_t>(in + 13) << 11, SafeLoad<uint32_t>(in + 13), SafeLoad<uint32_t>(in + 13) >> 19 | SafeLoad<uint32_t>(in + 14) << 13, SafeLoad<uint32_t>(in + 14), SafeLoad<uint32_t>(in + 14) };
|
||||
shifts = simd_batch{ 16, 0, 14, 0, 12, 0, 10, 0, 8, 0, 6, 0, 4, 0, 2, 17 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 15;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack16_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0xffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 16-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) };
|
||||
shifts = simd_batch{ 0, 16, 0, 16, 0, 16, 0, 16, 0, 16, 0, 16, 0, 16, 0, 16 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 16-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 12), SafeLoad<uint32_t>(in + 12), SafeLoad<uint32_t>(in + 13), SafeLoad<uint32_t>(in + 13), SafeLoad<uint32_t>(in + 14), SafeLoad<uint32_t>(in + 14), SafeLoad<uint32_t>(in + 15), SafeLoad<uint32_t>(in + 15) };
|
||||
shifts = simd_batch{ 0, 16, 0, 16, 0, 16, 0, 16, 0, 16, 0, 16, 0, 16, 0, 16 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 16;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack17_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x1ffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 17-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 17 | SafeLoad<uint32_t>(in + 1) << 15, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 19 | SafeLoad<uint32_t>(in + 2) << 13, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 21 | SafeLoad<uint32_t>(in + 3) << 11, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 23 | SafeLoad<uint32_t>(in + 4) << 9, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 25 | SafeLoad<uint32_t>(in + 5) << 7, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 27 | SafeLoad<uint32_t>(in + 6) << 5, SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) >> 29 | SafeLoad<uint32_t>(in + 7) << 3, SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 31 | SafeLoad<uint32_t>(in + 8) << 1 };
|
||||
shifts = simd_batch{ 0, 0, 2, 0, 4, 0, 6, 0, 8, 0, 10, 0, 12, 0, 14, 0 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 17-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 8) >> 16 | SafeLoad<uint32_t>(in + 9) << 16, SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9) >> 18 | SafeLoad<uint32_t>(in + 10) << 14, SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) >> 20 | SafeLoad<uint32_t>(in + 11) << 12, SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 11) >> 22 | SafeLoad<uint32_t>(in + 12) << 10, SafeLoad<uint32_t>(in + 12), SafeLoad<uint32_t>(in + 12) >> 24 | SafeLoad<uint32_t>(in + 13) << 8, SafeLoad<uint32_t>(in + 13), SafeLoad<uint32_t>(in + 13) >> 26 | SafeLoad<uint32_t>(in + 14) << 6, SafeLoad<uint32_t>(in + 14), SafeLoad<uint32_t>(in + 14) >> 28 | SafeLoad<uint32_t>(in + 15) << 4, SafeLoad<uint32_t>(in + 15), SafeLoad<uint32_t>(in + 15) >> 30 | SafeLoad<uint32_t>(in + 16) << 2, SafeLoad<uint32_t>(in + 16) };
|
||||
shifts = simd_batch{ 0, 1, 0, 3, 0, 5, 0, 7, 0, 9, 0, 11, 0, 13, 0, 15 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 17;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack18_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x3ffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 18-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 18 | SafeLoad<uint32_t>(in + 1) << 14, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 22 | SafeLoad<uint32_t>(in + 2) << 10, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 26 | SafeLoad<uint32_t>(in + 3) << 6, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 30 | SafeLoad<uint32_t>(in + 4) << 2, SafeLoad<uint32_t>(in + 4) >> 16 | SafeLoad<uint32_t>(in + 5) << 16, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 20 | SafeLoad<uint32_t>(in + 6) << 12, SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) >> 24 | SafeLoad<uint32_t>(in + 7) << 8, SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 28 | SafeLoad<uint32_t>(in + 8) << 4, SafeLoad<uint32_t>(in + 8) };
|
||||
shifts = simd_batch{ 0, 0, 4, 0, 8, 0, 12, 0, 0, 2, 0, 6, 0, 10, 0, 14 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 18-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9) >> 18 | SafeLoad<uint32_t>(in + 10) << 14, SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) >> 22 | SafeLoad<uint32_t>(in + 11) << 10, SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 11) >> 26 | SafeLoad<uint32_t>(in + 12) << 6, SafeLoad<uint32_t>(in + 12), SafeLoad<uint32_t>(in + 12) >> 30 | SafeLoad<uint32_t>(in + 13) << 2, SafeLoad<uint32_t>(in + 13) >> 16 | SafeLoad<uint32_t>(in + 14) << 16, SafeLoad<uint32_t>(in + 14), SafeLoad<uint32_t>(in + 14) >> 20 | SafeLoad<uint32_t>(in + 15) << 12, SafeLoad<uint32_t>(in + 15), SafeLoad<uint32_t>(in + 15) >> 24 | SafeLoad<uint32_t>(in + 16) << 8, SafeLoad<uint32_t>(in + 16), SafeLoad<uint32_t>(in + 16) >> 28 | SafeLoad<uint32_t>(in + 17) << 4, SafeLoad<uint32_t>(in + 17) };
|
||||
shifts = simd_batch{ 0, 0, 4, 0, 8, 0, 12, 0, 0, 2, 0, 6, 0, 10, 0, 14 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 18;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack19_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x7ffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 19-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 19 | SafeLoad<uint32_t>(in + 1) << 13, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 25 | SafeLoad<uint32_t>(in + 2) << 7, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 31 | SafeLoad<uint32_t>(in + 3) << 1, SafeLoad<uint32_t>(in + 3) >> 18 | SafeLoad<uint32_t>(in + 4) << 14, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 24 | SafeLoad<uint32_t>(in + 5) << 8, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 30 | SafeLoad<uint32_t>(in + 6) << 2, SafeLoad<uint32_t>(in + 6) >> 17 | SafeLoad<uint32_t>(in + 7) << 15, SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 23 | SafeLoad<uint32_t>(in + 8) << 9, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8) >> 29 | SafeLoad<uint32_t>(in + 9) << 3 };
|
||||
shifts = simd_batch{ 0, 0, 6, 0, 12, 0, 0, 5, 0, 11, 0, 0, 4, 0, 10, 0 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 19-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 9) >> 16 | SafeLoad<uint32_t>(in + 10) << 16, SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) >> 22 | SafeLoad<uint32_t>(in + 11) << 10, SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 11) >> 28 | SafeLoad<uint32_t>(in + 12) << 4, SafeLoad<uint32_t>(in + 12) >> 15 | SafeLoad<uint32_t>(in + 13) << 17, SafeLoad<uint32_t>(in + 13), SafeLoad<uint32_t>(in + 13) >> 21 | SafeLoad<uint32_t>(in + 14) << 11, SafeLoad<uint32_t>(in + 14), SafeLoad<uint32_t>(in + 14) >> 27 | SafeLoad<uint32_t>(in + 15) << 5, SafeLoad<uint32_t>(in + 15) >> 14 | SafeLoad<uint32_t>(in + 16) << 18, SafeLoad<uint32_t>(in + 16), SafeLoad<uint32_t>(in + 16) >> 20 | SafeLoad<uint32_t>(in + 17) << 12, SafeLoad<uint32_t>(in + 17), SafeLoad<uint32_t>(in + 17) >> 26 | SafeLoad<uint32_t>(in + 18) << 6, SafeLoad<uint32_t>(in + 18) };
|
||||
shifts = simd_batch{ 0, 3, 0, 9, 0, 0, 2, 0, 8, 0, 0, 1, 0, 7, 0, 13 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 19;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack20_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0xfffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 20-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 20 | SafeLoad<uint32_t>(in + 1) << 12, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 28 | SafeLoad<uint32_t>(in + 2) << 4, SafeLoad<uint32_t>(in + 2) >> 16 | SafeLoad<uint32_t>(in + 3) << 16, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 24 | SafeLoad<uint32_t>(in + 4) << 8, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 20 | SafeLoad<uint32_t>(in + 6) << 12, SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) >> 28 | SafeLoad<uint32_t>(in + 7) << 4, SafeLoad<uint32_t>(in + 7) >> 16 | SafeLoad<uint32_t>(in + 8) << 16, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8) >> 24 | SafeLoad<uint32_t>(in + 9) << 8, SafeLoad<uint32_t>(in + 9) };
|
||||
shifts = simd_batch{ 0, 0, 8, 0, 0, 4, 0, 12, 0, 0, 8, 0, 0, 4, 0, 12 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 20-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) >> 20 | SafeLoad<uint32_t>(in + 11) << 12, SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 11) >> 28 | SafeLoad<uint32_t>(in + 12) << 4, SafeLoad<uint32_t>(in + 12) >> 16 | SafeLoad<uint32_t>(in + 13) << 16, SafeLoad<uint32_t>(in + 13), SafeLoad<uint32_t>(in + 13) >> 24 | SafeLoad<uint32_t>(in + 14) << 8, SafeLoad<uint32_t>(in + 14), SafeLoad<uint32_t>(in + 15), SafeLoad<uint32_t>(in + 15) >> 20 | SafeLoad<uint32_t>(in + 16) << 12, SafeLoad<uint32_t>(in + 16), SafeLoad<uint32_t>(in + 16) >> 28 | SafeLoad<uint32_t>(in + 17) << 4, SafeLoad<uint32_t>(in + 17) >> 16 | SafeLoad<uint32_t>(in + 18) << 16, SafeLoad<uint32_t>(in + 18), SafeLoad<uint32_t>(in + 18) >> 24 | SafeLoad<uint32_t>(in + 19) << 8, SafeLoad<uint32_t>(in + 19) };
|
||||
shifts = simd_batch{ 0, 0, 8, 0, 0, 4, 0, 12, 0, 0, 8, 0, 0, 4, 0, 12 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 20;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack21_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x1fffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 21-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 21 | SafeLoad<uint32_t>(in + 1) << 11, SafeLoad<uint32_t>(in + 1), SafeLoad<uint32_t>(in + 1) >> 31 | SafeLoad<uint32_t>(in + 2) << 1, SafeLoad<uint32_t>(in + 2) >> 20 | SafeLoad<uint32_t>(in + 3) << 12, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 30 | SafeLoad<uint32_t>(in + 4) << 2, SafeLoad<uint32_t>(in + 4) >> 19 | SafeLoad<uint32_t>(in + 5) << 13, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 29 | SafeLoad<uint32_t>(in + 6) << 3, SafeLoad<uint32_t>(in + 6) >> 18 | SafeLoad<uint32_t>(in + 7) << 14, SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 28 | SafeLoad<uint32_t>(in + 8) << 4, SafeLoad<uint32_t>(in + 8) >> 17 | SafeLoad<uint32_t>(in + 9) << 15, SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9) >> 27 | SafeLoad<uint32_t>(in + 10) << 5 };
|
||||
shifts = simd_batch{ 0, 0, 10, 0, 0, 9, 0, 0, 8, 0, 0, 7, 0, 0, 6, 0 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 21-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 10) >> 16 | SafeLoad<uint32_t>(in + 11) << 16, SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 11) >> 26 | SafeLoad<uint32_t>(in + 12) << 6, SafeLoad<uint32_t>(in + 12) >> 15 | SafeLoad<uint32_t>(in + 13) << 17, SafeLoad<uint32_t>(in + 13), SafeLoad<uint32_t>(in + 13) >> 25 | SafeLoad<uint32_t>(in + 14) << 7, SafeLoad<uint32_t>(in + 14) >> 14 | SafeLoad<uint32_t>(in + 15) << 18, SafeLoad<uint32_t>(in + 15), SafeLoad<uint32_t>(in + 15) >> 24 | SafeLoad<uint32_t>(in + 16) << 8, SafeLoad<uint32_t>(in + 16) >> 13 | SafeLoad<uint32_t>(in + 17) << 19, SafeLoad<uint32_t>(in + 17), SafeLoad<uint32_t>(in + 17) >> 23 | SafeLoad<uint32_t>(in + 18) << 9, SafeLoad<uint32_t>(in + 18) >> 12 | SafeLoad<uint32_t>(in + 19) << 20, SafeLoad<uint32_t>(in + 19), SafeLoad<uint32_t>(in + 19) >> 22 | SafeLoad<uint32_t>(in + 20) << 10, SafeLoad<uint32_t>(in + 20) };
|
||||
shifts = simd_batch{ 0, 5, 0, 0, 4, 0, 0, 3, 0, 0, 2, 0, 0, 1, 0, 11 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 21;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack22_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x3fffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 22-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 22 | SafeLoad<uint32_t>(in + 1) << 10, SafeLoad<uint32_t>(in + 1) >> 12 | SafeLoad<uint32_t>(in + 2) << 20, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 24 | SafeLoad<uint32_t>(in + 3) << 8, SafeLoad<uint32_t>(in + 3) >> 14 | SafeLoad<uint32_t>(in + 4) << 18, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 26 | SafeLoad<uint32_t>(in + 5) << 6, SafeLoad<uint32_t>(in + 5) >> 16 | SafeLoad<uint32_t>(in + 6) << 16, SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) >> 28 | SafeLoad<uint32_t>(in + 7) << 4, SafeLoad<uint32_t>(in + 7) >> 18 | SafeLoad<uint32_t>(in + 8) << 14, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8) >> 30 | SafeLoad<uint32_t>(in + 9) << 2, SafeLoad<uint32_t>(in + 9) >> 20 | SafeLoad<uint32_t>(in + 10) << 12, SafeLoad<uint32_t>(in + 10) };
|
||||
shifts = simd_batch{ 0, 0, 0, 2, 0, 0, 4, 0, 0, 6, 0, 0, 8, 0, 0, 10 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 22-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 11), SafeLoad<uint32_t>(in + 11) >> 22 | SafeLoad<uint32_t>(in + 12) << 10, SafeLoad<uint32_t>(in + 12) >> 12 | SafeLoad<uint32_t>(in + 13) << 20, SafeLoad<uint32_t>(in + 13), SafeLoad<uint32_t>(in + 13) >> 24 | SafeLoad<uint32_t>(in + 14) << 8, SafeLoad<uint32_t>(in + 14) >> 14 | SafeLoad<uint32_t>(in + 15) << 18, SafeLoad<uint32_t>(in + 15), SafeLoad<uint32_t>(in + 15) >> 26 | SafeLoad<uint32_t>(in + 16) << 6, SafeLoad<uint32_t>(in + 16) >> 16 | SafeLoad<uint32_t>(in + 17) << 16, SafeLoad<uint32_t>(in + 17), SafeLoad<uint32_t>(in + 17) >> 28 | SafeLoad<uint32_t>(in + 18) << 4, SafeLoad<uint32_t>(in + 18) >> 18 | SafeLoad<uint32_t>(in + 19) << 14, SafeLoad<uint32_t>(in + 19), SafeLoad<uint32_t>(in + 19) >> 30 | SafeLoad<uint32_t>(in + 20) << 2, SafeLoad<uint32_t>(in + 20) >> 20 | SafeLoad<uint32_t>(in + 21) << 12, SafeLoad<uint32_t>(in + 21) };
|
||||
shifts = simd_batch{ 0, 0, 0, 2, 0, 0, 4, 0, 0, 6, 0, 0, 8, 0, 0, 10 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 22;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack23_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x7fffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 23-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 23 | SafeLoad<uint32_t>(in + 1) << 9, SafeLoad<uint32_t>(in + 1) >> 14 | SafeLoad<uint32_t>(in + 2) << 18, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 2) >> 28 | SafeLoad<uint32_t>(in + 3) << 4, SafeLoad<uint32_t>(in + 3) >> 19 | SafeLoad<uint32_t>(in + 4) << 13, SafeLoad<uint32_t>(in + 4) >> 10 | SafeLoad<uint32_t>(in + 5) << 22, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 24 | SafeLoad<uint32_t>(in + 6) << 8, SafeLoad<uint32_t>(in + 6) >> 15 | SafeLoad<uint32_t>(in + 7) << 17, SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 29 | SafeLoad<uint32_t>(in + 8) << 3, SafeLoad<uint32_t>(in + 8) >> 20 | SafeLoad<uint32_t>(in + 9) << 12, SafeLoad<uint32_t>(in + 9) >> 11 | SafeLoad<uint32_t>(in + 10) << 21, SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) >> 25 | SafeLoad<uint32_t>(in + 11) << 7 };
|
||||
shifts = simd_batch{ 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 6, 0, 0, 0, 2, 0 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 23-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 11) >> 16 | SafeLoad<uint32_t>(in + 12) << 16, SafeLoad<uint32_t>(in + 12), SafeLoad<uint32_t>(in + 12) >> 30 | SafeLoad<uint32_t>(in + 13) << 2, SafeLoad<uint32_t>(in + 13) >> 21 | SafeLoad<uint32_t>(in + 14) << 11, SafeLoad<uint32_t>(in + 14) >> 12 | SafeLoad<uint32_t>(in + 15) << 20, SafeLoad<uint32_t>(in + 15), SafeLoad<uint32_t>(in + 15) >> 26 | SafeLoad<uint32_t>(in + 16) << 6, SafeLoad<uint32_t>(in + 16) >> 17 | SafeLoad<uint32_t>(in + 17) << 15, SafeLoad<uint32_t>(in + 17), SafeLoad<uint32_t>(in + 17) >> 31 | SafeLoad<uint32_t>(in + 18) << 1, SafeLoad<uint32_t>(in + 18) >> 22 | SafeLoad<uint32_t>(in + 19) << 10, SafeLoad<uint32_t>(in + 19) >> 13 | SafeLoad<uint32_t>(in + 20) << 19, SafeLoad<uint32_t>(in + 20), SafeLoad<uint32_t>(in + 20) >> 27 | SafeLoad<uint32_t>(in + 21) << 5, SafeLoad<uint32_t>(in + 21) >> 18 | SafeLoad<uint32_t>(in + 22) << 14, SafeLoad<uint32_t>(in + 22) };
|
||||
shifts = simd_batch{ 0, 7, 0, 0, 0, 3, 0, 0, 8, 0, 0, 0, 4, 0, 0, 9 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 23;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack24_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0xffffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 24-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 24 | SafeLoad<uint32_t>(in + 1) << 8, SafeLoad<uint32_t>(in + 1) >> 16 | SafeLoad<uint32_t>(in + 2) << 16, SafeLoad<uint32_t>(in + 2), SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 24 | SafeLoad<uint32_t>(in + 4) << 8, SafeLoad<uint32_t>(in + 4) >> 16 | SafeLoad<uint32_t>(in + 5) << 16, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 6) >> 24 | SafeLoad<uint32_t>(in + 7) << 8, SafeLoad<uint32_t>(in + 7) >> 16 | SafeLoad<uint32_t>(in + 8) << 16, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9) >> 24 | SafeLoad<uint32_t>(in + 10) << 8, SafeLoad<uint32_t>(in + 10) >> 16 | SafeLoad<uint32_t>(in + 11) << 16, SafeLoad<uint32_t>(in + 11) };
|
||||
shifts = simd_batch{ 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 24-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 12), SafeLoad<uint32_t>(in + 12) >> 24 | SafeLoad<uint32_t>(in + 13) << 8, SafeLoad<uint32_t>(in + 13) >> 16 | SafeLoad<uint32_t>(in + 14) << 16, SafeLoad<uint32_t>(in + 14), SafeLoad<uint32_t>(in + 15), SafeLoad<uint32_t>(in + 15) >> 24 | SafeLoad<uint32_t>(in + 16) << 8, SafeLoad<uint32_t>(in + 16) >> 16 | SafeLoad<uint32_t>(in + 17) << 16, SafeLoad<uint32_t>(in + 17), SafeLoad<uint32_t>(in + 18), SafeLoad<uint32_t>(in + 18) >> 24 | SafeLoad<uint32_t>(in + 19) << 8, SafeLoad<uint32_t>(in + 19) >> 16 | SafeLoad<uint32_t>(in + 20) << 16, SafeLoad<uint32_t>(in + 20), SafeLoad<uint32_t>(in + 21), SafeLoad<uint32_t>(in + 21) >> 24 | SafeLoad<uint32_t>(in + 22) << 8, SafeLoad<uint32_t>(in + 22) >> 16 | SafeLoad<uint32_t>(in + 23) << 16, SafeLoad<uint32_t>(in + 23) };
|
||||
shifts = simd_batch{ 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 24;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack25_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x1ffffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 25-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 25 | SafeLoad<uint32_t>(in + 1) << 7, SafeLoad<uint32_t>(in + 1) >> 18 | SafeLoad<uint32_t>(in + 2) << 14, SafeLoad<uint32_t>(in + 2) >> 11 | SafeLoad<uint32_t>(in + 3) << 21, SafeLoad<uint32_t>(in + 3), SafeLoad<uint32_t>(in + 3) >> 29 | SafeLoad<uint32_t>(in + 4) << 3, SafeLoad<uint32_t>(in + 4) >> 22 | SafeLoad<uint32_t>(in + 5) << 10, SafeLoad<uint32_t>(in + 5) >> 15 | SafeLoad<uint32_t>(in + 6) << 17, SafeLoad<uint32_t>(in + 6) >> 8 | SafeLoad<uint32_t>(in + 7) << 24, SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 26 | SafeLoad<uint32_t>(in + 8) << 6, SafeLoad<uint32_t>(in + 8) >> 19 | SafeLoad<uint32_t>(in + 9) << 13, SafeLoad<uint32_t>(in + 9) >> 12 | SafeLoad<uint32_t>(in + 10) << 20, SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) >> 30 | SafeLoad<uint32_t>(in + 11) << 2, SafeLoad<uint32_t>(in + 11) >> 23 | SafeLoad<uint32_t>(in + 12) << 9 };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 4, 0, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 25-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 12) >> 16 | SafeLoad<uint32_t>(in + 13) << 16, SafeLoad<uint32_t>(in + 13) >> 9 | SafeLoad<uint32_t>(in + 14) << 23, SafeLoad<uint32_t>(in + 14), SafeLoad<uint32_t>(in + 14) >> 27 | SafeLoad<uint32_t>(in + 15) << 5, SafeLoad<uint32_t>(in + 15) >> 20 | SafeLoad<uint32_t>(in + 16) << 12, SafeLoad<uint32_t>(in + 16) >> 13 | SafeLoad<uint32_t>(in + 17) << 19, SafeLoad<uint32_t>(in + 17), SafeLoad<uint32_t>(in + 17) >> 31 | SafeLoad<uint32_t>(in + 18) << 1, SafeLoad<uint32_t>(in + 18) >> 24 | SafeLoad<uint32_t>(in + 19) << 8, SafeLoad<uint32_t>(in + 19) >> 17 | SafeLoad<uint32_t>(in + 20) << 15, SafeLoad<uint32_t>(in + 20) >> 10 | SafeLoad<uint32_t>(in + 21) << 22, SafeLoad<uint32_t>(in + 21), SafeLoad<uint32_t>(in + 21) >> 28 | SafeLoad<uint32_t>(in + 22) << 4, SafeLoad<uint32_t>(in + 22) >> 21 | SafeLoad<uint32_t>(in + 23) << 11, SafeLoad<uint32_t>(in + 23) >> 14 | SafeLoad<uint32_t>(in + 24) << 18, SafeLoad<uint32_t>(in + 24) };
|
||||
shifts = simd_batch{ 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 0, 3, 0, 0, 0, 7 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 25;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack26_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x3ffffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 26-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 26 | SafeLoad<uint32_t>(in + 1) << 6, SafeLoad<uint32_t>(in + 1) >> 20 | SafeLoad<uint32_t>(in + 2) << 12, SafeLoad<uint32_t>(in + 2) >> 14 | SafeLoad<uint32_t>(in + 3) << 18, SafeLoad<uint32_t>(in + 3) >> 8 | SafeLoad<uint32_t>(in + 4) << 24, SafeLoad<uint32_t>(in + 4), SafeLoad<uint32_t>(in + 4) >> 28 | SafeLoad<uint32_t>(in + 5) << 4, SafeLoad<uint32_t>(in + 5) >> 22 | SafeLoad<uint32_t>(in + 6) << 10, SafeLoad<uint32_t>(in + 6) >> 16 | SafeLoad<uint32_t>(in + 7) << 16, SafeLoad<uint32_t>(in + 7) >> 10 | SafeLoad<uint32_t>(in + 8) << 22, SafeLoad<uint32_t>(in + 8), SafeLoad<uint32_t>(in + 8) >> 30 | SafeLoad<uint32_t>(in + 9) << 2, SafeLoad<uint32_t>(in + 9) >> 24 | SafeLoad<uint32_t>(in + 10) << 8, SafeLoad<uint32_t>(in + 10) >> 18 | SafeLoad<uint32_t>(in + 11) << 14, SafeLoad<uint32_t>(in + 11) >> 12 | SafeLoad<uint32_t>(in + 12) << 20, SafeLoad<uint32_t>(in + 12) };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 4, 0, 0, 0, 0, 6 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 26-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 13), SafeLoad<uint32_t>(in + 13) >> 26 | SafeLoad<uint32_t>(in + 14) << 6, SafeLoad<uint32_t>(in + 14) >> 20 | SafeLoad<uint32_t>(in + 15) << 12, SafeLoad<uint32_t>(in + 15) >> 14 | SafeLoad<uint32_t>(in + 16) << 18, SafeLoad<uint32_t>(in + 16) >> 8 | SafeLoad<uint32_t>(in + 17) << 24, SafeLoad<uint32_t>(in + 17), SafeLoad<uint32_t>(in + 17) >> 28 | SafeLoad<uint32_t>(in + 18) << 4, SafeLoad<uint32_t>(in + 18) >> 22 | SafeLoad<uint32_t>(in + 19) << 10, SafeLoad<uint32_t>(in + 19) >> 16 | SafeLoad<uint32_t>(in + 20) << 16, SafeLoad<uint32_t>(in + 20) >> 10 | SafeLoad<uint32_t>(in + 21) << 22, SafeLoad<uint32_t>(in + 21), SafeLoad<uint32_t>(in + 21) >> 30 | SafeLoad<uint32_t>(in + 22) << 2, SafeLoad<uint32_t>(in + 22) >> 24 | SafeLoad<uint32_t>(in + 23) << 8, SafeLoad<uint32_t>(in + 23) >> 18 | SafeLoad<uint32_t>(in + 24) << 14, SafeLoad<uint32_t>(in + 24) >> 12 | SafeLoad<uint32_t>(in + 25) << 20, SafeLoad<uint32_t>(in + 25) };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 4, 0, 0, 0, 0, 6 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 26;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack27_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x7ffffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 27-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 27 | SafeLoad<uint32_t>(in + 1) << 5, SafeLoad<uint32_t>(in + 1) >> 22 | SafeLoad<uint32_t>(in + 2) << 10, SafeLoad<uint32_t>(in + 2) >> 17 | SafeLoad<uint32_t>(in + 3) << 15, SafeLoad<uint32_t>(in + 3) >> 12 | SafeLoad<uint32_t>(in + 4) << 20, SafeLoad<uint32_t>(in + 4) >> 7 | SafeLoad<uint32_t>(in + 5) << 25, SafeLoad<uint32_t>(in + 5), SafeLoad<uint32_t>(in + 5) >> 29 | SafeLoad<uint32_t>(in + 6) << 3, SafeLoad<uint32_t>(in + 6) >> 24 | SafeLoad<uint32_t>(in + 7) << 8, SafeLoad<uint32_t>(in + 7) >> 19 | SafeLoad<uint32_t>(in + 8) << 13, SafeLoad<uint32_t>(in + 8) >> 14 | SafeLoad<uint32_t>(in + 9) << 18, SafeLoad<uint32_t>(in + 9) >> 9 | SafeLoad<uint32_t>(in + 10) << 23, SafeLoad<uint32_t>(in + 10), SafeLoad<uint32_t>(in + 10) >> 31 | SafeLoad<uint32_t>(in + 11) << 1, SafeLoad<uint32_t>(in + 11) >> 26 | SafeLoad<uint32_t>(in + 12) << 6, SafeLoad<uint32_t>(in + 12) >> 21 | SafeLoad<uint32_t>(in + 13) << 11 };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 4, 0, 0, 0 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 27-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 13) >> 16 | SafeLoad<uint32_t>(in + 14) << 16, SafeLoad<uint32_t>(in + 14) >> 11 | SafeLoad<uint32_t>(in + 15) << 21, SafeLoad<uint32_t>(in + 15) >> 6 | SafeLoad<uint32_t>(in + 16) << 26, SafeLoad<uint32_t>(in + 16), SafeLoad<uint32_t>(in + 16) >> 28 | SafeLoad<uint32_t>(in + 17) << 4, SafeLoad<uint32_t>(in + 17) >> 23 | SafeLoad<uint32_t>(in + 18) << 9, SafeLoad<uint32_t>(in + 18) >> 18 | SafeLoad<uint32_t>(in + 19) << 14, SafeLoad<uint32_t>(in + 19) >> 13 | SafeLoad<uint32_t>(in + 20) << 19, SafeLoad<uint32_t>(in + 20) >> 8 | SafeLoad<uint32_t>(in + 21) << 24, SafeLoad<uint32_t>(in + 21), SafeLoad<uint32_t>(in + 21) >> 30 | SafeLoad<uint32_t>(in + 22) << 2, SafeLoad<uint32_t>(in + 22) >> 25 | SafeLoad<uint32_t>(in + 23) << 7, SafeLoad<uint32_t>(in + 23) >> 20 | SafeLoad<uint32_t>(in + 24) << 12, SafeLoad<uint32_t>(in + 24) >> 15 | SafeLoad<uint32_t>(in + 25) << 17, SafeLoad<uint32_t>(in + 25) >> 10 | SafeLoad<uint32_t>(in + 26) << 22, SafeLoad<uint32_t>(in + 26) };
|
||||
shifts = simd_batch{ 0, 0, 0, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 5 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 27;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack28_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0xfffffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 28-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 28 | SafeLoad<uint32_t>(in + 1) << 4, SafeLoad<uint32_t>(in + 1) >> 24 | SafeLoad<uint32_t>(in + 2) << 8, SafeLoad<uint32_t>(in + 2) >> 20 | SafeLoad<uint32_t>(in + 3) << 12, SafeLoad<uint32_t>(in + 3) >> 16 | SafeLoad<uint32_t>(in + 4) << 16, SafeLoad<uint32_t>(in + 4) >> 12 | SafeLoad<uint32_t>(in + 5) << 20, SafeLoad<uint32_t>(in + 5) >> 8 | SafeLoad<uint32_t>(in + 6) << 24, SafeLoad<uint32_t>(in + 6), SafeLoad<uint32_t>(in + 7), SafeLoad<uint32_t>(in + 7) >> 28 | SafeLoad<uint32_t>(in + 8) << 4, SafeLoad<uint32_t>(in + 8) >> 24 | SafeLoad<uint32_t>(in + 9) << 8, SafeLoad<uint32_t>(in + 9) >> 20 | SafeLoad<uint32_t>(in + 10) << 12, SafeLoad<uint32_t>(in + 10) >> 16 | SafeLoad<uint32_t>(in + 11) << 16, SafeLoad<uint32_t>(in + 11) >> 12 | SafeLoad<uint32_t>(in + 12) << 20, SafeLoad<uint32_t>(in + 12) >> 8 | SafeLoad<uint32_t>(in + 13) << 24, SafeLoad<uint32_t>(in + 13) };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 28-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 14), SafeLoad<uint32_t>(in + 14) >> 28 | SafeLoad<uint32_t>(in + 15) << 4, SafeLoad<uint32_t>(in + 15) >> 24 | SafeLoad<uint32_t>(in + 16) << 8, SafeLoad<uint32_t>(in + 16) >> 20 | SafeLoad<uint32_t>(in + 17) << 12, SafeLoad<uint32_t>(in + 17) >> 16 | SafeLoad<uint32_t>(in + 18) << 16, SafeLoad<uint32_t>(in + 18) >> 12 | SafeLoad<uint32_t>(in + 19) << 20, SafeLoad<uint32_t>(in + 19) >> 8 | SafeLoad<uint32_t>(in + 20) << 24, SafeLoad<uint32_t>(in + 20), SafeLoad<uint32_t>(in + 21), SafeLoad<uint32_t>(in + 21) >> 28 | SafeLoad<uint32_t>(in + 22) << 4, SafeLoad<uint32_t>(in + 22) >> 24 | SafeLoad<uint32_t>(in + 23) << 8, SafeLoad<uint32_t>(in + 23) >> 20 | SafeLoad<uint32_t>(in + 24) << 12, SafeLoad<uint32_t>(in + 24) >> 16 | SafeLoad<uint32_t>(in + 25) << 16, SafeLoad<uint32_t>(in + 25) >> 12 | SafeLoad<uint32_t>(in + 26) << 20, SafeLoad<uint32_t>(in + 26) >> 8 | SafeLoad<uint32_t>(in + 27) << 24, SafeLoad<uint32_t>(in + 27) };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 28;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack29_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x1fffffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 29-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 29 | SafeLoad<uint32_t>(in + 1) << 3, SafeLoad<uint32_t>(in + 1) >> 26 | SafeLoad<uint32_t>(in + 2) << 6, SafeLoad<uint32_t>(in + 2) >> 23 | SafeLoad<uint32_t>(in + 3) << 9, SafeLoad<uint32_t>(in + 3) >> 20 | SafeLoad<uint32_t>(in + 4) << 12, SafeLoad<uint32_t>(in + 4) >> 17 | SafeLoad<uint32_t>(in + 5) << 15, SafeLoad<uint32_t>(in + 5) >> 14 | SafeLoad<uint32_t>(in + 6) << 18, SafeLoad<uint32_t>(in + 6) >> 11 | SafeLoad<uint32_t>(in + 7) << 21, SafeLoad<uint32_t>(in + 7) >> 8 | SafeLoad<uint32_t>(in + 8) << 24, SafeLoad<uint32_t>(in + 8) >> 5 | SafeLoad<uint32_t>(in + 9) << 27, SafeLoad<uint32_t>(in + 9), SafeLoad<uint32_t>(in + 9) >> 31 | SafeLoad<uint32_t>(in + 10) << 1, SafeLoad<uint32_t>(in + 10) >> 28 | SafeLoad<uint32_t>(in + 11) << 4, SafeLoad<uint32_t>(in + 11) >> 25 | SafeLoad<uint32_t>(in + 12) << 7, SafeLoad<uint32_t>(in + 12) >> 22 | SafeLoad<uint32_t>(in + 13) << 10, SafeLoad<uint32_t>(in + 13) >> 19 | SafeLoad<uint32_t>(in + 14) << 13 };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 29-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 14) >> 16 | SafeLoad<uint32_t>(in + 15) << 16, SafeLoad<uint32_t>(in + 15) >> 13 | SafeLoad<uint32_t>(in + 16) << 19, SafeLoad<uint32_t>(in + 16) >> 10 | SafeLoad<uint32_t>(in + 17) << 22, SafeLoad<uint32_t>(in + 17) >> 7 | SafeLoad<uint32_t>(in + 18) << 25, SafeLoad<uint32_t>(in + 18) >> 4 | SafeLoad<uint32_t>(in + 19) << 28, SafeLoad<uint32_t>(in + 19), SafeLoad<uint32_t>(in + 19) >> 30 | SafeLoad<uint32_t>(in + 20) << 2, SafeLoad<uint32_t>(in + 20) >> 27 | SafeLoad<uint32_t>(in + 21) << 5, SafeLoad<uint32_t>(in + 21) >> 24 | SafeLoad<uint32_t>(in + 22) << 8, SafeLoad<uint32_t>(in + 22) >> 21 | SafeLoad<uint32_t>(in + 23) << 11, SafeLoad<uint32_t>(in + 23) >> 18 | SafeLoad<uint32_t>(in + 24) << 14, SafeLoad<uint32_t>(in + 24) >> 15 | SafeLoad<uint32_t>(in + 25) << 17, SafeLoad<uint32_t>(in + 25) >> 12 | SafeLoad<uint32_t>(in + 26) << 20, SafeLoad<uint32_t>(in + 26) >> 9 | SafeLoad<uint32_t>(in + 27) << 23, SafeLoad<uint32_t>(in + 27) >> 6 | SafeLoad<uint32_t>(in + 28) << 26, SafeLoad<uint32_t>(in + 28) };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 29;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack30_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x3fffffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 30-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 30 | SafeLoad<uint32_t>(in + 1) << 2, SafeLoad<uint32_t>(in + 1) >> 28 | SafeLoad<uint32_t>(in + 2) << 4, SafeLoad<uint32_t>(in + 2) >> 26 | SafeLoad<uint32_t>(in + 3) << 6, SafeLoad<uint32_t>(in + 3) >> 24 | SafeLoad<uint32_t>(in + 4) << 8, SafeLoad<uint32_t>(in + 4) >> 22 | SafeLoad<uint32_t>(in + 5) << 10, SafeLoad<uint32_t>(in + 5) >> 20 | SafeLoad<uint32_t>(in + 6) << 12, SafeLoad<uint32_t>(in + 6) >> 18 | SafeLoad<uint32_t>(in + 7) << 14, SafeLoad<uint32_t>(in + 7) >> 16 | SafeLoad<uint32_t>(in + 8) << 16, SafeLoad<uint32_t>(in + 8) >> 14 | SafeLoad<uint32_t>(in + 9) << 18, SafeLoad<uint32_t>(in + 9) >> 12 | SafeLoad<uint32_t>(in + 10) << 20, SafeLoad<uint32_t>(in + 10) >> 10 | SafeLoad<uint32_t>(in + 11) << 22, SafeLoad<uint32_t>(in + 11) >> 8 | SafeLoad<uint32_t>(in + 12) << 24, SafeLoad<uint32_t>(in + 12) >> 6 | SafeLoad<uint32_t>(in + 13) << 26, SafeLoad<uint32_t>(in + 13) >> 4 | SafeLoad<uint32_t>(in + 14) << 28, SafeLoad<uint32_t>(in + 14) };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 30-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 15), SafeLoad<uint32_t>(in + 15) >> 30 | SafeLoad<uint32_t>(in + 16) << 2, SafeLoad<uint32_t>(in + 16) >> 28 | SafeLoad<uint32_t>(in + 17) << 4, SafeLoad<uint32_t>(in + 17) >> 26 | SafeLoad<uint32_t>(in + 18) << 6, SafeLoad<uint32_t>(in + 18) >> 24 | SafeLoad<uint32_t>(in + 19) << 8, SafeLoad<uint32_t>(in + 19) >> 22 | SafeLoad<uint32_t>(in + 20) << 10, SafeLoad<uint32_t>(in + 20) >> 20 | SafeLoad<uint32_t>(in + 21) << 12, SafeLoad<uint32_t>(in + 21) >> 18 | SafeLoad<uint32_t>(in + 22) << 14, SafeLoad<uint32_t>(in + 22) >> 16 | SafeLoad<uint32_t>(in + 23) << 16, SafeLoad<uint32_t>(in + 23) >> 14 | SafeLoad<uint32_t>(in + 24) << 18, SafeLoad<uint32_t>(in + 24) >> 12 | SafeLoad<uint32_t>(in + 25) << 20, SafeLoad<uint32_t>(in + 25) >> 10 | SafeLoad<uint32_t>(in + 26) << 22, SafeLoad<uint32_t>(in + 26) >> 8 | SafeLoad<uint32_t>(in + 27) << 24, SafeLoad<uint32_t>(in + 27) >> 6 | SafeLoad<uint32_t>(in + 28) << 26, SafeLoad<uint32_t>(in + 28) >> 4 | SafeLoad<uint32_t>(in + 29) << 28, SafeLoad<uint32_t>(in + 29) };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 30;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack31_32(const uint32_t* in, uint32_t* out) {
|
||||
uint32_t mask = 0x7fffffff;
|
||||
|
||||
simd_batch masks(mask);
|
||||
simd_batch words, shifts;
|
||||
simd_batch results;
|
||||
|
||||
// extract 31-bit bundles 0 to 15
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 0), SafeLoad<uint32_t>(in + 0) >> 31 | SafeLoad<uint32_t>(in + 1) << 1, SafeLoad<uint32_t>(in + 1) >> 30 | SafeLoad<uint32_t>(in + 2) << 2, SafeLoad<uint32_t>(in + 2) >> 29 | SafeLoad<uint32_t>(in + 3) << 3, SafeLoad<uint32_t>(in + 3) >> 28 | SafeLoad<uint32_t>(in + 4) << 4, SafeLoad<uint32_t>(in + 4) >> 27 | SafeLoad<uint32_t>(in + 5) << 5, SafeLoad<uint32_t>(in + 5) >> 26 | SafeLoad<uint32_t>(in + 6) << 6, SafeLoad<uint32_t>(in + 6) >> 25 | SafeLoad<uint32_t>(in + 7) << 7, SafeLoad<uint32_t>(in + 7) >> 24 | SafeLoad<uint32_t>(in + 8) << 8, SafeLoad<uint32_t>(in + 8) >> 23 | SafeLoad<uint32_t>(in + 9) << 9, SafeLoad<uint32_t>(in + 9) >> 22 | SafeLoad<uint32_t>(in + 10) << 10, SafeLoad<uint32_t>(in + 10) >> 21 | SafeLoad<uint32_t>(in + 11) << 11, SafeLoad<uint32_t>(in + 11) >> 20 | SafeLoad<uint32_t>(in + 12) << 12, SafeLoad<uint32_t>(in + 12) >> 19 | SafeLoad<uint32_t>(in + 13) << 13, SafeLoad<uint32_t>(in + 13) >> 18 | SafeLoad<uint32_t>(in + 14) << 14, SafeLoad<uint32_t>(in + 14) >> 17 | SafeLoad<uint32_t>(in + 15) << 15 };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
// extract 31-bit bundles 16 to 31
|
||||
words = simd_batch{ SafeLoad<uint32_t>(in + 15) >> 16 | SafeLoad<uint32_t>(in + 16) << 16, SafeLoad<uint32_t>(in + 16) >> 15 | SafeLoad<uint32_t>(in + 17) << 17, SafeLoad<uint32_t>(in + 17) >> 14 | SafeLoad<uint32_t>(in + 18) << 18, SafeLoad<uint32_t>(in + 18) >> 13 | SafeLoad<uint32_t>(in + 19) << 19, SafeLoad<uint32_t>(in + 19) >> 12 | SafeLoad<uint32_t>(in + 20) << 20, SafeLoad<uint32_t>(in + 20) >> 11 | SafeLoad<uint32_t>(in + 21) << 21, SafeLoad<uint32_t>(in + 21) >> 10 | SafeLoad<uint32_t>(in + 22) << 22, SafeLoad<uint32_t>(in + 22) >> 9 | SafeLoad<uint32_t>(in + 23) << 23, SafeLoad<uint32_t>(in + 23) >> 8 | SafeLoad<uint32_t>(in + 24) << 24, SafeLoad<uint32_t>(in + 24) >> 7 | SafeLoad<uint32_t>(in + 25) << 25, SafeLoad<uint32_t>(in + 25) >> 6 | SafeLoad<uint32_t>(in + 26) << 26, SafeLoad<uint32_t>(in + 26) >> 5 | SafeLoad<uint32_t>(in + 27) << 27, SafeLoad<uint32_t>(in + 27) >> 4 | SafeLoad<uint32_t>(in + 28) << 28, SafeLoad<uint32_t>(in + 28) >> 3 | SafeLoad<uint32_t>(in + 29) << 29, SafeLoad<uint32_t>(in + 29) >> 2 | SafeLoad<uint32_t>(in + 30) << 30, SafeLoad<uint32_t>(in + 30) };
|
||||
shifts = simd_batch{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
|
||||
results = (words >> shifts) & masks;
|
||||
results.store_unaligned(out);
|
||||
out += 16;
|
||||
|
||||
in += 31;
|
||||
return in;
|
||||
}
|
||||
|
||||
inline static const uint32_t* unpack32_32(const uint32_t* in, uint32_t* out) {
|
||||
memcpy(out, in, 32 * sizeof(*out));
|
||||
in += 32;
|
||||
out += 32;
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
}; // struct UnpackBits512
|
||||
|
||||
} // namespace
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "arrow/type_fwd.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
namespace util {
|
||||
|
||||
/// \brief The sum of bytes in each buffer referenced by the array
|
||||
///
|
||||
/// Note: An array may only reference a portion of a buffer.
|
||||
/// This method will overestimate in this case and return the
|
||||
/// byte size of the entire buffer.
|
||||
/// Note: If a buffer is referenced multiple times then it will
|
||||
/// only be counted once.
|
||||
ARROW_EXPORT int64_t TotalBufferSize(const ArrayData& array_data);
|
||||
/// \brief The sum of bytes in each buffer referenced by the array
|
||||
/// \see TotalBufferSize(const ArrayData& array_data) for details
|
||||
ARROW_EXPORT int64_t TotalBufferSize(const Array& array);
|
||||
/// \brief The sum of bytes in each buffer referenced by the array
|
||||
/// \see TotalBufferSize(const ArrayData& array_data) for details
|
||||
ARROW_EXPORT int64_t TotalBufferSize(const ChunkedArray& chunked_array);
|
||||
/// \brief The sum of bytes in each buffer referenced by the batch
|
||||
/// \see TotalBufferSize(const ArrayData& array_data) for details
|
||||
ARROW_EXPORT int64_t TotalBufferSize(const RecordBatch& record_batch);
|
||||
/// \brief The sum of bytes in each buffer referenced by the table
|
||||
/// \see TotalBufferSize(const ArrayData& array_data) for details
|
||||
ARROW_EXPORT int64_t TotalBufferSize(const Table& table);
|
||||
|
||||
/// \brief Calculate the buffer ranges referenced by the array
|
||||
///
|
||||
/// These ranges will take into account array offsets
|
||||
///
|
||||
/// The ranges may contain duplicates
|
||||
///
|
||||
/// Dictionary arrays will ignore the offset of their containing array
|
||||
///
|
||||
/// The return value will be a struct array corresponding to the schema:
|
||||
/// schema({field("start", uint64()), field("offset", uint64()), field("length",
|
||||
/// uint64()))
|
||||
ARROW_EXPORT Result<std::shared_ptr<Array>> ReferencedRanges(const ArrayData& array_data);
|
||||
|
||||
/// \brief Returns the sum of bytes from all buffer ranges referenced
|
||||
///
|
||||
/// Unlike TotalBufferSize this method will account for array
|
||||
/// offsets.
|
||||
///
|
||||
/// If buffers are shared between arrays then the shared
|
||||
/// portion will be counted multiple times.
|
||||
///
|
||||
/// Dictionary arrays will always be counted in their entirety
|
||||
/// even if the array only references a portion of the dictionary.
|
||||
ARROW_EXPORT Result<int64_t> ReferencedBufferSize(const ArrayData& array_data);
|
||||
/// \brief Returns the sum of bytes from all buffer ranges referenced
|
||||
/// \see ReferencedBufferSize(const ArrayData& array_data) for details
|
||||
ARROW_EXPORT Result<int64_t> ReferencedBufferSize(const Array& array_data);
|
||||
/// \brief Returns the sum of bytes from all buffer ranges referenced
|
||||
/// \see ReferencedBufferSize(const ArrayData& array_data) for details
|
||||
ARROW_EXPORT Result<int64_t> ReferencedBufferSize(const ChunkedArray& array_data);
|
||||
/// \brief Returns the sum of bytes from all buffer ranges referenced
|
||||
/// \see ReferencedBufferSize(const ArrayData& array_data) for details
|
||||
ARROW_EXPORT Result<int64_t> ReferencedBufferSize(const RecordBatch& array_data);
|
||||
/// \brief Returns the sum of bytes from all buffer ranges referenced
|
||||
/// \see ReferencedBufferSize(const ArrayData& array_data) for details
|
||||
ARROW_EXPORT Result<int64_t> ReferencedBufferSize(const Table& array_data);
|
||||
|
||||
} // namespace util
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,626 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arrow/util/simd.h"
|
||||
#include "arrow/util/ubsan.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef ARROW_HAVE_SSE4_2
|
||||
// Enable the SIMD for ByteStreamSplit Encoder/Decoder
|
||||
#define ARROW_HAVE_SIMD_SPLIT
|
||||
#endif // ARROW_HAVE_SSE4_2
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
namespace internal {
|
||||
|
||||
#if defined(ARROW_HAVE_SSE4_2)
|
||||
template <typename T>
|
||||
void ByteStreamSplitDecodeSse2(const uint8_t* data, int64_t num_values, int64_t stride,
|
||||
T* out) {
|
||||
constexpr size_t kNumStreams = sizeof(T);
|
||||
static_assert(kNumStreams == 4U || kNumStreams == 8U, "Invalid number of streams.");
|
||||
constexpr size_t kNumStreamsLog2 = (kNumStreams == 8U ? 3U : 2U);
|
||||
|
||||
const int64_t size = num_values * sizeof(T);
|
||||
constexpr int64_t kBlockSize = sizeof(__m128i) * kNumStreams;
|
||||
const int64_t num_blocks = size / kBlockSize;
|
||||
uint8_t* output_data = reinterpret_cast<uint8_t*>(out);
|
||||
|
||||
// First handle suffix.
|
||||
// This helps catch if the simd-based processing overflows into the suffix
|
||||
// since almost surely a test would fail.
|
||||
const int64_t num_processed_elements = (num_blocks * kBlockSize) / kNumStreams;
|
||||
for (int64_t i = num_processed_elements; i < num_values; ++i) {
|
||||
uint8_t gathered_byte_data[kNumStreams];
|
||||
for (size_t b = 0; b < kNumStreams; ++b) {
|
||||
const size_t byte_index = b * stride + i;
|
||||
gathered_byte_data[b] = data[byte_index];
|
||||
}
|
||||
out[i] = arrow::util::SafeLoadAs<T>(&gathered_byte_data[0]);
|
||||
}
|
||||
|
||||
// The blocks get processed hierarchically using the unpack intrinsics.
|
||||
// Example with four streams:
|
||||
// Stage 1: AAAA BBBB CCCC DDDD
|
||||
// Stage 2: ACAC ACAC BDBD BDBD
|
||||
// Stage 3: ABCD ABCD ABCD ABCD
|
||||
__m128i stage[kNumStreamsLog2 + 1U][kNumStreams];
|
||||
constexpr size_t kNumStreamsHalf = kNumStreams / 2U;
|
||||
|
||||
for (int64_t i = 0; i < num_blocks; ++i) {
|
||||
for (size_t j = 0; j < kNumStreams; ++j) {
|
||||
stage[0][j] = _mm_loadu_si128(
|
||||
reinterpret_cast<const __m128i*>(&data[i * sizeof(__m128i) + j * stride]));
|
||||
}
|
||||
for (size_t step = 0; step < kNumStreamsLog2; ++step) {
|
||||
for (size_t j = 0; j < kNumStreamsHalf; ++j) {
|
||||
stage[step + 1U][j * 2] =
|
||||
_mm_unpacklo_epi8(stage[step][j], stage[step][kNumStreamsHalf + j]);
|
||||
stage[step + 1U][j * 2 + 1U] =
|
||||
_mm_unpackhi_epi8(stage[step][j], stage[step][kNumStreamsHalf + j]);
|
||||
}
|
||||
}
|
||||
for (size_t j = 0; j < kNumStreams; ++j) {
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(
|
||||
&output_data[(i * kNumStreams + j) * sizeof(__m128i)]),
|
||||
stage[kNumStreamsLog2][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ByteStreamSplitEncodeSse2(const uint8_t* raw_values, const size_t num_values,
|
||||
uint8_t* output_buffer_raw) {
|
||||
constexpr size_t kNumStreams = sizeof(T);
|
||||
static_assert(kNumStreams == 4U || kNumStreams == 8U, "Invalid number of streams.");
|
||||
__m128i stage[3][kNumStreams];
|
||||
__m128i final_result[kNumStreams];
|
||||
|
||||
const size_t size = num_values * sizeof(T);
|
||||
constexpr size_t kBlockSize = sizeof(__m128i) * kNumStreams;
|
||||
const size_t num_blocks = size / kBlockSize;
|
||||
const __m128i* raw_values_sse = reinterpret_cast<const __m128i*>(raw_values);
|
||||
__m128i* output_buffer_streams[kNumStreams];
|
||||
for (size_t i = 0; i < kNumStreams; ++i) {
|
||||
output_buffer_streams[i] =
|
||||
reinterpret_cast<__m128i*>(&output_buffer_raw[num_values * i]);
|
||||
}
|
||||
|
||||
// First handle suffix.
|
||||
const size_t num_processed_elements = (num_blocks * kBlockSize) / sizeof(T);
|
||||
for (size_t i = num_processed_elements; i < num_values; ++i) {
|
||||
for (size_t j = 0U; j < kNumStreams; ++j) {
|
||||
const uint8_t byte_in_value = raw_values[i * kNumStreams + j];
|
||||
output_buffer_raw[j * num_values + i] = byte_in_value;
|
||||
}
|
||||
}
|
||||
// The current shuffling algorithm diverges for float and double types but the compiler
|
||||
// should be able to remove the branch since only one path is taken for each template
|
||||
// instantiation.
|
||||
// Example run for floats:
|
||||
// Step 0, copy:
|
||||
// 0: ABCD ABCD ABCD ABCD 1: ABCD ABCD ABCD ABCD ...
|
||||
// Step 1: _mm_unpacklo_epi8 and mm_unpackhi_epi8:
|
||||
// 0: AABB CCDD AABB CCDD 1: AABB CCDD AABB CCDD ...
|
||||
// 0: AAAA BBBB CCCC DDDD 1: AAAA BBBB CCCC DDDD ...
|
||||
// Step 3: __mm_unpacklo_epi8 and _mm_unpackhi_epi8:
|
||||
// 0: AAAA AAAA BBBB BBBB 1: CCCC CCCC DDDD DDDD ...
|
||||
// Step 4: __mm_unpacklo_epi64 and _mm_unpackhi_epi64:
|
||||
// 0: AAAA AAAA AAAA AAAA 1: BBBB BBBB BBBB BBBB ...
|
||||
for (size_t block_index = 0; block_index < num_blocks; ++block_index) {
|
||||
// First copy the data to stage 0.
|
||||
for (size_t i = 0; i < kNumStreams; ++i) {
|
||||
stage[0][i] = _mm_loadu_si128(&raw_values_sse[block_index * kNumStreams + i]);
|
||||
}
|
||||
|
||||
// The shuffling of bytes is performed through the unpack intrinsics.
|
||||
// In my measurements this gives better performance then an implementation
|
||||
// which uses the shuffle intrinsics.
|
||||
for (size_t stage_lvl = 0; stage_lvl < 2U; ++stage_lvl) {
|
||||
for (size_t i = 0; i < kNumStreams / 2U; ++i) {
|
||||
stage[stage_lvl + 1][i * 2] =
|
||||
_mm_unpacklo_epi8(stage[stage_lvl][i * 2], stage[stage_lvl][i * 2 + 1]);
|
||||
stage[stage_lvl + 1][i * 2 + 1] =
|
||||
_mm_unpackhi_epi8(stage[stage_lvl][i * 2], stage[stage_lvl][i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
if (kNumStreams == 8U) {
|
||||
// This is the path for double.
|
||||
__m128i tmp[8];
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
tmp[i * 2] = _mm_unpacklo_epi32(stage[2][i], stage[2][i + 4]);
|
||||
tmp[i * 2 + 1] = _mm_unpackhi_epi32(stage[2][i], stage[2][i + 4]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
final_result[i * 2] = _mm_unpacklo_epi32(tmp[i], tmp[i + 4]);
|
||||
final_result[i * 2 + 1] = _mm_unpackhi_epi32(tmp[i], tmp[i + 4]);
|
||||
}
|
||||
} else {
|
||||
// this is the path for float.
|
||||
__m128i tmp[4];
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
tmp[i * 2] = _mm_unpacklo_epi8(stage[2][i * 2], stage[2][i * 2 + 1]);
|
||||
tmp[i * 2 + 1] = _mm_unpackhi_epi8(stage[2][i * 2], stage[2][i * 2 + 1]);
|
||||
}
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
final_result[i * 2] = _mm_unpacklo_epi64(tmp[i], tmp[i + 2]);
|
||||
final_result[i * 2 + 1] = _mm_unpackhi_epi64(tmp[i], tmp[i + 2]);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < kNumStreams; ++i) {
|
||||
_mm_storeu_si128(&output_buffer_streams[i][block_index], final_result[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ARROW_HAVE_SSE4_2
|
||||
|
||||
#if defined(ARROW_HAVE_AVX2)
|
||||
template <typename T>
|
||||
void ByteStreamSplitDecodeAvx2(const uint8_t* data, int64_t num_values, int64_t stride,
|
||||
T* out) {
|
||||
constexpr size_t kNumStreams = sizeof(T);
|
||||
static_assert(kNumStreams == 4U || kNumStreams == 8U, "Invalid number of streams.");
|
||||
constexpr size_t kNumStreamsLog2 = (kNumStreams == 8U ? 3U : 2U);
|
||||
|
||||
const int64_t size = num_values * sizeof(T);
|
||||
constexpr int64_t kBlockSize = sizeof(__m256i) * kNumStreams;
|
||||
if (size < kBlockSize) // Back to SSE for small size
|
||||
return ByteStreamSplitDecodeSse2(data, num_values, stride, out);
|
||||
const int64_t num_blocks = size / kBlockSize;
|
||||
uint8_t* output_data = reinterpret_cast<uint8_t*>(out);
|
||||
|
||||
// First handle suffix.
|
||||
const int64_t num_processed_elements = (num_blocks * kBlockSize) / kNumStreams;
|
||||
for (int64_t i = num_processed_elements; i < num_values; ++i) {
|
||||
uint8_t gathered_byte_data[kNumStreams];
|
||||
for (size_t b = 0; b < kNumStreams; ++b) {
|
||||
const size_t byte_index = b * stride + i;
|
||||
gathered_byte_data[b] = data[byte_index];
|
||||
}
|
||||
out[i] = arrow::util::SafeLoadAs<T>(&gathered_byte_data[0]);
|
||||
}
|
||||
|
||||
// Processed hierarchically using unpack intrinsics, then permute intrinsics.
|
||||
__m256i stage[kNumStreamsLog2 + 1U][kNumStreams];
|
||||
__m256i final_result[kNumStreams];
|
||||
constexpr size_t kNumStreamsHalf = kNumStreams / 2U;
|
||||
|
||||
for (int64_t i = 0; i < num_blocks; ++i) {
|
||||
for (size_t j = 0; j < kNumStreams; ++j) {
|
||||
stage[0][j] = _mm256_loadu_si256(
|
||||
reinterpret_cast<const __m256i*>(&data[i * sizeof(__m256i) + j * stride]));
|
||||
}
|
||||
|
||||
for (size_t step = 0; step < kNumStreamsLog2; ++step) {
|
||||
for (size_t j = 0; j < kNumStreamsHalf; ++j) {
|
||||
stage[step + 1U][j * 2] =
|
||||
_mm256_unpacklo_epi8(stage[step][j], stage[step][kNumStreamsHalf + j]);
|
||||
stage[step + 1U][j * 2 + 1U] =
|
||||
_mm256_unpackhi_epi8(stage[step][j], stage[step][kNumStreamsHalf + j]);
|
||||
}
|
||||
}
|
||||
|
||||
if (kNumStreams == 8U) {
|
||||
// path for double, 128i index:
|
||||
// {0x00, 0x08}, {0x01, 0x09}, {0x02, 0x0A}, {0x03, 0x0B},
|
||||
// {0x04, 0x0C}, {0x05, 0x0D}, {0x06, 0x0E}, {0x07, 0x0F},
|
||||
final_result[0] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][0],
|
||||
stage[kNumStreamsLog2][1], 0b00100000);
|
||||
final_result[1] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][2],
|
||||
stage[kNumStreamsLog2][3], 0b00100000);
|
||||
final_result[2] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][4],
|
||||
stage[kNumStreamsLog2][5], 0b00100000);
|
||||
final_result[3] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][6],
|
||||
stage[kNumStreamsLog2][7], 0b00100000);
|
||||
final_result[4] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][0],
|
||||
stage[kNumStreamsLog2][1], 0b00110001);
|
||||
final_result[5] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][2],
|
||||
stage[kNumStreamsLog2][3], 0b00110001);
|
||||
final_result[6] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][4],
|
||||
stage[kNumStreamsLog2][5], 0b00110001);
|
||||
final_result[7] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][6],
|
||||
stage[kNumStreamsLog2][7], 0b00110001);
|
||||
} else {
|
||||
// path for float, 128i index:
|
||||
// {0x00, 0x04}, {0x01, 0x05}, {0x02, 0x06}, {0x03, 0x07}
|
||||
final_result[0] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][0],
|
||||
stage[kNumStreamsLog2][1], 0b00100000);
|
||||
final_result[1] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][2],
|
||||
stage[kNumStreamsLog2][3], 0b00100000);
|
||||
final_result[2] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][0],
|
||||
stage[kNumStreamsLog2][1], 0b00110001);
|
||||
final_result[3] = _mm256_permute2x128_si256(stage[kNumStreamsLog2][2],
|
||||
stage[kNumStreamsLog2][3], 0b00110001);
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < kNumStreams; ++j) {
|
||||
_mm256_storeu_si256(reinterpret_cast<__m256i*>(
|
||||
&output_data[(i * kNumStreams + j) * sizeof(__m256i)]),
|
||||
final_result[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ByteStreamSplitEncodeAvx2(const uint8_t* raw_values, const size_t num_values,
|
||||
uint8_t* output_buffer_raw) {
|
||||
constexpr size_t kNumStreams = sizeof(T);
|
||||
static_assert(kNumStreams == 4U || kNumStreams == 8U, "Invalid number of streams.");
|
||||
if (kNumStreams == 8U) // Back to SSE, currently no path for double.
|
||||
return ByteStreamSplitEncodeSse2<T>(raw_values, num_values, output_buffer_raw);
|
||||
|
||||
const size_t size = num_values * sizeof(T);
|
||||
constexpr size_t kBlockSize = sizeof(__m256i) * kNumStreams;
|
||||
if (size < kBlockSize) // Back to SSE for small size
|
||||
return ByteStreamSplitEncodeSse2<T>(raw_values, num_values, output_buffer_raw);
|
||||
const size_t num_blocks = size / kBlockSize;
|
||||
const __m256i* raw_values_simd = reinterpret_cast<const __m256i*>(raw_values);
|
||||
__m256i* output_buffer_streams[kNumStreams];
|
||||
|
||||
for (size_t i = 0; i < kNumStreams; ++i) {
|
||||
output_buffer_streams[i] =
|
||||
reinterpret_cast<__m256i*>(&output_buffer_raw[num_values * i]);
|
||||
}
|
||||
|
||||
// First handle suffix.
|
||||
const size_t num_processed_elements = (num_blocks * kBlockSize) / sizeof(T);
|
||||
for (size_t i = num_processed_elements; i < num_values; ++i) {
|
||||
for (size_t j = 0U; j < kNumStreams; ++j) {
|
||||
const uint8_t byte_in_value = raw_values[i * kNumStreams + j];
|
||||
output_buffer_raw[j * num_values + i] = byte_in_value;
|
||||
}
|
||||
}
|
||||
|
||||
// Path for float.
|
||||
// 1. Processed hierarchically to 32i blcok using the unpack intrinsics.
|
||||
// 2. Pack 128i block using _mm256_permutevar8x32_epi32.
|
||||
// 3. Pack final 256i block with _mm256_permute2x128_si256.
|
||||
constexpr size_t kNumUnpack = 3U;
|
||||
__m256i stage[kNumUnpack + 1][kNumStreams];
|
||||
static const __m256i kPermuteMask =
|
||||
_mm256_set_epi32(0x07, 0x03, 0x06, 0x02, 0x05, 0x01, 0x04, 0x00);
|
||||
__m256i permute[kNumStreams];
|
||||
__m256i final_result[kNumStreams];
|
||||
|
||||
for (size_t block_index = 0; block_index < num_blocks; ++block_index) {
|
||||
for (size_t i = 0; i < kNumStreams; ++i) {
|
||||
stage[0][i] = _mm256_loadu_si256(&raw_values_simd[block_index * kNumStreams + i]);
|
||||
}
|
||||
|
||||
for (size_t stage_lvl = 0; stage_lvl < kNumUnpack; ++stage_lvl) {
|
||||
for (size_t i = 0; i < kNumStreams / 2U; ++i) {
|
||||
stage[stage_lvl + 1][i * 2] =
|
||||
_mm256_unpacklo_epi8(stage[stage_lvl][i * 2], stage[stage_lvl][i * 2 + 1]);
|
||||
stage[stage_lvl + 1][i * 2 + 1] =
|
||||
_mm256_unpackhi_epi8(stage[stage_lvl][i * 2], stage[stage_lvl][i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < kNumStreams; ++i) {
|
||||
permute[i] = _mm256_permutevar8x32_epi32(stage[kNumUnpack][i], kPermuteMask);
|
||||
}
|
||||
|
||||
final_result[0] = _mm256_permute2x128_si256(permute[0], permute[2], 0b00100000);
|
||||
final_result[1] = _mm256_permute2x128_si256(permute[0], permute[2], 0b00110001);
|
||||
final_result[2] = _mm256_permute2x128_si256(permute[1], permute[3], 0b00100000);
|
||||
final_result[3] = _mm256_permute2x128_si256(permute[1], permute[3], 0b00110001);
|
||||
|
||||
for (size_t i = 0; i < kNumStreams; ++i) {
|
||||
_mm256_storeu_si256(&output_buffer_streams[i][block_index], final_result[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ARROW_HAVE_AVX2
|
||||
|
||||
#if defined(ARROW_HAVE_AVX512)
|
||||
template <typename T>
|
||||
void ByteStreamSplitDecodeAvx512(const uint8_t* data, int64_t num_values, int64_t stride,
|
||||
T* out) {
|
||||
constexpr size_t kNumStreams = sizeof(T);
|
||||
static_assert(kNumStreams == 4U || kNumStreams == 8U, "Invalid number of streams.");
|
||||
constexpr size_t kNumStreamsLog2 = (kNumStreams == 8U ? 3U : 2U);
|
||||
|
||||
const int64_t size = num_values * sizeof(T);
|
||||
constexpr int64_t kBlockSize = sizeof(__m512i) * kNumStreams;
|
||||
if (size < kBlockSize) // Back to AVX2 for small size
|
||||
return ByteStreamSplitDecodeAvx2(data, num_values, stride, out);
|
||||
const int64_t num_blocks = size / kBlockSize;
|
||||
uint8_t* output_data = reinterpret_cast<uint8_t*>(out);
|
||||
|
||||
// First handle suffix.
|
||||
const int64_t num_processed_elements = (num_blocks * kBlockSize) / kNumStreams;
|
||||
for (int64_t i = num_processed_elements; i < num_values; ++i) {
|
||||
uint8_t gathered_byte_data[kNumStreams];
|
||||
for (size_t b = 0; b < kNumStreams; ++b) {
|
||||
const size_t byte_index = b * stride + i;
|
||||
gathered_byte_data[b] = data[byte_index];
|
||||
}
|
||||
out[i] = arrow::util::SafeLoadAs<T>(&gathered_byte_data[0]);
|
||||
}
|
||||
|
||||
// Processed hierarchically using the unpack, then two shuffles.
|
||||
__m512i stage[kNumStreamsLog2 + 1U][kNumStreams];
|
||||
__m512i shuffle[kNumStreams];
|
||||
__m512i final_result[kNumStreams];
|
||||
constexpr size_t kNumStreamsHalf = kNumStreams / 2U;
|
||||
|
||||
for (int64_t i = 0; i < num_blocks; ++i) {
|
||||
for (size_t j = 0; j < kNumStreams; ++j) {
|
||||
stage[0][j] = _mm512_loadu_si512(
|
||||
reinterpret_cast<const __m512i*>(&data[i * sizeof(__m512i) + j * stride]));
|
||||
}
|
||||
|
||||
for (size_t step = 0; step < kNumStreamsLog2; ++step) {
|
||||
for (size_t j = 0; j < kNumStreamsHalf; ++j) {
|
||||
stage[step + 1U][j * 2] =
|
||||
_mm512_unpacklo_epi8(stage[step][j], stage[step][kNumStreamsHalf + j]);
|
||||
stage[step + 1U][j * 2 + 1U] =
|
||||
_mm512_unpackhi_epi8(stage[step][j], stage[step][kNumStreamsHalf + j]);
|
||||
}
|
||||
}
|
||||
|
||||
if (kNumStreams == 8U) {
|
||||
// path for double, 128i index:
|
||||
// {0x00, 0x04, 0x08, 0x0C}, {0x10, 0x14, 0x18, 0x1C},
|
||||
// {0x01, 0x05, 0x09, 0x0D}, {0x11, 0x15, 0x19, 0x1D},
|
||||
// {0x02, 0x06, 0x0A, 0x0E}, {0x12, 0x16, 0x1A, 0x1E},
|
||||
// {0x03, 0x07, 0x0B, 0x0F}, {0x13, 0x17, 0x1B, 0x1F},
|
||||
shuffle[0] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][0],
|
||||
stage[kNumStreamsLog2][1], 0b01000100);
|
||||
shuffle[1] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][2],
|
||||
stage[kNumStreamsLog2][3], 0b01000100);
|
||||
shuffle[2] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][4],
|
||||
stage[kNumStreamsLog2][5], 0b01000100);
|
||||
shuffle[3] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][6],
|
||||
stage[kNumStreamsLog2][7], 0b01000100);
|
||||
shuffle[4] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][0],
|
||||
stage[kNumStreamsLog2][1], 0b11101110);
|
||||
shuffle[5] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][2],
|
||||
stage[kNumStreamsLog2][3], 0b11101110);
|
||||
shuffle[6] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][4],
|
||||
stage[kNumStreamsLog2][5], 0b11101110);
|
||||
shuffle[7] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][6],
|
||||
stage[kNumStreamsLog2][7], 0b11101110);
|
||||
|
||||
final_result[0] = _mm512_shuffle_i32x4(shuffle[0], shuffle[1], 0b10001000);
|
||||
final_result[1] = _mm512_shuffle_i32x4(shuffle[2], shuffle[3], 0b10001000);
|
||||
final_result[2] = _mm512_shuffle_i32x4(shuffle[0], shuffle[1], 0b11011101);
|
||||
final_result[3] = _mm512_shuffle_i32x4(shuffle[2], shuffle[3], 0b11011101);
|
||||
final_result[4] = _mm512_shuffle_i32x4(shuffle[4], shuffle[5], 0b10001000);
|
||||
final_result[5] = _mm512_shuffle_i32x4(shuffle[6], shuffle[7], 0b10001000);
|
||||
final_result[6] = _mm512_shuffle_i32x4(shuffle[4], shuffle[5], 0b11011101);
|
||||
final_result[7] = _mm512_shuffle_i32x4(shuffle[6], shuffle[7], 0b11011101);
|
||||
} else {
|
||||
// path for float, 128i index:
|
||||
// {0x00, 0x04, 0x08, 0x0C}, {0x01, 0x05, 0x09, 0x0D}
|
||||
// {0x02, 0x06, 0x0A, 0x0E}, {0x03, 0x07, 0x0B, 0x0F},
|
||||
shuffle[0] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][0],
|
||||
stage[kNumStreamsLog2][1], 0b01000100);
|
||||
shuffle[1] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][2],
|
||||
stage[kNumStreamsLog2][3], 0b01000100);
|
||||
shuffle[2] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][0],
|
||||
stage[kNumStreamsLog2][1], 0b11101110);
|
||||
shuffle[3] = _mm512_shuffle_i32x4(stage[kNumStreamsLog2][2],
|
||||
stage[kNumStreamsLog2][3], 0b11101110);
|
||||
|
||||
final_result[0] = _mm512_shuffle_i32x4(shuffle[0], shuffle[1], 0b10001000);
|
||||
final_result[1] = _mm512_shuffle_i32x4(shuffle[0], shuffle[1], 0b11011101);
|
||||
final_result[2] = _mm512_shuffle_i32x4(shuffle[2], shuffle[3], 0b10001000);
|
||||
final_result[3] = _mm512_shuffle_i32x4(shuffle[2], shuffle[3], 0b11011101);
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < kNumStreams; ++j) {
|
||||
_mm512_storeu_si512(reinterpret_cast<__m512i*>(
|
||||
&output_data[(i * kNumStreams + j) * sizeof(__m512i)]),
|
||||
final_result[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ByteStreamSplitEncodeAvx512(const uint8_t* raw_values, const size_t num_values,
|
||||
uint8_t* output_buffer_raw) {
|
||||
constexpr size_t kNumStreams = sizeof(T);
|
||||
static_assert(kNumStreams == 4U || kNumStreams == 8U, "Invalid number of streams.");
|
||||
const size_t size = num_values * sizeof(T);
|
||||
constexpr size_t kBlockSize = sizeof(__m512i) * kNumStreams;
|
||||
if (size < kBlockSize) // Back to AVX2 for small size
|
||||
return ByteStreamSplitEncodeAvx2<T>(raw_values, num_values, output_buffer_raw);
|
||||
|
||||
const size_t num_blocks = size / kBlockSize;
|
||||
const __m512i* raw_values_simd = reinterpret_cast<const __m512i*>(raw_values);
|
||||
__m512i* output_buffer_streams[kNumStreams];
|
||||
for (size_t i = 0; i < kNumStreams; ++i) {
|
||||
output_buffer_streams[i] =
|
||||
reinterpret_cast<__m512i*>(&output_buffer_raw[num_values * i]);
|
||||
}
|
||||
|
||||
// First handle suffix.
|
||||
const size_t num_processed_elements = (num_blocks * kBlockSize) / sizeof(T);
|
||||
for (size_t i = num_processed_elements; i < num_values; ++i) {
|
||||
for (size_t j = 0U; j < kNumStreams; ++j) {
|
||||
const uint8_t byte_in_value = raw_values[i * kNumStreams + j];
|
||||
output_buffer_raw[j * num_values + i] = byte_in_value;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t KNumUnpack = (kNumStreams == 8U) ? 2U : 3U;
|
||||
__m512i final_result[kNumStreams];
|
||||
__m512i unpack[KNumUnpack + 1][kNumStreams];
|
||||
__m512i permutex[kNumStreams];
|
||||
__m512i permutex_mask;
|
||||
if (kNumStreams == 8U) {
|
||||
// use _mm512_set_epi32, no _mm512_set_epi16 for some old gcc version.
|
||||
permutex_mask = _mm512_set_epi32(0x001F0017, 0x000F0007, 0x001E0016, 0x000E0006,
|
||||
0x001D0015, 0x000D0005, 0x001C0014, 0x000C0004,
|
||||
0x001B0013, 0x000B0003, 0x001A0012, 0x000A0002,
|
||||
0x00190011, 0x00090001, 0x00180010, 0x00080000);
|
||||
} else {
|
||||
permutex_mask = _mm512_set_epi32(0x0F, 0x0B, 0x07, 0x03, 0x0E, 0x0A, 0x06, 0x02, 0x0D,
|
||||
0x09, 0x05, 0x01, 0x0C, 0x08, 0x04, 0x00);
|
||||
}
|
||||
|
||||
for (size_t block_index = 0; block_index < num_blocks; ++block_index) {
|
||||
for (size_t i = 0; i < kNumStreams; ++i) {
|
||||
unpack[0][i] = _mm512_loadu_si512(&raw_values_simd[block_index * kNumStreams + i]);
|
||||
}
|
||||
|
||||
for (size_t unpack_lvl = 0; unpack_lvl < KNumUnpack; ++unpack_lvl) {
|
||||
for (size_t i = 0; i < kNumStreams / 2U; ++i) {
|
||||
unpack[unpack_lvl + 1][i * 2] = _mm512_unpacklo_epi8(
|
||||
unpack[unpack_lvl][i * 2], unpack[unpack_lvl][i * 2 + 1]);
|
||||
unpack[unpack_lvl + 1][i * 2 + 1] = _mm512_unpackhi_epi8(
|
||||
unpack[unpack_lvl][i * 2], unpack[unpack_lvl][i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (kNumStreams == 8U) {
|
||||
// path for double
|
||||
// 1. unpack to epi16 block
|
||||
// 2. permutexvar_epi16 to 128i block
|
||||
// 3. shuffle 128i to final 512i target, index:
|
||||
// {0x00, 0x04, 0x08, 0x0C}, {0x10, 0x14, 0x18, 0x1C},
|
||||
// {0x01, 0x05, 0x09, 0x0D}, {0x11, 0x15, 0x19, 0x1D},
|
||||
// {0x02, 0x06, 0x0A, 0x0E}, {0x12, 0x16, 0x1A, 0x1E},
|
||||
// {0x03, 0x07, 0x0B, 0x0F}, {0x13, 0x17, 0x1B, 0x1F},
|
||||
for (size_t i = 0; i < kNumStreams; ++i)
|
||||
permutex[i] = _mm512_permutexvar_epi16(permutex_mask, unpack[KNumUnpack][i]);
|
||||
|
||||
__m512i shuffle[kNumStreams];
|
||||
shuffle[0] = _mm512_shuffle_i32x4(permutex[0], permutex[2], 0b01000100);
|
||||
shuffle[1] = _mm512_shuffle_i32x4(permutex[4], permutex[6], 0b01000100);
|
||||
shuffle[2] = _mm512_shuffle_i32x4(permutex[0], permutex[2], 0b11101110);
|
||||
shuffle[3] = _mm512_shuffle_i32x4(permutex[4], permutex[6], 0b11101110);
|
||||
shuffle[4] = _mm512_shuffle_i32x4(permutex[1], permutex[3], 0b01000100);
|
||||
shuffle[5] = _mm512_shuffle_i32x4(permutex[5], permutex[7], 0b01000100);
|
||||
shuffle[6] = _mm512_shuffle_i32x4(permutex[1], permutex[3], 0b11101110);
|
||||
shuffle[7] = _mm512_shuffle_i32x4(permutex[5], permutex[7], 0b11101110);
|
||||
|
||||
final_result[0] = _mm512_shuffle_i32x4(shuffle[0], shuffle[1], 0b10001000);
|
||||
final_result[1] = _mm512_shuffle_i32x4(shuffle[0], shuffle[1], 0b11011101);
|
||||
final_result[2] = _mm512_shuffle_i32x4(shuffle[2], shuffle[3], 0b10001000);
|
||||
final_result[3] = _mm512_shuffle_i32x4(shuffle[2], shuffle[3], 0b11011101);
|
||||
final_result[4] = _mm512_shuffle_i32x4(shuffle[4], shuffle[5], 0b10001000);
|
||||
final_result[5] = _mm512_shuffle_i32x4(shuffle[4], shuffle[5], 0b11011101);
|
||||
final_result[6] = _mm512_shuffle_i32x4(shuffle[6], shuffle[7], 0b10001000);
|
||||
final_result[7] = _mm512_shuffle_i32x4(shuffle[6], shuffle[7], 0b11011101);
|
||||
} else {
|
||||
// Path for float.
|
||||
// 1. Processed hierarchically to 32i blcok using the unpack intrinsics.
|
||||
// 2. Pack 128i block using _mm256_permutevar8x32_epi32.
|
||||
// 3. Pack final 256i block with _mm256_permute2x128_si256.
|
||||
for (size_t i = 0; i < kNumStreams; ++i)
|
||||
permutex[i] = _mm512_permutexvar_epi32(permutex_mask, unpack[KNumUnpack][i]);
|
||||
|
||||
final_result[0] = _mm512_shuffle_i32x4(permutex[0], permutex[2], 0b01000100);
|
||||
final_result[1] = _mm512_shuffle_i32x4(permutex[0], permutex[2], 0b11101110);
|
||||
final_result[2] = _mm512_shuffle_i32x4(permutex[1], permutex[3], 0b01000100);
|
||||
final_result[3] = _mm512_shuffle_i32x4(permutex[1], permutex[3], 0b11101110);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < kNumStreams; ++i) {
|
||||
_mm512_storeu_si512(&output_buffer_streams[i][block_index], final_result[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ARROW_HAVE_AVX512
|
||||
|
||||
#if defined(ARROW_HAVE_SIMD_SPLIT)
|
||||
template <typename T>
|
||||
void inline ByteStreamSplitDecodeSimd(const uint8_t* data, int64_t num_values,
|
||||
int64_t stride, T* out) {
|
||||
#if defined(ARROW_HAVE_AVX512)
|
||||
return ByteStreamSplitDecodeAvx512(data, num_values, stride, out);
|
||||
#elif defined(ARROW_HAVE_AVX2)
|
||||
return ByteStreamSplitDecodeAvx2(data, num_values, stride, out);
|
||||
#elif defined(ARROW_HAVE_SSE4_2)
|
||||
return ByteStreamSplitDecodeSse2(data, num_values, stride, out);
|
||||
#else
|
||||
#error "ByteStreamSplitDecodeSimd not implemented"
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void inline ByteStreamSplitEncodeSimd(const uint8_t* raw_values, const size_t num_values,
|
||||
uint8_t* output_buffer_raw) {
|
||||
#if defined(ARROW_HAVE_AVX512)
|
||||
return ByteStreamSplitEncodeAvx512<T>(raw_values, num_values, output_buffer_raw);
|
||||
#elif defined(ARROW_HAVE_AVX2)
|
||||
return ByteStreamSplitEncodeAvx2<T>(raw_values, num_values, output_buffer_raw);
|
||||
#elif defined(ARROW_HAVE_SSE4_2)
|
||||
return ByteStreamSplitEncodeSse2<T>(raw_values, num_values, output_buffer_raw);
|
||||
#else
|
||||
#error "ByteStreamSplitEncodeSimd not implemented"
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
void ByteStreamSplitEncodeScalar(const uint8_t* raw_values, const size_t num_values,
|
||||
uint8_t* output_buffer_raw) {
|
||||
constexpr size_t kNumStreams = sizeof(T);
|
||||
for (size_t i = 0U; i < num_values; ++i) {
|
||||
for (size_t j = 0U; j < kNumStreams; ++j) {
|
||||
const uint8_t byte_in_value = raw_values[i * kNumStreams + j];
|
||||
output_buffer_raw[j * num_values + i] = byte_in_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ByteStreamSplitDecodeScalar(const uint8_t* data, int64_t num_values, int64_t stride,
|
||||
T* out) {
|
||||
constexpr size_t kNumStreams = sizeof(T);
|
||||
auto output_buffer_raw = reinterpret_cast<uint8_t*>(out);
|
||||
|
||||
for (int64_t i = 0; i < num_values; ++i) {
|
||||
for (size_t b = 0; b < kNumStreams; ++b) {
|
||||
const size_t byte_index = b * stride + i;
|
||||
output_buffer_raw[i * kNumStreams + b] = data[byte_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void inline ByteStreamSplitEncode(const uint8_t* raw_values, const size_t num_values,
|
||||
uint8_t* output_buffer_raw) {
|
||||
#if defined(ARROW_HAVE_SIMD_SPLIT)
|
||||
return ByteStreamSplitEncodeSimd<T>(raw_values, num_values, output_buffer_raw);
|
||||
#else
|
||||
return ByteStreamSplitEncodeScalar<T>(raw_values, num_values, output_buffer_raw);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void inline ByteStreamSplitDecode(const uint8_t* data, int64_t num_values, int64_t stride,
|
||||
T* out) {
|
||||
#if defined(ARROW_HAVE_SIMD_SPLIT)
|
||||
return ByteStreamSplitDecodeSimd(data, num_values, stride, out);
|
||||
#else
|
||||
return ByteStreamSplitDecodeScalar(data, num_values, stride, out);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
using bytes_view = std::basic_string_view<uint8_t>;
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,118 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/type_fwd.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
class StopToken;
|
||||
|
||||
struct StopSourceImpl;
|
||||
|
||||
/// EXPERIMENTAL
|
||||
class ARROW_EXPORT StopSource {
|
||||
public:
|
||||
StopSource();
|
||||
~StopSource();
|
||||
|
||||
// Consumer API (the side that stops)
|
||||
void RequestStop();
|
||||
void RequestStop(Status error);
|
||||
// Async-signal-safe. TODO Deprecate this?
|
||||
void RequestStopFromSignal(int signum);
|
||||
|
||||
StopToken token();
|
||||
|
||||
// For internal use only
|
||||
void Reset();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<StopSourceImpl> impl_;
|
||||
};
|
||||
|
||||
/// EXPERIMENTAL
|
||||
class ARROW_EXPORT StopToken {
|
||||
public:
|
||||
// Public for Cython
|
||||
StopToken() {}
|
||||
|
||||
explicit StopToken(std::shared_ptr<StopSourceImpl> impl) : impl_(std::move(impl)) {}
|
||||
|
||||
// A trivial token that never propagates any stop request
|
||||
static StopToken Unstoppable() { return StopToken(); }
|
||||
|
||||
/// \brief Check if the stop source has been cancelled.
|
||||
///
|
||||
/// Producers should call this method, whenever convenient, to check and
|
||||
/// see if they should stop producing early (i.e. have been cancelled).
|
||||
/// Failure to call this method often enough will lead to an unresponsive
|
||||
/// cancellation.
|
||||
///
|
||||
/// This is part of the producer API (the side that gets asked to stop)
|
||||
/// This method is thread-safe
|
||||
///
|
||||
/// \return An OK status if the stop source has not been cancelled or a
|
||||
/// cancel error if the source has been cancelled.
|
||||
Status Poll() const;
|
||||
bool IsStopRequested() const;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<StopSourceImpl> impl_;
|
||||
};
|
||||
|
||||
/// EXPERIMENTAL: Set a global StopSource that can receive signals
|
||||
///
|
||||
/// The only allowed order of calls is the following:
|
||||
/// - SetSignalStopSource()
|
||||
/// - any number of pairs of (RegisterCancellingSignalHandler,
|
||||
/// UnregisterCancellingSignalHandler) calls
|
||||
/// - ResetSignalStopSource()
|
||||
///
|
||||
/// Beware that these settings are process-wide. Typically, only one
|
||||
/// thread should call these APIs, even in a multithreaded setting.
|
||||
ARROW_EXPORT
|
||||
Result<StopSource*> SetSignalStopSource();
|
||||
|
||||
/// EXPERIMENTAL: Reset the global signal-receiving StopSource
|
||||
///
|
||||
/// This will invalidate the pointer returned by SetSignalStopSource.
|
||||
ARROW_EXPORT
|
||||
void ResetSignalStopSource();
|
||||
|
||||
/// EXPERIMENTAL: Register signal handler triggering the signal-receiving StopSource
|
||||
///
|
||||
/// Note that those handlers are automatically un-registered in a fork()ed process,
|
||||
/// therefore the child process will need to call RegisterCancellingSignalHandler()
|
||||
/// if desired.
|
||||
ARROW_EXPORT
|
||||
Status RegisterCancellingSignalHandler(const std::vector<int>& signals);
|
||||
|
||||
/// EXPERIMENTAL: Unregister signal handler set up by RegisterCancellingSignalHandler
|
||||
ARROW_EXPORT
|
||||
void UnregisterCancellingSignalHandler();
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
template <typename OutputType, typename InputType>
|
||||
inline OutputType checked_cast(InputType&& value) {
|
||||
static_assert(std::is_class<typename std::remove_pointer<
|
||||
typename std::remove_reference<InputType>::type>::type>::value,
|
||||
"checked_cast input type must be a class");
|
||||
static_assert(std::is_class<typename std::remove_pointer<
|
||||
typename std::remove_reference<OutputType>::type>::type>::value,
|
||||
"checked_cast output type must be a class");
|
||||
#ifdef NDEBUG
|
||||
return static_cast<OutputType>(value);
|
||||
#else
|
||||
return dynamic_cast<OutputType>(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
std::shared_ptr<T> checked_pointer_cast(std::shared_ptr<U> r) noexcept {
|
||||
#ifdef NDEBUG
|
||||
return std::static_pointer_cast<T>(std::move(r));
|
||||
#else
|
||||
return std::dynamic_pointer_cast<T>(std::move(r));
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
std::unique_ptr<T> checked_pointer_cast(std::unique_ptr<U> r) noexcept {
|
||||
#ifdef NDEBUG
|
||||
return std::unique_ptr<T>(static_cast<T*>(r.release()));
|
||||
#else
|
||||
return std::unique_ptr<T>(dynamic_cast<T*>(r.release()));
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/util/macros.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
/// CRTP helper for declaring equality comparison. Defines operator== and operator!=
|
||||
template <typename T>
|
||||
class EqualityComparable {
|
||||
public:
|
||||
~EqualityComparable() {
|
||||
static_assert(
|
||||
std::is_same<decltype(std::declval<const T>().Equals(std::declval<const T>())),
|
||||
bool>::value,
|
||||
"EqualityComparable depends on the method T::Equals(const T&) const");
|
||||
}
|
||||
|
||||
template <typename... Extra>
|
||||
bool Equals(const std::shared_ptr<T>& other, Extra&&... extra) const {
|
||||
if (other == NULLPTR) {
|
||||
return false;
|
||||
}
|
||||
return cast().Equals(*other, std::forward<Extra>(extra)...);
|
||||
}
|
||||
|
||||
struct PtrsEqual {
|
||||
bool operator()(const std::shared_ptr<T>& l, const std::shared_ptr<T>& r) const {
|
||||
return l->Equals(r);
|
||||
}
|
||||
};
|
||||
|
||||
bool operator==(const T& other) const { return cast().Equals(other); }
|
||||
bool operator!=(const T& other) const { return !(cast() == other); }
|
||||
|
||||
private:
|
||||
const T& cast() const { return static_cast<const T&>(*this); }
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,202 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/util/type_fwd.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
constexpr int kUseDefaultCompressionLevel = std::numeric_limits<int>::min();
|
||||
|
||||
/// \brief Streaming compressor interface
|
||||
///
|
||||
class ARROW_EXPORT Compressor {
|
||||
public:
|
||||
virtual ~Compressor() = default;
|
||||
|
||||
struct CompressResult {
|
||||
int64_t bytes_read;
|
||||
int64_t bytes_written;
|
||||
};
|
||||
struct FlushResult {
|
||||
int64_t bytes_written;
|
||||
bool should_retry;
|
||||
};
|
||||
struct EndResult {
|
||||
int64_t bytes_written;
|
||||
bool should_retry;
|
||||
};
|
||||
|
||||
/// \brief Compress some input.
|
||||
///
|
||||
/// If bytes_read is 0 on return, then a larger output buffer should be supplied.
|
||||
virtual Result<CompressResult> Compress(int64_t input_len, const uint8_t* input,
|
||||
int64_t output_len, uint8_t* output) = 0;
|
||||
|
||||
/// \brief Flush part of the compressed output.
|
||||
///
|
||||
/// If should_retry is true on return, Flush() should be called again
|
||||
/// with a larger buffer.
|
||||
virtual Result<FlushResult> Flush(int64_t output_len, uint8_t* output) = 0;
|
||||
|
||||
/// \brief End compressing, doing whatever is necessary to end the stream.
|
||||
///
|
||||
/// If should_retry is true on return, End() should be called again
|
||||
/// with a larger buffer. Otherwise, the Compressor should not be used anymore.
|
||||
///
|
||||
/// End() implies Flush().
|
||||
virtual Result<EndResult> End(int64_t output_len, uint8_t* output) = 0;
|
||||
|
||||
// XXX add methods for buffer size heuristics?
|
||||
};
|
||||
|
||||
/// \brief Streaming decompressor interface
|
||||
///
|
||||
class ARROW_EXPORT Decompressor {
|
||||
public:
|
||||
virtual ~Decompressor() = default;
|
||||
|
||||
struct DecompressResult {
|
||||
// XXX is need_more_output necessary? (Brotli?)
|
||||
int64_t bytes_read;
|
||||
int64_t bytes_written;
|
||||
bool need_more_output;
|
||||
};
|
||||
|
||||
/// \brief Decompress some input.
|
||||
///
|
||||
/// If need_more_output is true on return, a larger output buffer needs
|
||||
/// to be supplied.
|
||||
virtual Result<DecompressResult> Decompress(int64_t input_len, const uint8_t* input,
|
||||
int64_t output_len, uint8_t* output) = 0;
|
||||
|
||||
/// \brief Return whether the compressed stream is finished.
|
||||
///
|
||||
/// This is a heuristic. If true is returned, then it is guaranteed
|
||||
/// that the stream is finished. If false is returned, however, it may
|
||||
/// simply be that the underlying library isn't able to provide the information.
|
||||
virtual bool IsFinished() = 0;
|
||||
|
||||
/// \brief Reinitialize decompressor, making it ready for a new compressed stream.
|
||||
virtual Status Reset() = 0;
|
||||
|
||||
// XXX add methods for buffer size heuristics?
|
||||
};
|
||||
|
||||
/// \brief Compression codec
|
||||
class ARROW_EXPORT Codec {
|
||||
public:
|
||||
virtual ~Codec() = default;
|
||||
|
||||
/// \brief Return special value to indicate that a codec implementation
|
||||
/// should use its default compression level
|
||||
static int UseDefaultCompressionLevel();
|
||||
|
||||
/// \brief Return a string name for compression type
|
||||
static const std::string& GetCodecAsString(Compression::type t);
|
||||
|
||||
/// \brief Return compression type for name (all lower case)
|
||||
static Result<Compression::type> GetCompressionType(const std::string& name);
|
||||
|
||||
/// \brief Create a codec for the given compression algorithm
|
||||
static Result<std::unique_ptr<Codec>> Create(
|
||||
Compression::type codec, int compression_level = kUseDefaultCompressionLevel);
|
||||
|
||||
/// \brief Return true if support for indicated codec has been enabled
|
||||
static bool IsAvailable(Compression::type codec);
|
||||
|
||||
/// \brief Return true if indicated codec supports setting a compression level
|
||||
static bool SupportsCompressionLevel(Compression::type codec);
|
||||
|
||||
/// \brief Return the smallest supported compression level for the codec
|
||||
/// Note: This function creates a temporary Codec instance
|
||||
static Result<int> MinimumCompressionLevel(Compression::type codec);
|
||||
|
||||
/// \brief Return the largest supported compression level for the codec
|
||||
/// Note: This function creates a temporary Codec instance
|
||||
static Result<int> MaximumCompressionLevel(Compression::type codec);
|
||||
|
||||
/// \brief Return the default compression level
|
||||
/// Note: This function creates a temporary Codec instance
|
||||
static Result<int> DefaultCompressionLevel(Compression::type codec);
|
||||
|
||||
/// \brief Return the smallest supported compression level
|
||||
virtual int minimum_compression_level() const = 0;
|
||||
|
||||
/// \brief Return the largest supported compression level
|
||||
virtual int maximum_compression_level() const = 0;
|
||||
|
||||
/// \brief Return the default compression level
|
||||
virtual int default_compression_level() const = 0;
|
||||
|
||||
/// \brief One-shot decompression function
|
||||
///
|
||||
/// output_buffer_len must be correct and therefore be obtained in advance.
|
||||
/// The actual decompressed length is returned.
|
||||
///
|
||||
/// \note One-shot decompression is not always compatible with streaming
|
||||
/// compression. Depending on the codec (e.g. LZ4), different formats may
|
||||
/// be used.
|
||||
virtual Result<int64_t> Decompress(int64_t input_len, const uint8_t* input,
|
||||
int64_t output_buffer_len,
|
||||
uint8_t* output_buffer) = 0;
|
||||
|
||||
/// \brief One-shot compression function
|
||||
///
|
||||
/// output_buffer_len must first have been computed using MaxCompressedLen().
|
||||
/// The actual compressed length is returned.
|
||||
///
|
||||
/// \note One-shot compression is not always compatible with streaming
|
||||
/// decompression. Depending on the codec (e.g. LZ4), different formats may
|
||||
/// be used.
|
||||
virtual Result<int64_t> Compress(int64_t input_len, const uint8_t* input,
|
||||
int64_t output_buffer_len, uint8_t* output_buffer) = 0;
|
||||
|
||||
virtual int64_t MaxCompressedLen(int64_t input_len, const uint8_t* input) = 0;
|
||||
|
||||
/// \brief Create a streaming compressor instance
|
||||
virtual Result<std::shared_ptr<Compressor>> MakeCompressor() = 0;
|
||||
|
||||
/// \brief Create a streaming compressor instance
|
||||
virtual Result<std::shared_ptr<Decompressor>> MakeDecompressor() = 0;
|
||||
|
||||
/// \brief This Codec's compression type
|
||||
virtual Compression::type compression_type() const = 0;
|
||||
|
||||
/// \brief The name of this Codec's compression type
|
||||
const std::string& name() const { return GetCodecAsString(compression_type()); }
|
||||
|
||||
/// \brief This Codec's compression level, if applicable
|
||||
virtual int compression_level() const { return UseDefaultCompressionLevel(); }
|
||||
|
||||
private:
|
||||
/// \brief Initializes the codec's resources.
|
||||
virtual Status Init();
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/util/mutex.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
template <typename K, typename V>
|
||||
class ConcurrentMap {
|
||||
public:
|
||||
void Insert(const K& key, const V& value) {
|
||||
auto lock = mutex_.Lock();
|
||||
map_.insert({key, value});
|
||||
}
|
||||
|
||||
template <typename ValueFunc>
|
||||
V GetOrInsert(const K& key, ValueFunc&& compute_value_func) {
|
||||
auto lock = mutex_.Lock();
|
||||
auto it = map_.find(key);
|
||||
if (it == map_.end()) {
|
||||
auto pair = map_.emplace(key, compute_value_func());
|
||||
it = pair.first;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void Erase(const K& key) {
|
||||
auto lock = mutex_.Lock();
|
||||
map_.erase(key);
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
auto lock = mutex_.Lock();
|
||||
map_.clear();
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
auto lock = mutex_.Lock();
|
||||
return map_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<K, V> map_;
|
||||
mutable arrow::util::Mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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.
|
||||
|
||||
#define ARROW_VERSION_MAJOR 11
|
||||
#define ARROW_VERSION_MINOR 0
|
||||
#define ARROW_VERSION_PATCH 0
|
||||
#define ARROW_VERSION ((ARROW_VERSION_MAJOR * 1000) + ARROW_VERSION_MINOR) * 1000 + ARROW_VERSION_PATCH
|
||||
|
||||
#define ARROW_VERSION_STRING "11.0.0"
|
||||
|
||||
#define ARROW_SO_VERSION "1100"
|
||||
#define ARROW_FULL_SO_VERSION "1100.0.0"
|
||||
|
||||
#define ARROW_CXX_COMPILER_ID "AppleClang"
|
||||
#define ARROW_CXX_COMPILER_VERSION "14.0.0.14000029"
|
||||
#define ARROW_CXX_COMPILER_FLAGS " -Qunused-arguments -fcolor-diagnostics"
|
||||
|
||||
#define ARROW_BUILD_TYPE "RELEASE"
|
||||
|
||||
#define ARROW_GIT_ID "f10f5cfd1376fb0e602334588b3f3624d41dee7d"
|
||||
#define ARROW_GIT_DESCRIPTION ""
|
||||
|
||||
#define ARROW_PACKAGE_KIND "python-wheel-macos"
|
||||
|
||||
#define ARROW_COMPUTE
|
||||
#define ARROW_CSV
|
||||
/* #undef ARROW_CUDA */
|
||||
#define ARROW_DATASET
|
||||
#define ARROW_FILESYSTEM
|
||||
#define ARROW_FLIGHT
|
||||
/* #undef ARROW_FLIGHT_SQL */
|
||||
#define ARROW_IPC
|
||||
#define ARROW_JEMALLOC
|
||||
#define ARROW_JEMALLOC_VENDORED
|
||||
#define ARROW_JSON
|
||||
#define ARROW_ORC
|
||||
#define ARROW_PARQUET
|
||||
#define ARROW_SUBSTRAIT
|
||||
|
||||
#define ARROW_GCS
|
||||
#define ARROW_S3
|
||||
#define ARROW_USE_NATIVE_INT128
|
||||
/* #undef ARROW_WITH_MUSL */
|
||||
/* #undef ARROW_WITH_OPENTELEMETRY */
|
||||
/* #undef ARROW_WITH_UCX */
|
||||
|
||||
#define GRPCPP_PP_INCLUDE
|
||||
@@ -0,0 +1,411 @@
|
||||
// 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 <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/array.h"
|
||||
#include "arrow/chunked_array.h"
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/type.h"
|
||||
#include "arrow/type_traits.h"
|
||||
#include "arrow/util/checked_cast.h"
|
||||
#include "arrow/visit_type_inline.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
template <typename BaseConverter, template <typename...> class ConverterTrait>
|
||||
static Result<std::unique_ptr<BaseConverter>> MakeConverter(
|
||||
std::shared_ptr<DataType> type, typename BaseConverter::OptionsType options,
|
||||
MemoryPool* pool);
|
||||
|
||||
template <typename Input, typename Options>
|
||||
class Converter {
|
||||
public:
|
||||
using Self = Converter<Input, Options>;
|
||||
using InputType = Input;
|
||||
using OptionsType = Options;
|
||||
|
||||
virtual ~Converter() = default;
|
||||
|
||||
Status Construct(std::shared_ptr<DataType> type, OptionsType options,
|
||||
MemoryPool* pool) {
|
||||
type_ = std::move(type);
|
||||
options_ = std::move(options);
|
||||
return Init(pool);
|
||||
}
|
||||
|
||||
virtual Status Append(InputType value) { return Status::NotImplemented("Append"); }
|
||||
|
||||
virtual Status Extend(InputType values, int64_t size, int64_t offset = 0) {
|
||||
return Status::NotImplemented("Extend");
|
||||
}
|
||||
|
||||
virtual Status ExtendMasked(InputType values, InputType mask, int64_t size,
|
||||
int64_t offset = 0) {
|
||||
return Status::NotImplemented("ExtendMasked");
|
||||
}
|
||||
|
||||
const std::shared_ptr<ArrayBuilder>& builder() const { return builder_; }
|
||||
|
||||
const std::shared_ptr<DataType>& type() const { return type_; }
|
||||
|
||||
OptionsType options() const { return options_; }
|
||||
|
||||
bool may_overflow() const { return may_overflow_; }
|
||||
|
||||
bool rewind_on_overflow() const { return rewind_on_overflow_; }
|
||||
|
||||
virtual Status Reserve(int64_t additional_capacity) {
|
||||
return builder_->Reserve(additional_capacity);
|
||||
}
|
||||
|
||||
Status AppendNull() { return builder_->AppendNull(); }
|
||||
|
||||
virtual Result<std::shared_ptr<Array>> ToArray() { return builder_->Finish(); }
|
||||
|
||||
virtual Result<std::shared_ptr<Array>> ToArray(int64_t length) {
|
||||
ARROW_ASSIGN_OR_RAISE(auto arr, this->ToArray());
|
||||
return arr->Slice(0, length);
|
||||
}
|
||||
|
||||
virtual Result<std::shared_ptr<ChunkedArray>> ToChunkedArray() {
|
||||
ARROW_ASSIGN_OR_RAISE(auto array, ToArray());
|
||||
std::vector<std::shared_ptr<Array>> chunks = {std::move(array)};
|
||||
return std::make_shared<ChunkedArray>(chunks);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual Status Init(MemoryPool* pool) { return Status::OK(); }
|
||||
|
||||
std::shared_ptr<DataType> type_;
|
||||
std::shared_ptr<ArrayBuilder> builder_;
|
||||
OptionsType options_;
|
||||
bool may_overflow_ = false;
|
||||
bool rewind_on_overflow_ = false;
|
||||
};
|
||||
|
||||
template <typename ArrowType, typename BaseConverter>
|
||||
class PrimitiveConverter : public BaseConverter {
|
||||
public:
|
||||
using BuilderType = typename TypeTraits<ArrowType>::BuilderType;
|
||||
|
||||
protected:
|
||||
Status Init(MemoryPool* pool) override {
|
||||
this->builder_ = std::make_shared<BuilderType>(this->type_, pool);
|
||||
// Narrow variable-sized binary types may overflow
|
||||
this->may_overflow_ = is_binary_like(this->type_->id());
|
||||
primitive_type_ = checked_cast<const ArrowType*>(this->type_.get());
|
||||
primitive_builder_ = checked_cast<BuilderType*>(this->builder_.get());
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
const ArrowType* primitive_type_;
|
||||
BuilderType* primitive_builder_;
|
||||
};
|
||||
|
||||
template <typename ArrowType, typename BaseConverter,
|
||||
template <typename...> class ConverterTrait>
|
||||
class ListConverter : public BaseConverter {
|
||||
public:
|
||||
using BuilderType = typename TypeTraits<ArrowType>::BuilderType;
|
||||
using ConverterType = typename ConverterTrait<ArrowType>::type;
|
||||
|
||||
protected:
|
||||
Status Init(MemoryPool* pool) override {
|
||||
list_type_ = checked_cast<const ArrowType*>(this->type_.get());
|
||||
ARROW_ASSIGN_OR_RAISE(value_converter_,
|
||||
(MakeConverter<BaseConverter, ConverterTrait>(
|
||||
list_type_->value_type(), this->options_, pool)));
|
||||
this->builder_ =
|
||||
std::make_shared<BuilderType>(pool, value_converter_->builder(), this->type_);
|
||||
list_builder_ = checked_cast<BuilderType*>(this->builder_.get());
|
||||
// Narrow list types may overflow
|
||||
this->may_overflow_ = this->rewind_on_overflow_ =
|
||||
sizeof(typename ArrowType::offset_type) < sizeof(int64_t);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
const ArrowType* list_type_;
|
||||
BuilderType* list_builder_;
|
||||
std::unique_ptr<BaseConverter> value_converter_;
|
||||
};
|
||||
|
||||
template <typename BaseConverter, template <typename...> class ConverterTrait>
|
||||
class StructConverter : public BaseConverter {
|
||||
public:
|
||||
using ConverterType = typename ConverterTrait<StructType>::type;
|
||||
|
||||
Status Reserve(int64_t additional_capacity) override {
|
||||
ARROW_RETURN_NOT_OK(this->builder_->Reserve(additional_capacity));
|
||||
for (const auto& child : children_) {
|
||||
ARROW_RETURN_NOT_OK(child->Reserve(additional_capacity));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
protected:
|
||||
Status Init(MemoryPool* pool) override {
|
||||
std::unique_ptr<BaseConverter> child_converter;
|
||||
std::vector<std::shared_ptr<ArrayBuilder>> child_builders;
|
||||
|
||||
struct_type_ = checked_cast<const StructType*>(this->type_.get());
|
||||
for (const auto& field : struct_type_->fields()) {
|
||||
ARROW_ASSIGN_OR_RAISE(child_converter,
|
||||
(MakeConverter<BaseConverter, ConverterTrait>(
|
||||
field->type(), this->options_, pool)));
|
||||
this->may_overflow_ |= child_converter->may_overflow();
|
||||
this->rewind_on_overflow_ = this->may_overflow_;
|
||||
child_builders.push_back(child_converter->builder());
|
||||
children_.push_back(std::move(child_converter));
|
||||
}
|
||||
|
||||
this->builder_ =
|
||||
std::make_shared<StructBuilder>(this->type_, pool, std::move(child_builders));
|
||||
struct_builder_ = checked_cast<StructBuilder*>(this->builder_.get());
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
const StructType* struct_type_;
|
||||
StructBuilder* struct_builder_;
|
||||
std::vector<std::unique_ptr<BaseConverter>> children_;
|
||||
};
|
||||
|
||||
template <typename ValueType, typename BaseConverter>
|
||||
class DictionaryConverter : public BaseConverter {
|
||||
public:
|
||||
using BuilderType = DictionaryBuilder<ValueType>;
|
||||
|
||||
protected:
|
||||
Status Init(MemoryPool* pool) override {
|
||||
std::unique_ptr<ArrayBuilder> builder;
|
||||
ARROW_RETURN_NOT_OK(MakeDictionaryBuilder(pool, this->type_, NULLPTR, &builder));
|
||||
this->builder_ = std::move(builder);
|
||||
this->may_overflow_ = false;
|
||||
dict_type_ = checked_cast<const DictionaryType*>(this->type_.get());
|
||||
value_type_ = checked_cast<const ValueType*>(dict_type_->value_type().get());
|
||||
value_builder_ = checked_cast<BuilderType*>(this->builder_.get());
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
const DictionaryType* dict_type_;
|
||||
const ValueType* value_type_;
|
||||
BuilderType* value_builder_;
|
||||
};
|
||||
|
||||
template <typename BaseConverter, template <typename...> class ConverterTrait>
|
||||
struct MakeConverterImpl {
|
||||
template <typename T, typename ConverterType = typename ConverterTrait<T>::type>
|
||||
Status Visit(const T&) {
|
||||
out.reset(new ConverterType());
|
||||
return out->Construct(std::move(type), std::move(options), pool);
|
||||
}
|
||||
|
||||
Status Visit(const DictionaryType& t) {
|
||||
switch (t.value_type()->id()) {
|
||||
#define DICTIONARY_CASE(TYPE) \
|
||||
case TYPE::type_id: \
|
||||
out = std::make_unique< \
|
||||
typename ConverterTrait<DictionaryType>::template dictionary_type<TYPE>>(); \
|
||||
break;
|
||||
DICTIONARY_CASE(BooleanType);
|
||||
DICTIONARY_CASE(Int8Type);
|
||||
DICTIONARY_CASE(Int16Type);
|
||||
DICTIONARY_CASE(Int32Type);
|
||||
DICTIONARY_CASE(Int64Type);
|
||||
DICTIONARY_CASE(UInt8Type);
|
||||
DICTIONARY_CASE(UInt16Type);
|
||||
DICTIONARY_CASE(UInt32Type);
|
||||
DICTIONARY_CASE(UInt64Type);
|
||||
DICTIONARY_CASE(FloatType);
|
||||
DICTIONARY_CASE(DoubleType);
|
||||
DICTIONARY_CASE(BinaryType);
|
||||
DICTIONARY_CASE(StringType);
|
||||
DICTIONARY_CASE(FixedSizeBinaryType);
|
||||
#undef DICTIONARY_CASE
|
||||
default:
|
||||
return Status::NotImplemented("DictionaryArray converter for type ", t.ToString(),
|
||||
" not implemented");
|
||||
}
|
||||
return out->Construct(std::move(type), std::move(options), pool);
|
||||
}
|
||||
|
||||
Status Visit(const DataType& t) { return Status::NotImplemented(t.name()); }
|
||||
|
||||
std::shared_ptr<DataType> type;
|
||||
typename BaseConverter::OptionsType options;
|
||||
MemoryPool* pool;
|
||||
std::unique_ptr<BaseConverter> out;
|
||||
};
|
||||
|
||||
template <typename BaseConverter, template <typename...> class ConverterTrait>
|
||||
static Result<std::unique_ptr<BaseConverter>> MakeConverter(
|
||||
std::shared_ptr<DataType> type, typename BaseConverter::OptionsType options,
|
||||
MemoryPool* pool) {
|
||||
MakeConverterImpl<BaseConverter, ConverterTrait> visitor{
|
||||
std::move(type), std::move(options), pool, NULLPTR};
|
||||
ARROW_RETURN_NOT_OK(VisitTypeInline(*visitor.type, &visitor));
|
||||
return std::move(visitor.out);
|
||||
}
|
||||
|
||||
template <typename Converter>
|
||||
class Chunker {
|
||||
public:
|
||||
using InputType = typename Converter::InputType;
|
||||
|
||||
explicit Chunker(std::unique_ptr<Converter> converter)
|
||||
: converter_(std::move(converter)) {}
|
||||
|
||||
Status Reserve(int64_t additional_capacity) {
|
||||
ARROW_RETURN_NOT_OK(converter_->Reserve(additional_capacity));
|
||||
reserved_ += additional_capacity;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status AppendNull() {
|
||||
auto status = converter_->AppendNull();
|
||||
if (ARROW_PREDICT_FALSE(status.IsCapacityError())) {
|
||||
if (converter_->builder()->length() == 0) {
|
||||
// Builder length == 0 means the individual element is too large to append.
|
||||
// In this case, no need to try again.
|
||||
return status;
|
||||
}
|
||||
ARROW_RETURN_NOT_OK(FinishChunk());
|
||||
return converter_->AppendNull();
|
||||
}
|
||||
++length_;
|
||||
return status;
|
||||
}
|
||||
|
||||
Status Append(InputType value) {
|
||||
auto status = converter_->Append(value);
|
||||
if (ARROW_PREDICT_FALSE(status.IsCapacityError())) {
|
||||
if (converter_->builder()->length() == 0) {
|
||||
return status;
|
||||
}
|
||||
ARROW_RETURN_NOT_OK(FinishChunk());
|
||||
return Append(value);
|
||||
}
|
||||
++length_;
|
||||
return status;
|
||||
}
|
||||
|
||||
Status Extend(InputType values, int64_t size, int64_t offset = 0) {
|
||||
while (offset < size) {
|
||||
auto length_before = converter_->builder()->length();
|
||||
auto status = converter_->Extend(values, size, offset);
|
||||
auto length_after = converter_->builder()->length();
|
||||
auto num_converted = length_after - length_before;
|
||||
|
||||
offset += num_converted;
|
||||
length_ += num_converted;
|
||||
|
||||
if (status.IsCapacityError()) {
|
||||
if (converter_->builder()->length() == 0) {
|
||||
// Builder length == 0 means the individual element is too large to append.
|
||||
// In this case, no need to try again.
|
||||
return status;
|
||||
} else if (converter_->rewind_on_overflow()) {
|
||||
// The list-like and binary-like conversion paths may raise a capacity error,
|
||||
// we need to handle them differently. While the binary-like converters check
|
||||
// the capacity before append/extend the list-like converters just check after
|
||||
// append/extend. Thus depending on the implementation semantics we may need
|
||||
// to rewind (slice) the output chunk by one.
|
||||
length_ -= 1;
|
||||
offset -= 1;
|
||||
}
|
||||
ARROW_RETURN_NOT_OK(FinishChunk());
|
||||
} else if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status ExtendMasked(InputType values, InputType mask, int64_t size,
|
||||
int64_t offset = 0) {
|
||||
while (offset < size) {
|
||||
auto length_before = converter_->builder()->length();
|
||||
auto status = converter_->ExtendMasked(values, mask, size, offset);
|
||||
auto length_after = converter_->builder()->length();
|
||||
auto num_converted = length_after - length_before;
|
||||
|
||||
offset += num_converted;
|
||||
length_ += num_converted;
|
||||
|
||||
if (status.IsCapacityError()) {
|
||||
if (converter_->builder()->length() == 0) {
|
||||
// Builder length == 0 means the individual element is too large to append.
|
||||
// In this case, no need to try again.
|
||||
return status;
|
||||
} else if (converter_->rewind_on_overflow()) {
|
||||
// The list-like and binary-like conversion paths may raise a capacity error,
|
||||
// we need to handle them differently. While the binary-like converters check
|
||||
// the capacity before append/extend the list-like converters just check after
|
||||
// append/extend. Thus depending on the implementation semantics we may need
|
||||
// to rewind (slice) the output chunk by one.
|
||||
length_ -= 1;
|
||||
offset -= 1;
|
||||
}
|
||||
ARROW_RETURN_NOT_OK(FinishChunk());
|
||||
} else if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status FinishChunk() {
|
||||
ARROW_ASSIGN_OR_RAISE(auto chunk, converter_->ToArray(length_));
|
||||
chunks_.push_back(chunk);
|
||||
// Reserve space for the remaining items.
|
||||
// Besides being an optimization, it is also required if the converter's
|
||||
// implementation relies on unsafe builder methods in converter->Append().
|
||||
auto remaining = reserved_ - length_;
|
||||
Reset();
|
||||
return Reserve(remaining);
|
||||
}
|
||||
|
||||
Result<std::shared_ptr<ChunkedArray>> ToChunkedArray() {
|
||||
ARROW_RETURN_NOT_OK(FinishChunk());
|
||||
return std::make_shared<ChunkedArray>(chunks_);
|
||||
}
|
||||
|
||||
protected:
|
||||
void Reset() {
|
||||
converter_->builder()->Reset();
|
||||
length_ = 0;
|
||||
reserved_ = 0;
|
||||
}
|
||||
|
||||
int64_t length_ = 0;
|
||||
int64_t reserved_ = 0;
|
||||
std::unique_ptr<Converter> converter_;
|
||||
std::vector<std::shared_ptr<Array>> chunks_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static Result<std::unique_ptr<Chunker<T>>> MakeChunker(std::unique_ptr<T> converter) {
|
||||
return std::make_unique<Chunker<T>>(std::move(converter));
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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.
|
||||
|
||||
#ifndef ARROW_COUNTING_SEMAPHORE_H
|
||||
#define ARROW_COUNTING_SEMAPHORE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "arrow/status.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
/// \brief Simple mutex-based counting semaphore with timeout
|
||||
class ARROW_EXPORT CountingSemaphore {
|
||||
public:
|
||||
/// \brief Create an instance with initial_avail starting permits
|
||||
///
|
||||
/// \param[in] initial_avail The semaphore will start with this many permits available
|
||||
/// \param[in] timeout_seconds A timeout to be applied to all operations. Operations
|
||||
/// will return Status::Invalid if this timeout elapses
|
||||
explicit CountingSemaphore(uint32_t initial_avail = 0, double timeout_seconds = 10);
|
||||
~CountingSemaphore();
|
||||
/// \brief Block until num_permits permits are available
|
||||
Status Acquire(uint32_t num_permits);
|
||||
/// \brief Make num_permits permits available
|
||||
Status Release(uint32_t num_permits);
|
||||
/// \brief Wait until num_waiters are waiting on permits
|
||||
///
|
||||
/// This method is non-standard but useful in unit tests to ensure sequencing
|
||||
Status WaitForWaiters(uint32_t num_waiters);
|
||||
/// \brief Immediately time out any waiters
|
||||
///
|
||||
/// This method will return Status::OK only if there were no waiters to time out.
|
||||
/// Once closed any operation on this instance will return an invalid status.
|
||||
Status Close();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
|
||||
#endif // ARROW_COUNTING_SEMAPHORE_H
|
||||
@@ -0,0 +1,114 @@
|
||||
// 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.
|
||||
|
||||
// From Apache Impala (incubating) as of 2016-01-29. Pared down to a minimal
|
||||
// set of functions needed for Apache Arrow / Apache parquet-cpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
/// CpuInfo is an interface to query for cpu information at runtime. The caller can
|
||||
/// ask for the sizes of the caches and what hardware features are supported.
|
||||
/// On Linux, this information is pulled from a couple of sys files (/proc/cpuinfo and
|
||||
/// /sys/devices)
|
||||
class ARROW_EXPORT CpuInfo {
|
||||
public:
|
||||
~CpuInfo();
|
||||
|
||||
/// x86 features
|
||||
static constexpr int64_t SSSE3 = (1LL << 0);
|
||||
static constexpr int64_t SSE4_1 = (1LL << 1);
|
||||
static constexpr int64_t SSE4_2 = (1LL << 2);
|
||||
static constexpr int64_t POPCNT = (1LL << 3);
|
||||
static constexpr int64_t AVX = (1LL << 4);
|
||||
static constexpr int64_t AVX2 = (1LL << 5);
|
||||
static constexpr int64_t AVX512F = (1LL << 6);
|
||||
static constexpr int64_t AVX512CD = (1LL << 7);
|
||||
static constexpr int64_t AVX512VL = (1LL << 8);
|
||||
static constexpr int64_t AVX512DQ = (1LL << 9);
|
||||
static constexpr int64_t AVX512BW = (1LL << 10);
|
||||
static constexpr int64_t AVX512 = AVX512F | AVX512CD | AVX512VL | AVX512DQ | AVX512BW;
|
||||
static constexpr int64_t BMI1 = (1LL << 11);
|
||||
static constexpr int64_t BMI2 = (1LL << 12);
|
||||
|
||||
/// Arm features
|
||||
static constexpr int64_t ASIMD = (1LL << 32);
|
||||
|
||||
/// Cache enums for L1 (data), L2 and L3
|
||||
enum class CacheLevel { L1 = 0, L2, L3, Last = L3 };
|
||||
|
||||
/// CPU vendors
|
||||
enum class Vendor { Unknown, Intel, AMD };
|
||||
|
||||
static const CpuInfo* GetInstance();
|
||||
|
||||
/// Returns all the flags for this cpu
|
||||
int64_t hardware_flags() const;
|
||||
|
||||
/// Returns the number of cores (including hyper-threaded) on this machine.
|
||||
int num_cores() const;
|
||||
|
||||
/// Returns the vendor of the cpu.
|
||||
Vendor vendor() const;
|
||||
|
||||
/// Returns the model name of the cpu (e.g. Intel i7-2600)
|
||||
const std::string& model_name() const;
|
||||
|
||||
/// Returns the size of the cache in KB at this cache level
|
||||
int64_t CacheSize(CacheLevel level) const;
|
||||
|
||||
/// \brief Returns whether or not the given feature is enabled.
|
||||
///
|
||||
/// IsSupported() is true iff IsDetected() is also true and the feature
|
||||
/// wasn't disabled by the user (for example by setting the ARROW_USER_SIMD_LEVEL
|
||||
/// environment variable).
|
||||
bool IsSupported(int64_t flags) const;
|
||||
|
||||
/// Returns whether or not the given feature is available on the CPU.
|
||||
bool IsDetected(int64_t flags) const;
|
||||
|
||||
/// Determine if the CPU meets the minimum CPU requirements and if not, issue an error
|
||||
/// and terminate.
|
||||
void VerifyCpuRequirements() const;
|
||||
|
||||
/// Toggle a hardware feature on and off. It is not valid to turn on a feature
|
||||
/// that the underlying hardware cannot support. This is useful for testing.
|
||||
void EnableFeature(int64_t flag, bool enable);
|
||||
|
||||
bool HasEfficientBmi2() const {
|
||||
// BMI2 (pext, pdep) is only efficient on Intel X86 processors.
|
||||
return vendor() == Vendor::Intel && IsSupported(BMI2);
|
||||
}
|
||||
|
||||
private:
|
||||
CpuInfo();
|
||||
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
ARROW_EXPORT
|
||||
void DebugTrap();
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,316 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/type_fwd.h"
|
||||
#include "arrow/util/basic_decimal.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
/// Represents a signed 128-bit integer in two's complement.
|
||||
/// Calculations wrap around and overflow is ignored.
|
||||
/// The max decimal precision that can be safely represented is
|
||||
/// 38 significant digits.
|
||||
///
|
||||
/// For a discussion of the algorithms, look at Knuth's volume 2,
|
||||
/// Semi-numerical Algorithms section 4.3.1.
|
||||
///
|
||||
/// Adapted from the Apache ORC C++ implementation
|
||||
///
|
||||
/// The implementation is split into two parts :
|
||||
///
|
||||
/// 1. BasicDecimal128
|
||||
/// - can be safely compiled to IR without references to libstdc++.
|
||||
/// 2. Decimal128
|
||||
/// - has additional functionality on top of BasicDecimal128 to deal with
|
||||
/// strings and streams.
|
||||
class ARROW_EXPORT Decimal128 : public BasicDecimal128 {
|
||||
public:
|
||||
/// \cond FALSE
|
||||
// (need to avoid a duplicate definition in Sphinx)
|
||||
using BasicDecimal128::BasicDecimal128;
|
||||
/// \endcond
|
||||
|
||||
/// \brief constructor creates a Decimal128 from a BasicDecimal128.
|
||||
constexpr Decimal128(const BasicDecimal128& value) noexcept // NOLINT runtime/explicit
|
||||
: BasicDecimal128(value) {}
|
||||
|
||||
/// \brief Parse the number from a base 10 string representation.
|
||||
explicit Decimal128(const std::string& value);
|
||||
|
||||
/// \brief Empty constructor creates a Decimal128 with a value of 0.
|
||||
// This is required on some older compilers.
|
||||
constexpr Decimal128() noexcept : BasicDecimal128() {}
|
||||
|
||||
/// Divide this number by right and return the result.
|
||||
///
|
||||
/// This operation is not destructive.
|
||||
/// The answer rounds to zero. Signs work like:
|
||||
/// 21 / 5 -> 4, 1
|
||||
/// -21 / 5 -> -4, -1
|
||||
/// 21 / -5 -> -4, 1
|
||||
/// -21 / -5 -> 4, -1
|
||||
/// \param[in] divisor the number to divide by
|
||||
/// \return the pair of the quotient and the remainder
|
||||
Result<std::pair<Decimal128, Decimal128>> Divide(const Decimal128& divisor) const {
|
||||
std::pair<Decimal128, Decimal128> result;
|
||||
auto dstatus = BasicDecimal128::Divide(divisor, &result.first, &result.second);
|
||||
ARROW_RETURN_NOT_OK(ToArrowStatus(dstatus));
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
/// \brief Convert the Decimal128 value to a base 10 decimal string with the given
|
||||
/// scale.
|
||||
std::string ToString(int32_t scale) const;
|
||||
|
||||
/// \brief Convert the value to an integer string
|
||||
std::string ToIntegerString() const;
|
||||
|
||||
/// \brief Cast this value to an int64_t.
|
||||
explicit operator int64_t() const;
|
||||
|
||||
/// \brief Convert a decimal string to a Decimal128 value, optionally including
|
||||
/// precision and scale if they're passed in and not null.
|
||||
static Status FromString(const std::string_view& s, Decimal128* out, int32_t* precision,
|
||||
int32_t* scale = NULLPTR);
|
||||
static Status FromString(const std::string& s, Decimal128* out, int32_t* precision,
|
||||
int32_t* scale = NULLPTR);
|
||||
static Status FromString(const char* s, Decimal128* out, int32_t* precision,
|
||||
int32_t* scale = NULLPTR);
|
||||
static Result<Decimal128> FromString(const std::string_view& s);
|
||||
static Result<Decimal128> FromString(const std::string& s);
|
||||
static Result<Decimal128> FromString(const char* s);
|
||||
|
||||
static Result<Decimal128> FromReal(double real, int32_t precision, int32_t scale);
|
||||
static Result<Decimal128> FromReal(float real, int32_t precision, int32_t scale);
|
||||
|
||||
/// \brief Convert from a big-endian byte representation. The length must be
|
||||
/// between 1 and 16.
|
||||
/// \return error status if the length is an invalid value
|
||||
static Result<Decimal128> FromBigEndian(const uint8_t* data, int32_t length);
|
||||
|
||||
/// \brief Convert Decimal128 from one scale to another
|
||||
Result<Decimal128> Rescale(int32_t original_scale, int32_t new_scale) const {
|
||||
Decimal128 out;
|
||||
auto dstatus = BasicDecimal128::Rescale(original_scale, new_scale, &out);
|
||||
ARROW_RETURN_NOT_OK(ToArrowStatus(dstatus));
|
||||
return std::move(out);
|
||||
}
|
||||
|
||||
/// \brief Convert to a signed integer
|
||||
template <typename T, typename = internal::EnableIfIsOneOf<T, int32_t, int64_t>>
|
||||
Result<T> ToInteger() const {
|
||||
constexpr auto min_value = std::numeric_limits<T>::min();
|
||||
constexpr auto max_value = std::numeric_limits<T>::max();
|
||||
const auto& self = *this;
|
||||
if (self < min_value || self > max_value) {
|
||||
return Status::Invalid("Invalid cast from Decimal128 to ", sizeof(T),
|
||||
" byte integer");
|
||||
}
|
||||
return static_cast<T>(low_bits());
|
||||
}
|
||||
|
||||
/// \brief Convert to a signed integer
|
||||
template <typename T, typename = internal::EnableIfIsOneOf<T, int32_t, int64_t>>
|
||||
Status ToInteger(T* out) const {
|
||||
return ToInteger<T>().Value(out);
|
||||
}
|
||||
|
||||
/// \brief Convert to a floating-point number (scaled)
|
||||
float ToFloat(int32_t scale) const;
|
||||
/// \brief Convert to a floating-point number (scaled)
|
||||
double ToDouble(int32_t scale) const;
|
||||
|
||||
/// \brief Convert to a floating-point number (scaled)
|
||||
template <typename T>
|
||||
T ToReal(int32_t scale) const {
|
||||
return ToRealConversion<T>::ToReal(*this, scale);
|
||||
}
|
||||
|
||||
ARROW_FRIEND_EXPORT friend std::ostream& operator<<(std::ostream& os,
|
||||
const Decimal128& decimal);
|
||||
|
||||
private:
|
||||
/// Converts internal error code to Status
|
||||
Status ToArrowStatus(DecimalStatus dstatus) const;
|
||||
|
||||
template <typename T>
|
||||
struct ToRealConversion {};
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Decimal128::ToRealConversion<float> {
|
||||
static float ToReal(const Decimal128& dec, int32_t scale) { return dec.ToFloat(scale); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Decimal128::ToRealConversion<double> {
|
||||
static double ToReal(const Decimal128& dec, int32_t scale) {
|
||||
return dec.ToDouble(scale);
|
||||
}
|
||||
};
|
||||
|
||||
/// Represents a signed 256-bit integer in two's complement.
|
||||
/// The max decimal precision that can be safely represented is
|
||||
/// 76 significant digits.
|
||||
///
|
||||
/// The implementation is split into two parts :
|
||||
///
|
||||
/// 1. BasicDecimal256
|
||||
/// - can be safely compiled to IR without references to libstdc++.
|
||||
/// 2. Decimal256
|
||||
/// - (TODO) has additional functionality on top of BasicDecimal256 to deal with
|
||||
/// strings and streams.
|
||||
class ARROW_EXPORT Decimal256 : public BasicDecimal256 {
|
||||
public:
|
||||
/// \cond FALSE
|
||||
// (need to avoid a duplicate definition in Sphinx)
|
||||
using BasicDecimal256::BasicDecimal256;
|
||||
/// \endcond
|
||||
|
||||
/// \brief constructor creates a Decimal256 from a BasicDecimal256.
|
||||
constexpr Decimal256(const BasicDecimal256& value) noexcept // NOLINT(runtime/explicit)
|
||||
: BasicDecimal256(value) {}
|
||||
|
||||
/// \brief Parse the number from a base 10 string representation.
|
||||
explicit Decimal256(const std::string& value);
|
||||
|
||||
/// \brief Empty constructor creates a Decimal256 with a value of 0.
|
||||
// This is required on some older compilers.
|
||||
constexpr Decimal256() noexcept : BasicDecimal256() {}
|
||||
|
||||
/// \brief Convert the Decimal256 value to a base 10 decimal string with the given
|
||||
/// scale.
|
||||
std::string ToString(int32_t scale) const;
|
||||
|
||||
/// \brief Convert the value to an integer string
|
||||
std::string ToIntegerString() const;
|
||||
|
||||
/// \brief Convert a decimal string to a Decimal256 value, optionally including
|
||||
/// precision and scale if they're passed in and not null.
|
||||
static Status FromString(const std::string_view& s, Decimal256* out, int32_t* precision,
|
||||
int32_t* scale = NULLPTR);
|
||||
static Status FromString(const std::string& s, Decimal256* out, int32_t* precision,
|
||||
int32_t* scale = NULLPTR);
|
||||
static Status FromString(const char* s, Decimal256* out, int32_t* precision,
|
||||
int32_t* scale = NULLPTR);
|
||||
static Result<Decimal256> FromString(const std::string_view& s);
|
||||
static Result<Decimal256> FromString(const std::string& s);
|
||||
static Result<Decimal256> FromString(const char* s);
|
||||
|
||||
/// \brief Convert Decimal256 from one scale to another
|
||||
Result<Decimal256> Rescale(int32_t original_scale, int32_t new_scale) const {
|
||||
Decimal256 out;
|
||||
auto dstatus = BasicDecimal256::Rescale(original_scale, new_scale, &out);
|
||||
ARROW_RETURN_NOT_OK(ToArrowStatus(dstatus));
|
||||
return std::move(out);
|
||||
}
|
||||
|
||||
/// Divide this number by right and return the result.
|
||||
///
|
||||
/// This operation is not destructive.
|
||||
/// The answer rounds to zero. Signs work like:
|
||||
/// 21 / 5 -> 4, 1
|
||||
/// -21 / 5 -> -4, -1
|
||||
/// 21 / -5 -> -4, 1
|
||||
/// -21 / -5 -> 4, -1
|
||||
/// \param[in] divisor the number to divide by
|
||||
/// \return the pair of the quotient and the remainder
|
||||
Result<std::pair<Decimal256, Decimal256>> Divide(const Decimal256& divisor) const {
|
||||
std::pair<Decimal256, Decimal256> result;
|
||||
auto dstatus = BasicDecimal256::Divide(divisor, &result.first, &result.second);
|
||||
ARROW_RETURN_NOT_OK(ToArrowStatus(dstatus));
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
/// \brief Convert from a big-endian byte representation. The length must be
|
||||
/// between 1 and 32.
|
||||
/// \return error status if the length is an invalid value
|
||||
static Result<Decimal256> FromBigEndian(const uint8_t* data, int32_t length);
|
||||
|
||||
static Result<Decimal256> FromReal(double real, int32_t precision, int32_t scale);
|
||||
static Result<Decimal256> FromReal(float real, int32_t precision, int32_t scale);
|
||||
|
||||
/// \brief Convert to a floating-point number (scaled).
|
||||
/// May return infinity in case of overflow.
|
||||
float ToFloat(int32_t scale) const;
|
||||
/// \brief Convert to a floating-point number (scaled)
|
||||
double ToDouble(int32_t scale) const;
|
||||
|
||||
/// \brief Convert to a floating-point number (scaled)
|
||||
template <typename T>
|
||||
T ToReal(int32_t scale) const {
|
||||
return ToRealConversion<T>::ToReal(*this, scale);
|
||||
}
|
||||
|
||||
ARROW_FRIEND_EXPORT friend std::ostream& operator<<(std::ostream& os,
|
||||
const Decimal256& decimal);
|
||||
|
||||
private:
|
||||
/// Converts internal error code to Status
|
||||
Status ToArrowStatus(DecimalStatus dstatus) const;
|
||||
|
||||
template <typename T>
|
||||
struct ToRealConversion {};
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Decimal256::ToRealConversion<float> {
|
||||
static float ToReal(const Decimal256& dec, int32_t scale) { return dec.ToFloat(scale); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Decimal256::ToRealConversion<double> {
|
||||
static double ToReal(const Decimal256& dec, int32_t scale) {
|
||||
return dec.ToDouble(scale);
|
||||
}
|
||||
};
|
||||
|
||||
/// For an integer type, return the max number of decimal digits
|
||||
/// (=minimal decimal precision) it can represent.
|
||||
inline Result<int32_t> MaxDecimalDigitsForInteger(Type::type type_id) {
|
||||
switch (type_id) {
|
||||
case Type::INT8:
|
||||
case Type::UINT8:
|
||||
return 3;
|
||||
case Type::INT16:
|
||||
case Type::UINT16:
|
||||
return 5;
|
||||
case Type::INT32:
|
||||
case Type::UINT32:
|
||||
return 10;
|
||||
case Type::INT64:
|
||||
return 19;
|
||||
case Type::UINT64:
|
||||
return 20;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return Status::Invalid("Not an integer type: ", type_id);
|
||||
}
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,181 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
class Buffer;
|
||||
|
||||
class ARROW_EXPORT BoundaryFinder {
|
||||
public:
|
||||
BoundaryFinder() = default;
|
||||
|
||||
virtual ~BoundaryFinder();
|
||||
|
||||
/// \brief Find the position of the first delimiter inside block
|
||||
///
|
||||
/// `partial` is taken to be the beginning of the block, and `block`
|
||||
/// its continuation. Also, `partial` doesn't contain a delimiter.
|
||||
///
|
||||
/// The returned `out_pos` is relative to `block`'s start and should point
|
||||
/// to the first character after the first delimiter.
|
||||
/// `out_pos` will be -1 if no delimiter is found.
|
||||
virtual Status FindFirst(std::string_view partial, std::string_view block,
|
||||
int64_t* out_pos) = 0;
|
||||
|
||||
/// \brief Find the position of the last delimiter inside block
|
||||
///
|
||||
/// The returned `out_pos` is relative to `block`'s start and should point
|
||||
/// to the first character after the last delimiter.
|
||||
/// `out_pos` will be -1 if no delimiter is found.
|
||||
virtual Status FindLast(std::string_view block, int64_t* out_pos) = 0;
|
||||
|
||||
/// \brief Find the position of the Nth delimiter inside the block
|
||||
///
|
||||
/// `partial` is taken to be the beginning of the block, and `block`
|
||||
/// its continuation. Also, `partial` doesn't contain a delimiter.
|
||||
///
|
||||
/// The returned `out_pos` is relative to `block`'s start and should point
|
||||
/// to the first character after the first delimiter.
|
||||
/// `out_pos` will be -1 if no delimiter is found.
|
||||
///
|
||||
/// The returned `num_found` is the number of delimiters actually found
|
||||
virtual Status FindNth(std::string_view partial, std::string_view block, int64_t count,
|
||||
int64_t* out_pos, int64_t* num_found) = 0;
|
||||
|
||||
static constexpr int64_t kNoDelimiterFound = -1;
|
||||
|
||||
protected:
|
||||
ARROW_DISALLOW_COPY_AND_ASSIGN(BoundaryFinder);
|
||||
};
|
||||
|
||||
ARROW_EXPORT
|
||||
std::shared_ptr<BoundaryFinder> MakeNewlineBoundaryFinder();
|
||||
|
||||
/// \brief A reusable block-based chunker for delimited data
|
||||
///
|
||||
/// The chunker takes a block of delimited data and helps carve a sub-block
|
||||
/// which begins and ends on delimiters (suitable for consumption by parsers
|
||||
/// which can only parse whole objects).
|
||||
class ARROW_EXPORT Chunker {
|
||||
public:
|
||||
explicit Chunker(std::shared_ptr<BoundaryFinder> delimiter);
|
||||
~Chunker();
|
||||
|
||||
/// \brief Carve up a chunk in a block of data to contain only whole objects
|
||||
///
|
||||
/// Pre-conditions:
|
||||
/// - `block` is the start of a valid block of delimited data
|
||||
/// (i.e. starts just after a delimiter)
|
||||
///
|
||||
/// Post-conditions:
|
||||
/// - block == whole + partial
|
||||
/// - `whole` is a valid block of delimited data
|
||||
/// (i.e. starts just after a delimiter and ends with a delimiter)
|
||||
/// - `partial` doesn't contain an entire delimited object
|
||||
/// (IOW: `partial` is generally small)
|
||||
///
|
||||
/// This method will look for the last delimiter in `block` and may
|
||||
/// therefore be costly.
|
||||
///
|
||||
/// \param[in] block data to be chunked
|
||||
/// \param[out] whole subrange of block containing whole delimited objects
|
||||
/// \param[out] partial subrange of block starting with a partial delimited object
|
||||
Status Process(std::shared_ptr<Buffer> block, std::shared_ptr<Buffer>* whole,
|
||||
std::shared_ptr<Buffer>* partial);
|
||||
|
||||
/// \brief Carve the completion of a partial object out of a block
|
||||
///
|
||||
/// Pre-conditions:
|
||||
/// - `partial` is the start of a valid block of delimited data
|
||||
/// (i.e. starts just after a delimiter)
|
||||
/// - `block` follows `partial` in file order
|
||||
///
|
||||
/// Post-conditions:
|
||||
/// - block == completion + rest
|
||||
/// - `partial + completion` is a valid block of delimited data
|
||||
/// (i.e. starts just after a delimiter and ends with a delimiter)
|
||||
/// - `completion` doesn't contain an entire delimited object
|
||||
/// (IOW: `completion` is generally small)
|
||||
///
|
||||
/// This method will look for the first delimiter in `block` and should
|
||||
/// therefore be reasonably cheap.
|
||||
///
|
||||
/// \param[in] partial incomplete delimited data
|
||||
/// \param[in] block delimited data following partial
|
||||
/// \param[out] completion subrange of block containing the completion of partial
|
||||
/// \param[out] rest subrange of block containing what completion does not cover
|
||||
Status ProcessWithPartial(std::shared_ptr<Buffer> partial,
|
||||
std::shared_ptr<Buffer> block,
|
||||
std::shared_ptr<Buffer>* completion,
|
||||
std::shared_ptr<Buffer>* rest);
|
||||
|
||||
/// \brief Like ProcessWithPartial, but for the last block of a file
|
||||
///
|
||||
/// This method allows for a final delimited object without a trailing delimiter
|
||||
/// (ProcessWithPartial would return an error in that case).
|
||||
///
|
||||
/// Pre-conditions:
|
||||
/// - `partial` is the start of a valid block of delimited data
|
||||
/// - `block` follows `partial` in file order and is the last data block
|
||||
///
|
||||
/// Post-conditions:
|
||||
/// - block == completion + rest
|
||||
/// - `partial + completion` is a valid block of delimited data
|
||||
/// - `completion` doesn't contain an entire delimited object
|
||||
/// (IOW: `completion` is generally small)
|
||||
///
|
||||
Status ProcessFinal(std::shared_ptr<Buffer> partial, std::shared_ptr<Buffer> block,
|
||||
std::shared_ptr<Buffer>* completion, std::shared_ptr<Buffer>* rest);
|
||||
|
||||
/// \brief Skip count number of rows
|
||||
/// Pre-conditions:
|
||||
/// - `partial` is the start of a valid block of delimited data
|
||||
/// (i.e. starts just after a delimiter)
|
||||
/// - `block` follows `partial` in file order
|
||||
///
|
||||
/// Post-conditions:
|
||||
/// - `count` is updated to indicate the number of rows that still need to be skipped
|
||||
/// - If `count` is > 0 then `rest` is an incomplete block that should be a future
|
||||
/// `partial`
|
||||
/// - Else `rest` could be one or more valid blocks of delimited data which need to be
|
||||
/// parsed
|
||||
///
|
||||
/// \param[in] partial incomplete delimited data
|
||||
/// \param[in] block delimited data following partial
|
||||
/// \param[in] final whether this is the final chunk
|
||||
/// \param[in,out] count number of rows that need to be skipped
|
||||
/// \param[out] rest subrange of block containing what was not skipped
|
||||
Status ProcessSkip(std::shared_ptr<Buffer> partial, std::shared_ptr<Buffer> block,
|
||||
bool final, int64_t* count, std::shared_ptr<Buffer>* rest);
|
||||
|
||||
protected:
|
||||
ARROW_DISALLOW_COPY_AND_ASSIGN(Chunker);
|
||||
|
||||
std::shared_ptr<BoundaryFinder> boundary_finder_;
|
||||
};
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,115 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/util/cpu_info.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
enum class DispatchLevel : int {
|
||||
// These dispatch levels, corresponding to instruction set features,
|
||||
// are sorted in increasing order of preference.
|
||||
NONE = 0,
|
||||
SSE4_2,
|
||||
AVX2,
|
||||
AVX512,
|
||||
NEON,
|
||||
MAX
|
||||
};
|
||||
|
||||
/*
|
||||
A facility for dynamic dispatch according to available DispatchLevel.
|
||||
|
||||
Typical use:
|
||||
|
||||
static void my_function_default(...);
|
||||
static void my_function_avx2(...);
|
||||
|
||||
struct MyDynamicFunction {
|
||||
using FunctionType = decltype(&my_function_default);
|
||||
|
||||
static std::vector<std::pair<DispatchLevel, FunctionType>> implementations() {
|
||||
return {
|
||||
{ DispatchLevel::NONE, my_function_default }
|
||||
#if defined(ARROW_HAVE_RUNTIME_AVX2)
|
||||
, { DispatchLevel::AVX2, my_function_avx2 }
|
||||
#endif
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
void my_function(...) {
|
||||
static DynamicDispatch<MyDynamicFunction> dispatch;
|
||||
return dispatch.func(...);
|
||||
}
|
||||
*/
|
||||
template <typename DynamicFunction>
|
||||
class DynamicDispatch {
|
||||
protected:
|
||||
using FunctionType = typename DynamicFunction::FunctionType;
|
||||
using Implementation = std::pair<DispatchLevel, FunctionType>;
|
||||
|
||||
public:
|
||||
DynamicDispatch() { Resolve(DynamicFunction::implementations()); }
|
||||
|
||||
FunctionType func = {};
|
||||
|
||||
protected:
|
||||
// Use the Implementation with the highest DispatchLevel
|
||||
void Resolve(const std::vector<Implementation>& implementations) {
|
||||
Implementation cur{DispatchLevel::NONE, {}};
|
||||
|
||||
for (const auto& impl : implementations) {
|
||||
if (impl.first >= cur.first && IsSupported(impl.first)) {
|
||||
// Higher (or same) level than current
|
||||
cur = impl;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cur.second) {
|
||||
Status::Invalid("No appropriate implementation found").Abort();
|
||||
}
|
||||
func = cur.second;
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsSupported(DispatchLevel level) const {
|
||||
static const auto cpu_info = arrow::internal::CpuInfo::GetInstance();
|
||||
|
||||
switch (level) {
|
||||
case DispatchLevel::NONE:
|
||||
return true;
|
||||
case DispatchLevel::SSE4_2:
|
||||
return cpu_info->IsSupported(CpuInfo::SSE4_2);
|
||||
case DispatchLevel::AVX2:
|
||||
return cpu_info->IsSupported(CpuInfo::AVX2);
|
||||
case DispatchLevel::AVX512:
|
||||
return cpu_info->IsSupported(CpuInfo::AVX512);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arrow/vendored/double-conversion/double-conversion.h" // IWYU pragma: export
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
namespace double_conversion {
|
||||
|
||||
using ::double_conversion::DoubleToStringConverter;
|
||||
using ::double_conversion::StringBuilder;
|
||||
using ::double_conversion::StringToDoubleConverter;
|
||||
|
||||
} // namespace double_conversion
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,245 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#define ARROW_LITTLE_ENDIAN 1
|
||||
#else
|
||||
#if defined(__APPLE__) || defined(__FreeBSD__)
|
||||
#include <machine/endian.h> // IWYU pragma: keep
|
||||
#elif defined(sun) || defined(__sun)
|
||||
#include <sys/byteorder.h> // IWYU pragma: keep
|
||||
#else
|
||||
#include <endian.h> // IWYU pragma: keep
|
||||
#endif
|
||||
#
|
||||
#ifndef __BYTE_ORDER__
|
||||
#error "__BYTE_ORDER__ not defined"
|
||||
#endif
|
||||
#
|
||||
#ifndef __ORDER_LITTLE_ENDIAN__
|
||||
#error "__ORDER_LITTLE_ENDIAN__ not defined"
|
||||
#endif
|
||||
#
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
#define ARROW_LITTLE_ENDIAN 1
|
||||
#else
|
||||
#define ARROW_LITTLE_ENDIAN 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h> // IWYU pragma: keep
|
||||
#define ARROW_BYTE_SWAP64 _byteswap_uint64
|
||||
#define ARROW_BYTE_SWAP32 _byteswap_ulong
|
||||
#else
|
||||
#define ARROW_BYTE_SWAP64 __builtin_bswap64
|
||||
#define ARROW_BYTE_SWAP32 __builtin_bswap32
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "arrow/util/type_traits.h"
|
||||
#include "arrow/util/ubsan.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace bit_util {
|
||||
|
||||
//
|
||||
// Byte-swap 16-bit, 32-bit and 64-bit values
|
||||
//
|
||||
|
||||
// Swap the byte order (i.e. endianness)
|
||||
static inline int64_t ByteSwap(int64_t value) { return ARROW_BYTE_SWAP64(value); }
|
||||
static inline uint64_t ByteSwap(uint64_t value) {
|
||||
return static_cast<uint64_t>(ARROW_BYTE_SWAP64(value));
|
||||
}
|
||||
static inline int32_t ByteSwap(int32_t value) { return ARROW_BYTE_SWAP32(value); }
|
||||
static inline uint32_t ByteSwap(uint32_t value) {
|
||||
return static_cast<uint32_t>(ARROW_BYTE_SWAP32(value));
|
||||
}
|
||||
static inline int16_t ByteSwap(int16_t value) {
|
||||
constexpr auto m = static_cast<int16_t>(0xff);
|
||||
return static_cast<int16_t>(((value >> 8) & m) | ((value & m) << 8));
|
||||
}
|
||||
static inline uint16_t ByteSwap(uint16_t value) {
|
||||
return static_cast<uint16_t>(ByteSwap(static_cast<int16_t>(value)));
|
||||
}
|
||||
static inline uint8_t ByteSwap(uint8_t value) { return value; }
|
||||
static inline int8_t ByteSwap(int8_t value) { return value; }
|
||||
static inline double ByteSwap(double value) {
|
||||
const uint64_t swapped = ARROW_BYTE_SWAP64(util::SafeCopy<uint64_t>(value));
|
||||
return util::SafeCopy<double>(swapped);
|
||||
}
|
||||
static inline float ByteSwap(float value) {
|
||||
const uint32_t swapped = ARROW_BYTE_SWAP32(util::SafeCopy<uint32_t>(value));
|
||||
return util::SafeCopy<float>(swapped);
|
||||
}
|
||||
|
||||
// Write the swapped bytes into dst. Src and dst cannot overlap.
|
||||
static inline void ByteSwap(void* dst, const void* src, int len) {
|
||||
switch (len) {
|
||||
case 1:
|
||||
*reinterpret_cast<int8_t*>(dst) = *reinterpret_cast<const int8_t*>(src);
|
||||
return;
|
||||
case 2:
|
||||
*reinterpret_cast<int16_t*>(dst) = ByteSwap(*reinterpret_cast<const int16_t*>(src));
|
||||
return;
|
||||
case 4:
|
||||
*reinterpret_cast<int32_t*>(dst) = ByteSwap(*reinterpret_cast<const int32_t*>(src));
|
||||
return;
|
||||
case 8:
|
||||
*reinterpret_cast<int64_t*>(dst) = ByteSwap(*reinterpret_cast<const int64_t*>(src));
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
auto d = reinterpret_cast<uint8_t*>(dst);
|
||||
auto s = reinterpret_cast<const uint8_t*>(src);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
d[i] = s[len - i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to little/big endian format from the machine's native endian format.
|
||||
#if ARROW_LITTLE_ENDIAN
|
||||
template <typename T, typename = internal::EnableIfIsOneOf<
|
||||
T, int64_t, uint64_t, int32_t, uint32_t, int16_t, uint16_t,
|
||||
uint8_t, int8_t, float, double, bool>>
|
||||
static inline T ToBigEndian(T value) {
|
||||
return ByteSwap(value);
|
||||
}
|
||||
|
||||
template <typename T, typename = internal::EnableIfIsOneOf<
|
||||
T, int64_t, uint64_t, int32_t, uint32_t, int16_t, uint16_t,
|
||||
uint8_t, int8_t, float, double, bool>>
|
||||
static inline T ToLittleEndian(T value) {
|
||||
return value;
|
||||
}
|
||||
#else
|
||||
template <typename T, typename = internal::EnableIfIsOneOf<
|
||||
T, int64_t, uint64_t, int32_t, uint32_t, int16_t, uint16_t,
|
||||
uint8_t, int8_t, float, double, bool>>
|
||||
static inline T ToBigEndian(T value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename T, typename = internal::EnableIfIsOneOf<
|
||||
T, int64_t, uint64_t, int32_t, uint32_t, int16_t, uint16_t,
|
||||
uint8_t, int8_t, float, double, bool>>
|
||||
static inline T ToLittleEndian(T value) {
|
||||
return ByteSwap(value);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Convert from big/little endian format to the machine's native endian format.
|
||||
#if ARROW_LITTLE_ENDIAN
|
||||
template <typename T, typename = internal::EnableIfIsOneOf<
|
||||
T, int64_t, uint64_t, int32_t, uint32_t, int16_t, uint16_t,
|
||||
uint8_t, int8_t, float, double, bool>>
|
||||
static inline T FromBigEndian(T value) {
|
||||
return ByteSwap(value);
|
||||
}
|
||||
|
||||
template <typename T, typename = internal::EnableIfIsOneOf<
|
||||
T, int64_t, uint64_t, int32_t, uint32_t, int16_t, uint16_t,
|
||||
uint8_t, int8_t, float, double, bool>>
|
||||
static inline T FromLittleEndian(T value) {
|
||||
return value;
|
||||
}
|
||||
#else
|
||||
template <typename T, typename = internal::EnableIfIsOneOf<
|
||||
T, int64_t, uint64_t, int32_t, uint32_t, int16_t, uint16_t,
|
||||
uint8_t, int8_t, float, double, bool>>
|
||||
static inline T FromBigEndian(T value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename T, typename = internal::EnableIfIsOneOf<
|
||||
T, int64_t, uint64_t, int32_t, uint32_t, int16_t, uint16_t,
|
||||
uint8_t, int8_t, float, double, bool>>
|
||||
static inline T FromLittleEndian(T value) {
|
||||
return ByteSwap(value);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Handle endianness in *word* granuality (keep individual array element untouched)
|
||||
namespace little_endian {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Read a native endian array as little endian
|
||||
template <typename T, size_t N>
|
||||
struct Reader {
|
||||
const std::array<T, N>& native_array;
|
||||
|
||||
explicit Reader(const std::array<T, N>& native_array) : native_array(native_array) {}
|
||||
|
||||
const T& operator[](size_t i) const {
|
||||
return native_array[ARROW_LITTLE_ENDIAN ? i : N - 1 - i];
|
||||
}
|
||||
};
|
||||
|
||||
// Read/write a native endian array as little endian
|
||||
template <typename T, size_t N>
|
||||
struct Writer {
|
||||
std::array<T, N>* native_array;
|
||||
|
||||
explicit Writer(std::array<T, N>* native_array) : native_array(native_array) {}
|
||||
|
||||
const T& operator[](size_t i) const {
|
||||
return (*native_array)[ARROW_LITTLE_ENDIAN ? i : N - 1 - i];
|
||||
}
|
||||
T& operator[](size_t i) { return (*native_array)[ARROW_LITTLE_ENDIAN ? i : N - 1 - i]; }
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Construct array reader and try to deduce template augments
|
||||
template <typename T, size_t N>
|
||||
static inline detail::Reader<T, N> Make(const std::array<T, N>& native_array) {
|
||||
return detail::Reader<T, N>(native_array);
|
||||
}
|
||||
|
||||
// Construct array writer and try to deduce template augments
|
||||
template <typename T, size_t N>
|
||||
static inline detail::Writer<T, N> Make(std::array<T, N>* native_array) {
|
||||
return detail::Writer<T, N>(native_array);
|
||||
}
|
||||
|
||||
// Convert little endian array to native endian
|
||||
template <typename T, size_t N>
|
||||
static inline std::array<T, N> ToNative(std::array<T, N> array) {
|
||||
if (!ARROW_LITTLE_ENDIAN) {
|
||||
std::reverse(array.begin(), array.end());
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
// Convert native endian array to little endian
|
||||
template <typename T, size_t N>
|
||||
static inline std::array<T, N> FromNative(std::array<T, N> array) {
|
||||
return ToNative(array);
|
||||
}
|
||||
|
||||
} // namespace little_endian
|
||||
|
||||
} // namespace bit_util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,635 @@
|
||||
// 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.
|
||||
|
||||
// This is a private header for number-to-string formatting utilities
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/type.h"
|
||||
#include "arrow/type_traits.h"
|
||||
#include "arrow/util/double_conversion.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/string.h"
|
||||
#include "arrow/util/time.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
#include "arrow/vendored/datetime.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
/// \brief The entry point for conversion to strings.
|
||||
template <typename ARROW_TYPE, typename Enable = void>
|
||||
class StringFormatter;
|
||||
|
||||
template <typename T>
|
||||
struct is_formattable {
|
||||
template <typename U, typename = typename StringFormatter<U>::value_type>
|
||||
static std::true_type Test(U*);
|
||||
|
||||
template <typename U>
|
||||
static std::false_type Test(...);
|
||||
|
||||
static constexpr bool value = decltype(Test<T>(NULLPTR))::value;
|
||||
};
|
||||
|
||||
template <typename T, typename R = void>
|
||||
using enable_if_formattable = enable_if_t<is_formattable<T>::value, R>;
|
||||
|
||||
template <typename Appender>
|
||||
using Return = decltype(std::declval<Appender>()(std::string_view{}));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Boolean formatting
|
||||
|
||||
template <>
|
||||
class StringFormatter<BooleanType> {
|
||||
public:
|
||||
explicit StringFormatter(const DataType* = NULLPTR) {}
|
||||
|
||||
using value_type = bool;
|
||||
|
||||
template <typename Appender>
|
||||
Return<Appender> operator()(bool value, Appender&& append) {
|
||||
if (value) {
|
||||
const char string[] = "true";
|
||||
return append(std::string_view(string));
|
||||
} else {
|
||||
const char string[] = "false";
|
||||
return append(std::string_view(string));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Decimals formatting
|
||||
|
||||
template <typename ARROW_TYPE>
|
||||
class DecimalToStringFormatterMixin {
|
||||
public:
|
||||
explicit DecimalToStringFormatterMixin(const DataType* type)
|
||||
: scale_(static_cast<const ARROW_TYPE*>(type)->scale()) {}
|
||||
|
||||
using value_type = typename TypeTraits<ARROW_TYPE>::CType;
|
||||
|
||||
template <typename Appender>
|
||||
Return<Appender> operator()(const value_type& value, Appender&& append) {
|
||||
return append(value.ToString(scale_));
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t scale_;
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<Decimal128Type>
|
||||
: public DecimalToStringFormatterMixin<Decimal128Type> {
|
||||
using DecimalToStringFormatterMixin::DecimalToStringFormatterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<Decimal256Type>
|
||||
: public DecimalToStringFormatterMixin<Decimal256Type> {
|
||||
using DecimalToStringFormatterMixin::DecimalToStringFormatterMixin;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Integer formatting
|
||||
|
||||
namespace detail {
|
||||
|
||||
// A 2x100 direct table mapping integers in [0..99] to their decimal representations.
|
||||
ARROW_EXPORT extern const char digit_pairs[];
|
||||
|
||||
// Based on fmtlib's format_int class:
|
||||
// Write digits from right to left into a stack allocated buffer
|
||||
inline void FormatOneChar(char c, char** cursor) { *--*cursor = c; }
|
||||
|
||||
template <typename Int>
|
||||
void FormatOneDigit(Int value, char** cursor) {
|
||||
assert(value >= 0 && value <= 9);
|
||||
FormatOneChar(static_cast<char>('0' + value), cursor);
|
||||
}
|
||||
|
||||
template <typename Int>
|
||||
void FormatTwoDigits(Int value, char** cursor) {
|
||||
assert(value >= 0 && value <= 99);
|
||||
auto digit_pair = &digit_pairs[value * 2];
|
||||
FormatOneChar(digit_pair[1], cursor);
|
||||
FormatOneChar(digit_pair[0], cursor);
|
||||
}
|
||||
|
||||
template <typename Int>
|
||||
void FormatAllDigits(Int value, char** cursor) {
|
||||
assert(value >= 0);
|
||||
while (value >= 100) {
|
||||
FormatTwoDigits(value % 100, cursor);
|
||||
value /= 100;
|
||||
}
|
||||
|
||||
if (value >= 10) {
|
||||
FormatTwoDigits(value, cursor);
|
||||
} else {
|
||||
FormatOneDigit(value, cursor);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Int>
|
||||
void FormatAllDigitsLeftPadded(Int value, size_t pad, char pad_char, char** cursor) {
|
||||
auto end = *cursor - pad;
|
||||
FormatAllDigits(value, cursor);
|
||||
while (*cursor > end) {
|
||||
FormatOneChar(pad_char, cursor);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t BUFFER_SIZE>
|
||||
std::string_view ViewDigitBuffer(const std::array<char, BUFFER_SIZE>& buffer,
|
||||
char* cursor) {
|
||||
auto buffer_end = buffer.data() + BUFFER_SIZE;
|
||||
return {cursor, static_cast<size_t>(buffer_end - cursor)};
|
||||
}
|
||||
|
||||
template <typename Int, typename UInt = typename std::make_unsigned<Int>::type>
|
||||
constexpr UInt Abs(Int value) {
|
||||
return value < 0 ? ~static_cast<UInt>(value) + 1 : static_cast<UInt>(value);
|
||||
}
|
||||
|
||||
template <typename Int>
|
||||
constexpr size_t Digits10(Int value) {
|
||||
return value <= 9 ? 1 : Digits10(value / 10) + 1;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename ARROW_TYPE>
|
||||
class IntToStringFormatterMixin {
|
||||
public:
|
||||
explicit IntToStringFormatterMixin(const DataType* = NULLPTR) {}
|
||||
|
||||
using value_type = typename ARROW_TYPE::c_type;
|
||||
|
||||
template <typename Appender>
|
||||
Return<Appender> operator()(value_type value, Appender&& append) {
|
||||
constexpr size_t buffer_size =
|
||||
detail::Digits10(std::numeric_limits<value_type>::max()) + 1;
|
||||
|
||||
std::array<char, buffer_size> buffer;
|
||||
char* cursor = buffer.data() + buffer_size;
|
||||
detail::FormatAllDigits(detail::Abs(value), &cursor);
|
||||
if (value < 0) {
|
||||
detail::FormatOneChar('-', &cursor);
|
||||
}
|
||||
return append(detail::ViewDigitBuffer(buffer, cursor));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<Int8Type> : public IntToStringFormatterMixin<Int8Type> {
|
||||
using IntToStringFormatterMixin::IntToStringFormatterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<Int16Type> : public IntToStringFormatterMixin<Int16Type> {
|
||||
using IntToStringFormatterMixin::IntToStringFormatterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<Int32Type> : public IntToStringFormatterMixin<Int32Type> {
|
||||
using IntToStringFormatterMixin::IntToStringFormatterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<Int64Type> : public IntToStringFormatterMixin<Int64Type> {
|
||||
using IntToStringFormatterMixin::IntToStringFormatterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<UInt8Type> : public IntToStringFormatterMixin<UInt8Type> {
|
||||
using IntToStringFormatterMixin::IntToStringFormatterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<UInt16Type> : public IntToStringFormatterMixin<UInt16Type> {
|
||||
using IntToStringFormatterMixin::IntToStringFormatterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<UInt32Type> : public IntToStringFormatterMixin<UInt32Type> {
|
||||
using IntToStringFormatterMixin::IntToStringFormatterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<UInt64Type> : public IntToStringFormatterMixin<UInt64Type> {
|
||||
using IntToStringFormatterMixin::IntToStringFormatterMixin;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Floating-point formatting
|
||||
|
||||
class ARROW_EXPORT FloatToStringFormatter {
|
||||
public:
|
||||
FloatToStringFormatter();
|
||||
FloatToStringFormatter(int flags, const char* inf_symbol, const char* nan_symbol,
|
||||
char exp_character, int decimal_in_shortest_low,
|
||||
int decimal_in_shortest_high,
|
||||
int max_leading_padding_zeroes_in_precision_mode,
|
||||
int max_trailing_padding_zeroes_in_precision_mode);
|
||||
~FloatToStringFormatter();
|
||||
|
||||
// Returns the number of characters written
|
||||
int FormatFloat(float v, char* out_buffer, int out_size);
|
||||
int FormatFloat(double v, char* out_buffer, int out_size);
|
||||
|
||||
protected:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
template <typename ARROW_TYPE>
|
||||
class FloatToStringFormatterMixin : public FloatToStringFormatter {
|
||||
public:
|
||||
using value_type = typename ARROW_TYPE::c_type;
|
||||
|
||||
static constexpr int buffer_size = 50;
|
||||
|
||||
explicit FloatToStringFormatterMixin(const DataType* = NULLPTR) {}
|
||||
|
||||
FloatToStringFormatterMixin(int flags, const char* inf_symbol, const char* nan_symbol,
|
||||
char exp_character, int decimal_in_shortest_low,
|
||||
int decimal_in_shortest_high,
|
||||
int max_leading_padding_zeroes_in_precision_mode,
|
||||
int max_trailing_padding_zeroes_in_precision_mode)
|
||||
: FloatToStringFormatter(flags, inf_symbol, nan_symbol, exp_character,
|
||||
decimal_in_shortest_low, decimal_in_shortest_high,
|
||||
max_leading_padding_zeroes_in_precision_mode,
|
||||
max_trailing_padding_zeroes_in_precision_mode) {}
|
||||
|
||||
template <typename Appender>
|
||||
Return<Appender> operator()(value_type value, Appender&& append) {
|
||||
char buffer[buffer_size];
|
||||
int size = FormatFloat(value, buffer, buffer_size);
|
||||
return append(std::string_view(buffer, size));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<FloatType> : public FloatToStringFormatterMixin<FloatType> {
|
||||
public:
|
||||
using FloatToStringFormatterMixin::FloatToStringFormatterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<DoubleType> : public FloatToStringFormatterMixin<DoubleType> {
|
||||
public:
|
||||
using FloatToStringFormatterMixin::FloatToStringFormatterMixin;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Temporal formatting
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr size_t BufferSizeYYYY_MM_DD() {
|
||||
return 1 + detail::Digits10(99999) + 1 + detail::Digits10(12) + 1 +
|
||||
detail::Digits10(31);
|
||||
}
|
||||
|
||||
inline void FormatYYYY_MM_DD(arrow_vendored::date::year_month_day ymd, char** cursor) {
|
||||
FormatTwoDigits(static_cast<unsigned>(ymd.day()), cursor);
|
||||
FormatOneChar('-', cursor);
|
||||
FormatTwoDigits(static_cast<unsigned>(ymd.month()), cursor);
|
||||
FormatOneChar('-', cursor);
|
||||
auto year = static_cast<int>(ymd.year());
|
||||
const auto is_neg_year = year < 0;
|
||||
year = std::abs(year);
|
||||
assert(year <= 99999);
|
||||
FormatTwoDigits(year % 100, cursor);
|
||||
year /= 100;
|
||||
FormatTwoDigits(year % 100, cursor);
|
||||
if (year >= 100) {
|
||||
FormatOneDigit(year / 100, cursor);
|
||||
}
|
||||
if (is_neg_year) {
|
||||
FormatOneChar('-', cursor);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
constexpr size_t BufferSizeHH_MM_SS() {
|
||||
return detail::Digits10(23) + 1 + detail::Digits10(59) + 1 + detail::Digits10(59) + 1 +
|
||||
detail::Digits10(Duration::period::den) - 1;
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
void FormatHH_MM_SS(arrow_vendored::date::hh_mm_ss<Duration> hms, char** cursor) {
|
||||
constexpr size_t subsecond_digits = Digits10(Duration::period::den) - 1;
|
||||
if (subsecond_digits != 0) {
|
||||
FormatAllDigitsLeftPadded(hms.subseconds().count(), subsecond_digits, '0', cursor);
|
||||
FormatOneChar('.', cursor);
|
||||
}
|
||||
FormatTwoDigits(hms.seconds().count(), cursor);
|
||||
FormatOneChar(':', cursor);
|
||||
FormatTwoDigits(hms.minutes().count(), cursor);
|
||||
FormatOneChar(':', cursor);
|
||||
FormatTwoDigits(hms.hours().count(), cursor);
|
||||
}
|
||||
|
||||
// Some out-of-bound datetime values would result in erroneous printing
|
||||
// because of silent integer wraparound in the `arrow_vendored::date` library.
|
||||
//
|
||||
// To avoid such misprinting, we must therefore check the bounds explicitly.
|
||||
// The bounds correspond to start of year -32767 and end of year 32767,
|
||||
// respectively (-32768 is an invalid year value in `arrow_vendored::date`).
|
||||
//
|
||||
// Note these values are the same as documented for C++20:
|
||||
// https://en.cppreference.com/w/cpp/chrono/year_month_day/operator_days
|
||||
template <typename Unit>
|
||||
bool IsDateTimeInRange(Unit duration) {
|
||||
constexpr Unit kMinIncl =
|
||||
std::chrono::duration_cast<Unit>(arrow_vendored::date::days{-12687428});
|
||||
constexpr Unit kMaxExcl =
|
||||
std::chrono::duration_cast<Unit>(arrow_vendored::date::days{11248738});
|
||||
return duration >= kMinIncl && duration < kMaxExcl;
|
||||
}
|
||||
|
||||
// IsDateTimeInRange() specialization for nanoseconds: a 64-bit number of
|
||||
// nanoseconds cannot represent years outside of the [-32767, 32767]
|
||||
// range, and the {kMinIncl, kMaxExcl} constants above would overflow.
|
||||
constexpr bool IsDateTimeInRange(std::chrono::nanoseconds duration) { return true; }
|
||||
|
||||
template <typename Unit>
|
||||
bool IsTimeInRange(Unit duration) {
|
||||
constexpr Unit kMinIncl = std::chrono::duration_cast<Unit>(std::chrono::seconds{0});
|
||||
constexpr Unit kMaxExcl = std::chrono::duration_cast<Unit>(std::chrono::seconds{86400});
|
||||
return duration >= kMinIncl && duration < kMaxExcl;
|
||||
}
|
||||
|
||||
template <typename RawValue, typename Appender>
|
||||
Return<Appender> FormatOutOfRange(RawValue&& raw_value, Appender&& append) {
|
||||
// XXX locale-sensitive but good enough for now
|
||||
std::string formatted = "<value out of range: " + ToChars(raw_value) + ">";
|
||||
return append(std::move(formatted));
|
||||
}
|
||||
|
||||
const auto kEpoch = arrow_vendored::date::sys_days{arrow_vendored::date::jan / 1 / 1970};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <>
|
||||
class StringFormatter<DurationType> : public IntToStringFormatterMixin<DurationType> {
|
||||
using IntToStringFormatterMixin::IntToStringFormatterMixin;
|
||||
};
|
||||
|
||||
class DateToStringFormatterMixin {
|
||||
public:
|
||||
explicit DateToStringFormatterMixin(const DataType* = NULLPTR) {}
|
||||
|
||||
protected:
|
||||
template <typename Appender>
|
||||
Return<Appender> FormatDays(arrow_vendored::date::days since_epoch, Appender&& append) {
|
||||
arrow_vendored::date::sys_days timepoint_days{since_epoch};
|
||||
|
||||
constexpr size_t buffer_size = detail::BufferSizeYYYY_MM_DD();
|
||||
|
||||
std::array<char, buffer_size> buffer;
|
||||
char* cursor = buffer.data() + buffer_size;
|
||||
|
||||
detail::FormatYYYY_MM_DD(arrow_vendored::date::year_month_day{timepoint_days},
|
||||
&cursor);
|
||||
return append(detail::ViewDigitBuffer(buffer, cursor));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<Date32Type> : public DateToStringFormatterMixin {
|
||||
public:
|
||||
using value_type = typename Date32Type::c_type;
|
||||
|
||||
using DateToStringFormatterMixin::DateToStringFormatterMixin;
|
||||
|
||||
template <typename Appender>
|
||||
Return<Appender> operator()(value_type value, Appender&& append) {
|
||||
const auto since_epoch = arrow_vendored::date::days{value};
|
||||
if (!ARROW_PREDICT_TRUE(detail::IsDateTimeInRange(since_epoch))) {
|
||||
return detail::FormatOutOfRange(value, append);
|
||||
}
|
||||
return FormatDays(since_epoch, std::forward<Appender>(append));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<Date64Type> : public DateToStringFormatterMixin {
|
||||
public:
|
||||
using value_type = typename Date64Type::c_type;
|
||||
|
||||
using DateToStringFormatterMixin::DateToStringFormatterMixin;
|
||||
|
||||
template <typename Appender>
|
||||
Return<Appender> operator()(value_type value, Appender&& append) {
|
||||
const auto since_epoch = std::chrono::milliseconds{value};
|
||||
if (!ARROW_PREDICT_TRUE(detail::IsDateTimeInRange(since_epoch))) {
|
||||
return detail::FormatOutOfRange(value, append);
|
||||
}
|
||||
return FormatDays(std::chrono::duration_cast<arrow_vendored::date::days>(since_epoch),
|
||||
std::forward<Appender>(append));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<TimestampType> {
|
||||
public:
|
||||
using value_type = int64_t;
|
||||
|
||||
explicit StringFormatter(const DataType* type)
|
||||
: unit_(checked_cast<const TimestampType&>(*type).unit()) {}
|
||||
|
||||
template <typename Duration, typename Appender>
|
||||
Return<Appender> operator()(Duration, value_type value, Appender&& append) {
|
||||
using arrow_vendored::date::days;
|
||||
|
||||
const Duration since_epoch{value};
|
||||
if (!ARROW_PREDICT_TRUE(detail::IsDateTimeInRange(since_epoch))) {
|
||||
return detail::FormatOutOfRange(value, append);
|
||||
}
|
||||
|
||||
const auto timepoint = detail::kEpoch + since_epoch;
|
||||
// Round days towards zero
|
||||
// (the naive approach of using arrow_vendored::date::floor() would
|
||||
// result in UB for very large negative timestamps, similarly as
|
||||
// https://github.com/HowardHinnant/date/issues/696)
|
||||
auto timepoint_days = std::chrono::time_point_cast<days>(timepoint);
|
||||
Duration since_midnight;
|
||||
if (timepoint_days <= timepoint) {
|
||||
// Year >= 1970
|
||||
since_midnight = timepoint - timepoint_days;
|
||||
} else {
|
||||
// Year < 1970
|
||||
since_midnight = days(1) - (timepoint_days - timepoint);
|
||||
timepoint_days -= days(1);
|
||||
}
|
||||
|
||||
constexpr size_t buffer_size =
|
||||
detail::BufferSizeYYYY_MM_DD() + 1 + detail::BufferSizeHH_MM_SS<Duration>();
|
||||
|
||||
std::array<char, buffer_size> buffer;
|
||||
char* cursor = buffer.data() + buffer_size;
|
||||
|
||||
detail::FormatHH_MM_SS(arrow_vendored::date::make_time(since_midnight), &cursor);
|
||||
detail::FormatOneChar(' ', &cursor);
|
||||
detail::FormatYYYY_MM_DD(timepoint_days, &cursor);
|
||||
return append(detail::ViewDigitBuffer(buffer, cursor));
|
||||
}
|
||||
|
||||
template <typename Appender>
|
||||
Return<Appender> operator()(value_type value, Appender&& append) {
|
||||
return util::VisitDuration(unit_, *this, value, std::forward<Appender>(append));
|
||||
}
|
||||
|
||||
private:
|
||||
TimeUnit::type unit_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class StringFormatter<T, enable_if_time<T>> {
|
||||
public:
|
||||
using value_type = typename T::c_type;
|
||||
|
||||
explicit StringFormatter(const DataType* type)
|
||||
: unit_(checked_cast<const T&>(*type).unit()) {}
|
||||
|
||||
template <typename Duration, typename Appender>
|
||||
Return<Appender> operator()(Duration, value_type count, Appender&& append) {
|
||||
const Duration since_midnight{count};
|
||||
if (!ARROW_PREDICT_TRUE(detail::IsTimeInRange(since_midnight))) {
|
||||
return detail::FormatOutOfRange(count, append);
|
||||
}
|
||||
|
||||
constexpr size_t buffer_size = detail::BufferSizeHH_MM_SS<Duration>();
|
||||
|
||||
std::array<char, buffer_size> buffer;
|
||||
char* cursor = buffer.data() + buffer_size;
|
||||
|
||||
detail::FormatHH_MM_SS(arrow_vendored::date::make_time(since_midnight), &cursor);
|
||||
return append(detail::ViewDigitBuffer(buffer, cursor));
|
||||
}
|
||||
|
||||
template <typename Appender>
|
||||
Return<Appender> operator()(value_type value, Appender&& append) {
|
||||
return util::VisitDuration(unit_, *this, value, std::forward<Appender>(append));
|
||||
}
|
||||
|
||||
private:
|
||||
TimeUnit::type unit_;
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<MonthIntervalType> {
|
||||
public:
|
||||
using value_type = MonthIntervalType::c_type;
|
||||
|
||||
explicit StringFormatter(const DataType*) {}
|
||||
|
||||
template <typename Appender>
|
||||
Return<Appender> operator()(value_type interval, Appender&& append) {
|
||||
constexpr size_t buffer_size =
|
||||
/*'m'*/ 3 + /*negative signs*/ 1 +
|
||||
/*months*/ detail::Digits10(std::numeric_limits<value_type>::max());
|
||||
std::array<char, buffer_size> buffer;
|
||||
char* cursor = buffer.data() + buffer_size;
|
||||
|
||||
detail::FormatOneChar('M', &cursor);
|
||||
detail::FormatAllDigits(detail::Abs(interval), &cursor);
|
||||
if (interval < 0) detail::FormatOneChar('-', &cursor);
|
||||
|
||||
return append(detail::ViewDigitBuffer(buffer, cursor));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<DayTimeIntervalType> {
|
||||
public:
|
||||
using value_type = DayTimeIntervalType::DayMilliseconds;
|
||||
|
||||
explicit StringFormatter(const DataType*) {}
|
||||
|
||||
template <typename Appender>
|
||||
Return<Appender> operator()(value_type interval, Appender&& append) {
|
||||
constexpr size_t buffer_size =
|
||||
/*d, ms*/ 3 + /*negative signs*/ 2 +
|
||||
/*days/milliseconds*/ 2 * detail::Digits10(std::numeric_limits<int32_t>::max());
|
||||
std::array<char, buffer_size> buffer;
|
||||
char* cursor = buffer.data() + buffer_size;
|
||||
|
||||
detail::FormatOneChar('s', &cursor);
|
||||
detail::FormatOneChar('m', &cursor);
|
||||
detail::FormatAllDigits(detail::Abs(interval.milliseconds), &cursor);
|
||||
if (interval.milliseconds < 0) detail::FormatOneChar('-', &cursor);
|
||||
|
||||
detail::FormatOneChar('d', &cursor);
|
||||
detail::FormatAllDigits(detail::Abs(interval.days), &cursor);
|
||||
if (interval.days < 0) detail::FormatOneChar('-', &cursor);
|
||||
|
||||
return append(detail::ViewDigitBuffer(buffer, cursor));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class StringFormatter<MonthDayNanoIntervalType> {
|
||||
public:
|
||||
using value_type = MonthDayNanoIntervalType::MonthDayNanos;
|
||||
|
||||
explicit StringFormatter(const DataType*) {}
|
||||
|
||||
template <typename Appender>
|
||||
Return<Appender> operator()(value_type interval, Appender&& append) {
|
||||
constexpr size_t buffer_size =
|
||||
/*m, d, ns*/ 4 + /*negative signs*/ 3 +
|
||||
/*months/days*/ 2 * detail::Digits10(std::numeric_limits<int32_t>::max()) +
|
||||
/*nanoseconds*/ detail::Digits10(std::numeric_limits<int64_t>::max());
|
||||
std::array<char, buffer_size> buffer;
|
||||
char* cursor = buffer.data() + buffer_size;
|
||||
|
||||
detail::FormatOneChar('s', &cursor);
|
||||
detail::FormatOneChar('n', &cursor);
|
||||
detail::FormatAllDigits(detail::Abs(interval.nanoseconds), &cursor);
|
||||
if (interval.nanoseconds < 0) detail::FormatOneChar('-', &cursor);
|
||||
|
||||
detail::FormatOneChar('d', &cursor);
|
||||
detail::FormatAllDigits(detail::Abs(interval.days), &cursor);
|
||||
if (interval.days < 0) detail::FormatOneChar('-', &cursor);
|
||||
|
||||
detail::FormatOneChar('M', &cursor);
|
||||
detail::FormatAllDigits(detail::Abs(interval.months), &cursor);
|
||||
if (interval.months < 0) detail::FormatOneChar('-', &cursor);
|
||||
|
||||
return append(detail::ViewDigitBuffer(buffer, cursor));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,160 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/util/macros.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
struct Empty {
|
||||
static Result<Empty> ToResult(Status s) {
|
||||
if (ARROW_PREDICT_TRUE(s.ok())) {
|
||||
return Empty{};
|
||||
}
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper struct for examining lambdas and other callables.
|
||||
/// TODO(ARROW-12655) support function pointers
|
||||
struct call_traits {
|
||||
public:
|
||||
template <typename R, typename... A>
|
||||
static std::false_type is_overloaded_impl(R(A...));
|
||||
|
||||
template <typename F>
|
||||
static std::false_type is_overloaded_impl(decltype(&F::operator())*);
|
||||
|
||||
template <typename F>
|
||||
static std::true_type is_overloaded_impl(...);
|
||||
|
||||
template <typename F, typename R, typename... A>
|
||||
static R return_type_impl(R (F::*)(A...));
|
||||
|
||||
template <typename F, typename R, typename... A>
|
||||
static R return_type_impl(R (F::*)(A...) const);
|
||||
|
||||
template <std::size_t I, typename F, typename R, typename... A>
|
||||
static typename std::tuple_element<I, std::tuple<A...>>::type argument_type_impl(
|
||||
R (F::*)(A...));
|
||||
|
||||
template <std::size_t I, typename F, typename R, typename... A>
|
||||
static typename std::tuple_element<I, std::tuple<A...>>::type argument_type_impl(
|
||||
R (F::*)(A...) const);
|
||||
|
||||
template <std::size_t I, typename F, typename R, typename... A>
|
||||
static typename std::tuple_element<I, std::tuple<A...>>::type argument_type_impl(
|
||||
R (F::*)(A...) &&);
|
||||
|
||||
template <typename F, typename R, typename... A>
|
||||
static std::integral_constant<int, sizeof...(A)> argument_count_impl(R (F::*)(A...));
|
||||
|
||||
template <typename F, typename R, typename... A>
|
||||
static std::integral_constant<int, sizeof...(A)> argument_count_impl(R (F::*)(A...)
|
||||
const);
|
||||
|
||||
template <typename F, typename R, typename... A>
|
||||
static std::integral_constant<int, sizeof...(A)> argument_count_impl(R (F::*)(A...) &&);
|
||||
|
||||
/// bool constant indicating whether F is a callable with more than one possible
|
||||
/// signature. Will be true_type for objects which define multiple operator() or which
|
||||
/// define a template operator()
|
||||
template <typename F>
|
||||
using is_overloaded =
|
||||
decltype(is_overloaded_impl<typename std::decay<F>::type>(NULLPTR));
|
||||
|
||||
template <typename F, typename T = void>
|
||||
using enable_if_overloaded = typename std::enable_if<is_overloaded<F>::value, T>::type;
|
||||
|
||||
template <typename F, typename T = void>
|
||||
using disable_if_overloaded =
|
||||
typename std::enable_if<!is_overloaded<F>::value, T>::type;
|
||||
|
||||
/// If F is not overloaded, the argument types of its call operator can be
|
||||
/// extracted via call_traits::argument_type<Index, F>
|
||||
template <std::size_t I, typename F>
|
||||
using argument_type = decltype(argument_type_impl<I>(&std::decay<F>::type::operator()));
|
||||
|
||||
template <typename F>
|
||||
using argument_count = decltype(argument_count_impl(&std::decay<F>::type::operator()));
|
||||
|
||||
template <typename F>
|
||||
using return_type = decltype(return_type_impl(&std::decay<F>::type::operator()));
|
||||
|
||||
template <typename F, typename T, typename RT = T>
|
||||
using enable_if_return =
|
||||
typename std::enable_if<std::is_same<return_type<F>, T>::value, RT>;
|
||||
|
||||
template <typename T, typename R = void>
|
||||
using enable_if_empty = typename std::enable_if<std::is_same<T, Empty>::value, R>::type;
|
||||
|
||||
template <typename T, typename R = void>
|
||||
using enable_if_not_empty =
|
||||
typename std::enable_if<!std::is_same<T, Empty>::value, R>::type;
|
||||
};
|
||||
|
||||
/// A type erased callable object which may only be invoked once.
|
||||
/// It can be constructed from any lambda which matches the provided call signature.
|
||||
/// Invoking it results in destruction of the lambda, freeing any state/references
|
||||
/// immediately. Invoking a default constructed FnOnce or one which has already been
|
||||
/// invoked will segfault.
|
||||
template <typename Signature>
|
||||
class FnOnce;
|
||||
|
||||
template <typename R, typename... A>
|
||||
class FnOnce<R(A...)> {
|
||||
public:
|
||||
FnOnce() = default;
|
||||
|
||||
template <typename Fn,
|
||||
typename = typename std::enable_if<std::is_convertible<
|
||||
decltype(std::declval<Fn&&>()(std::declval<A>()...)), R>::value>::type>
|
||||
FnOnce(Fn fn) : impl_(new FnImpl<Fn>(std::move(fn))) { // NOLINT runtime/explicit
|
||||
}
|
||||
|
||||
explicit operator bool() const { return impl_ != NULLPTR; }
|
||||
|
||||
R operator()(A... a) && {
|
||||
auto bye = std::move(impl_);
|
||||
return bye->invoke(std::forward<A&&>(a)...);
|
||||
}
|
||||
|
||||
private:
|
||||
struct Impl {
|
||||
virtual ~Impl() = default;
|
||||
virtual R invoke(A&&... a) = 0;
|
||||
};
|
||||
|
||||
template <typename Fn>
|
||||
struct FnImpl : Impl {
|
||||
explicit FnImpl(Fn fn) : fn_(std::move(fn)) {}
|
||||
R invoke(A&&... a) override { return std::move(fn_)(std::forward<A&&>(a)...); }
|
||||
Fn fn_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,882 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/type_fwd.h"
|
||||
#include "arrow/type_traits.h"
|
||||
#include "arrow/util/config.h"
|
||||
#include "arrow/util/functional.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/tracing.h"
|
||||
#include "arrow/util/type_fwd.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
template <typename>
|
||||
struct EnsureFuture;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename>
|
||||
struct is_future : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_future<Future<T>> : std::true_type {};
|
||||
|
||||
template <typename Signature, typename Enable = void>
|
||||
struct result_of;
|
||||
|
||||
template <typename Fn, typename... A>
|
||||
struct result_of<Fn(A...),
|
||||
internal::void_t<decltype(std::declval<Fn>()(std::declval<A>()...))>> {
|
||||
using type = decltype(std::declval<Fn>()(std::declval<A>()...));
|
||||
};
|
||||
|
||||
template <typename Signature>
|
||||
using result_of_t = typename result_of<Signature>::type;
|
||||
|
||||
// Helper to find the synchronous counterpart for a Future
|
||||
template <typename T>
|
||||
struct SyncType {
|
||||
using type = Result<T>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SyncType<internal::Empty> {
|
||||
using type = Status;
|
||||
};
|
||||
|
||||
template <typename Fn>
|
||||
using first_arg_is_status =
|
||||
std::is_same<typename std::decay<internal::call_traits::argument_type<0, Fn>>::type,
|
||||
Status>;
|
||||
|
||||
template <typename Fn, typename Then, typename Else,
|
||||
typename Count = internal::call_traits::argument_count<Fn>>
|
||||
using if_has_no_args = typename std::conditional<Count::value == 0, Then, Else>::type;
|
||||
|
||||
/// Creates a callback that can be added to a future to mark a `dest` future finished
|
||||
template <typename Source, typename Dest, bool SourceEmpty = Source::is_empty,
|
||||
bool DestEmpty = Dest::is_empty>
|
||||
struct MarkNextFinished {};
|
||||
|
||||
/// If the source and dest are both empty we can pass on the status
|
||||
template <typename Source, typename Dest>
|
||||
struct MarkNextFinished<Source, Dest, true, true> {
|
||||
void operator()(const Status& status) && { next.MarkFinished(status); }
|
||||
Dest next;
|
||||
};
|
||||
|
||||
/// If the source is not empty but the dest is then we can take the
|
||||
/// status out of the result
|
||||
template <typename Source, typename Dest>
|
||||
struct MarkNextFinished<Source, Dest, false, true> {
|
||||
void operator()(const Result<typename Source::ValueType>& res) && {
|
||||
next.MarkFinished(internal::Empty::ToResult(res.status()));
|
||||
}
|
||||
Dest next;
|
||||
};
|
||||
|
||||
/// If neither are empty we pass on the result
|
||||
template <typename Source, typename Dest>
|
||||
struct MarkNextFinished<Source, Dest, false, false> {
|
||||
void operator()(const Result<typename Source::ValueType>& res) && {
|
||||
next.MarkFinished(res);
|
||||
}
|
||||
Dest next;
|
||||
};
|
||||
|
||||
/// Helper that contains information about how to apply a continuation
|
||||
struct ContinueFuture {
|
||||
template <typename Return>
|
||||
struct ForReturnImpl;
|
||||
|
||||
template <typename Return>
|
||||
using ForReturn = typename ForReturnImpl<Return>::type;
|
||||
|
||||
template <typename Signature>
|
||||
using ForSignature = ForReturn<result_of_t<Signature>>;
|
||||
|
||||
// If the callback returns void then we return Future<> that always finishes OK.
|
||||
template <typename ContinueFunc, typename... Args,
|
||||
typename ContinueResult = result_of_t<ContinueFunc && (Args && ...)>,
|
||||
typename NextFuture = ForReturn<ContinueResult>>
|
||||
typename std::enable_if<std::is_void<ContinueResult>::value>::type operator()(
|
||||
NextFuture next, ContinueFunc&& f, Args&&... a) const {
|
||||
std::forward<ContinueFunc>(f)(std::forward<Args>(a)...);
|
||||
next.MarkFinished();
|
||||
}
|
||||
|
||||
/// If the callback returns a non-future then we return Future<T>
|
||||
/// and mark the future finished with the callback result. It will get promoted
|
||||
/// to Result<T> as part of MarkFinished if it isn't already.
|
||||
///
|
||||
/// If the callback returns Status and we return Future<> then also send the callback
|
||||
/// result as-is to the destination future.
|
||||
template <typename ContinueFunc, typename... Args,
|
||||
typename ContinueResult = result_of_t<ContinueFunc && (Args && ...)>,
|
||||
typename NextFuture = ForReturn<ContinueResult>>
|
||||
typename std::enable_if<
|
||||
!std::is_void<ContinueResult>::value && !is_future<ContinueResult>::value &&
|
||||
(!NextFuture::is_empty || std::is_same<ContinueResult, Status>::value)>::type
|
||||
operator()(NextFuture next, ContinueFunc&& f, Args&&... a) const {
|
||||
next.MarkFinished(std::forward<ContinueFunc>(f)(std::forward<Args>(a)...));
|
||||
}
|
||||
|
||||
/// If the callback returns a Result and the next future is Future<> then we mark
|
||||
/// the future finished with the callback result.
|
||||
///
|
||||
/// It may seem odd that the next future is Future<> when the callback returns a
|
||||
/// result but this can occur if the OnFailure callback returns a result while the
|
||||
/// OnSuccess callback is void/Status (e.g. you would get this calling the one-arg
|
||||
/// version of Then with an OnSuccess callback that returns void)
|
||||
template <typename ContinueFunc, typename... Args,
|
||||
typename ContinueResult = result_of_t<ContinueFunc && (Args && ...)>,
|
||||
typename NextFuture = ForReturn<ContinueResult>>
|
||||
typename std::enable_if<!std::is_void<ContinueResult>::value &&
|
||||
!is_future<ContinueResult>::value && NextFuture::is_empty &&
|
||||
!std::is_same<ContinueResult, Status>::value>::type
|
||||
operator()(NextFuture next, ContinueFunc&& f, Args&&... a) const {
|
||||
next.MarkFinished(std::forward<ContinueFunc>(f)(std::forward<Args>(a)...).status());
|
||||
}
|
||||
|
||||
/// If the callback returns a Future<T> then we return Future<T>. We create a new
|
||||
/// future and add a callback to the future given to us by the user that forwards the
|
||||
/// result to the future we just created
|
||||
template <typename ContinueFunc, typename... Args,
|
||||
typename ContinueResult = result_of_t<ContinueFunc && (Args && ...)>,
|
||||
typename NextFuture = ForReturn<ContinueResult>>
|
||||
typename std::enable_if<is_future<ContinueResult>::value>::type operator()(
|
||||
NextFuture next, ContinueFunc&& f, Args&&... a) const {
|
||||
ContinueResult signal_to_complete_next =
|
||||
std::forward<ContinueFunc>(f)(std::forward<Args>(a)...);
|
||||
MarkNextFinished<ContinueResult, NextFuture> callback{std::move(next)};
|
||||
signal_to_complete_next.AddCallback(std::move(callback));
|
||||
}
|
||||
|
||||
/// Helpers to conditionally ignore arguments to ContinueFunc
|
||||
template <typename ContinueFunc, typename NextFuture, typename... Args>
|
||||
void IgnoringArgsIf(std::true_type, NextFuture&& next, ContinueFunc&& f,
|
||||
Args&&...) const {
|
||||
operator()(std::forward<NextFuture>(next), std::forward<ContinueFunc>(f));
|
||||
}
|
||||
template <typename ContinueFunc, typename NextFuture, typename... Args>
|
||||
void IgnoringArgsIf(std::false_type, NextFuture&& next, ContinueFunc&& f,
|
||||
Args&&... a) const {
|
||||
operator()(std::forward<NextFuture>(next), std::forward<ContinueFunc>(f),
|
||||
std::forward<Args>(a)...);
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper struct which tells us what kind of Future gets returned from `Then` based on
|
||||
/// the return type of the OnSuccess callback
|
||||
template <>
|
||||
struct ContinueFuture::ForReturnImpl<void> {
|
||||
using type = Future<>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ContinueFuture::ForReturnImpl<Status> {
|
||||
using type = Future<>;
|
||||
};
|
||||
|
||||
template <typename R>
|
||||
struct ContinueFuture::ForReturnImpl {
|
||||
using type = Future<R>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ContinueFuture::ForReturnImpl<Result<T>> {
|
||||
using type = Future<T>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ContinueFuture::ForReturnImpl<Future<T>> {
|
||||
using type = Future<T>;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// A Future's execution or completion status
|
||||
enum class FutureState : int8_t { PENDING, SUCCESS, FAILURE };
|
||||
|
||||
inline bool IsFutureFinished(FutureState state) { return state != FutureState::PENDING; }
|
||||
|
||||
/// \brief Describe whether the callback should be scheduled or run synchronously
|
||||
enum class ShouldSchedule {
|
||||
/// Always run the callback synchronously (the default)
|
||||
Never = 0,
|
||||
/// Schedule a new task only if the future is not finished when the
|
||||
/// callback is added
|
||||
IfUnfinished = 1,
|
||||
/// Always schedule the callback as a new task
|
||||
Always = 2,
|
||||
/// Schedule a new task only if it would run on an executor other than
|
||||
/// the specified executor.
|
||||
IfDifferentExecutor = 3,
|
||||
};
|
||||
|
||||
/// \brief Options that control how a continuation is run
|
||||
struct CallbackOptions {
|
||||
/// Describe whether the callback should be run synchronously or scheduled
|
||||
ShouldSchedule should_schedule = ShouldSchedule::Never;
|
||||
/// If the callback is scheduled then this is the executor it should be scheduled
|
||||
/// on. If this is NULL then should_schedule must be Never
|
||||
internal::Executor* executor = NULLPTR;
|
||||
|
||||
static CallbackOptions Defaults() { return {}; }
|
||||
};
|
||||
|
||||
// Untyped private implementation
|
||||
class ARROW_EXPORT FutureImpl : public std::enable_shared_from_this<FutureImpl> {
|
||||
public:
|
||||
FutureImpl();
|
||||
virtual ~FutureImpl() = default;
|
||||
|
||||
FutureState state() { return state_.load(); }
|
||||
|
||||
static std::unique_ptr<FutureImpl> Make();
|
||||
static std::unique_ptr<FutureImpl> MakeFinished(FutureState state);
|
||||
|
||||
#ifdef ARROW_WITH_OPENTELEMETRY
|
||||
void SetSpan(util::tracing::Span* span) { span_ = span; }
|
||||
#endif
|
||||
|
||||
// Future API
|
||||
void MarkFinished();
|
||||
void MarkFailed();
|
||||
void Wait();
|
||||
bool Wait(double seconds);
|
||||
template <typename ValueType>
|
||||
Result<ValueType>* CastResult() const {
|
||||
return static_cast<Result<ValueType>*>(result_.get());
|
||||
}
|
||||
|
||||
using Callback = internal::FnOnce<void(const FutureImpl& impl)>;
|
||||
void AddCallback(Callback callback, CallbackOptions opts);
|
||||
bool TryAddCallback(const std::function<Callback()>& callback_factory,
|
||||
CallbackOptions opts);
|
||||
|
||||
std::atomic<FutureState> state_{FutureState::PENDING};
|
||||
|
||||
// Type erased storage for arbitrary results
|
||||
// XXX small objects could be stored inline instead of boxed in a pointer
|
||||
using Storage = std::unique_ptr<void, void (*)(void*)>;
|
||||
Storage result_{NULLPTR, NULLPTR};
|
||||
|
||||
struct CallbackRecord {
|
||||
Callback callback;
|
||||
CallbackOptions options;
|
||||
};
|
||||
std::vector<CallbackRecord> callbacks_;
|
||||
#ifdef ARROW_WITH_OPENTELEMETRY
|
||||
util::tracing::Span* span_ = NULLPTR;
|
||||
#endif
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public API
|
||||
|
||||
/// \brief EXPERIMENTAL A std::future-like class with more functionality.
|
||||
///
|
||||
/// A Future represents the results of a past or future computation.
|
||||
/// The Future API has two sides: a producer side and a consumer side.
|
||||
///
|
||||
/// The producer API allows creating a Future and setting its result or
|
||||
/// status, possibly after running a computation function.
|
||||
///
|
||||
/// The consumer API allows querying a Future's current state, wait for it
|
||||
/// to complete, and composing futures with callbacks.
|
||||
template <typename T>
|
||||
class [[nodiscard]] Future {
|
||||
public:
|
||||
using ValueType = T;
|
||||
using SyncType = typename detail::SyncType<T>::type;
|
||||
static constexpr bool is_empty = std::is_same<T, internal::Empty>::value;
|
||||
// The default constructor creates an invalid Future. Use Future::Make()
|
||||
// for a valid Future. This constructor is mostly for the convenience
|
||||
// of being able to presize a vector of Futures.
|
||||
Future() = default;
|
||||
|
||||
#ifdef ARROW_WITH_OPENTELEMETRY
|
||||
void SetSpan(util::tracing::Span* span) { impl_->SetSpan(span); }
|
||||
#endif
|
||||
|
||||
// Consumer API
|
||||
|
||||
bool is_valid() const { return impl_ != NULLPTR; }
|
||||
|
||||
/// \brief Return the Future's current state
|
||||
///
|
||||
/// A return value of PENDING is only indicative, as the Future can complete
|
||||
/// concurrently. A return value of FAILURE or SUCCESS is definitive, though.
|
||||
FutureState state() const {
|
||||
CheckValid();
|
||||
return impl_->state();
|
||||
}
|
||||
|
||||
/// \brief Whether the Future is finished
|
||||
///
|
||||
/// A false return value is only indicative, as the Future can complete
|
||||
/// concurrently. A true return value is definitive, though.
|
||||
bool is_finished() const {
|
||||
CheckValid();
|
||||
return IsFutureFinished(impl_->state());
|
||||
}
|
||||
|
||||
/// \brief Wait for the Future to complete and return its Result
|
||||
const Result<ValueType>& result() const& {
|
||||
Wait();
|
||||
return *GetResult();
|
||||
}
|
||||
|
||||
/// \brief Returns an rvalue to the result. This method is potentially unsafe
|
||||
///
|
||||
/// The future is not the unique owner of the result, copies of a future will
|
||||
/// also point to the same result. You must make sure that no other copies
|
||||
/// of the future exist. Attempts to add callbacks after you move the result
|
||||
/// will result in undefined behavior.
|
||||
Result<ValueType>&& MoveResult() {
|
||||
Wait();
|
||||
return std::move(*GetResult());
|
||||
}
|
||||
|
||||
/// \brief Wait for the Future to complete and return its Status
|
||||
const Status& status() const { return result().status(); }
|
||||
|
||||
/// \brief Future<T> is convertible to Future<>, which views only the
|
||||
/// Status of the original. Marking the returned Future Finished is not supported.
|
||||
explicit operator Future<>() const {
|
||||
Future<> status_future;
|
||||
status_future.impl_ = impl_;
|
||||
return status_future;
|
||||
}
|
||||
|
||||
/// \brief Wait for the Future to complete
|
||||
void Wait() const {
|
||||
CheckValid();
|
||||
impl_->Wait();
|
||||
}
|
||||
|
||||
/// \brief Wait for the Future to complete, or for the timeout to expire
|
||||
///
|
||||
/// `true` is returned if the Future completed, `false` if the timeout expired.
|
||||
/// Note a `false` value is only indicative, as the Future can complete
|
||||
/// concurrently.
|
||||
bool Wait(double seconds) const {
|
||||
CheckValid();
|
||||
return impl_->Wait(seconds);
|
||||
}
|
||||
|
||||
// Producer API
|
||||
|
||||
/// \brief Producer API: mark Future finished
|
||||
///
|
||||
/// The Future's result is set to `res`.
|
||||
void MarkFinished(Result<ValueType> res) { DoMarkFinished(std::move(res)); }
|
||||
|
||||
/// \brief Mark a Future<> completed with the provided Status.
|
||||
template <typename E = ValueType, typename = typename std::enable_if<
|
||||
std::is_same<E, internal::Empty>::value>::type>
|
||||
void MarkFinished(Status s = Status::OK()) {
|
||||
return DoMarkFinished(E::ToResult(std::move(s)));
|
||||
}
|
||||
|
||||
/// \brief Producer API: instantiate a valid Future
|
||||
///
|
||||
/// The Future's state is initialized with PENDING. If you are creating a future with
|
||||
/// this method you must ensure that future is eventually completed (with success or
|
||||
/// failure). Creating a future, returning it, and never completing the future can lead
|
||||
/// to memory leaks (for example, see Loop).
|
||||
static Future Make() {
|
||||
Future fut;
|
||||
fut.impl_ = FutureImpl::Make();
|
||||
return fut;
|
||||
}
|
||||
|
||||
/// \brief Producer API: instantiate a finished Future
|
||||
static Future<ValueType> MakeFinished(Result<ValueType> res) {
|
||||
Future<ValueType> fut;
|
||||
fut.InitializeFromResult(std::move(res));
|
||||
return fut;
|
||||
}
|
||||
|
||||
/// \brief Make a finished Future<> with the provided Status.
|
||||
template <typename E = ValueType, typename = typename std::enable_if<
|
||||
std::is_same<E, internal::Empty>::value>::type>
|
||||
static Future<> MakeFinished(Status s = Status::OK()) {
|
||||
return MakeFinished(E::ToResult(std::move(s)));
|
||||
}
|
||||
|
||||
struct WrapResultyOnComplete {
|
||||
template <typename OnComplete>
|
||||
struct Callback {
|
||||
void operator()(const FutureImpl& impl) && {
|
||||
std::move(on_complete)(*impl.CastResult<ValueType>());
|
||||
}
|
||||
OnComplete on_complete;
|
||||
};
|
||||
};
|
||||
|
||||
struct WrapStatusyOnComplete {
|
||||
template <typename OnComplete>
|
||||
struct Callback {
|
||||
static_assert(std::is_same<internal::Empty, ValueType>::value,
|
||||
"Only callbacks for Future<> should accept Status and not Result");
|
||||
|
||||
void operator()(const FutureImpl& impl) && {
|
||||
std::move(on_complete)(impl.CastResult<ValueType>()->status());
|
||||
}
|
||||
OnComplete on_complete;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename OnComplete>
|
||||
using WrapOnComplete = typename std::conditional<
|
||||
detail::first_arg_is_status<OnComplete>::value, WrapStatusyOnComplete,
|
||||
WrapResultyOnComplete>::type::template Callback<OnComplete>;
|
||||
|
||||
/// \brief Consumer API: Register a callback to run when this future completes
|
||||
///
|
||||
/// The callback should receive the result of the future (const Result<T>&)
|
||||
/// For a void or statusy future this should be (const Status&)
|
||||
///
|
||||
/// There is no guarantee to the order in which callbacks will run. In
|
||||
/// particular, callbacks added while the future is being marked complete
|
||||
/// may be executed immediately, ahead of, or even the same time as, other
|
||||
/// callbacks that have been previously added.
|
||||
///
|
||||
/// WARNING: callbacks may hold arbitrary references, including cyclic references.
|
||||
/// Since callbacks will only be destroyed after they are invoked, this can lead to
|
||||
/// memory leaks if a Future is never marked finished (abandoned):
|
||||
///
|
||||
/// {
|
||||
/// auto fut = Future<>::Make();
|
||||
/// fut.AddCallback([fut]() {});
|
||||
/// }
|
||||
///
|
||||
/// In this example `fut` falls out of scope but is not destroyed because it holds a
|
||||
/// cyclic reference to itself through the callback.
|
||||
template <typename OnComplete, typename Callback = WrapOnComplete<OnComplete>>
|
||||
void AddCallback(OnComplete on_complete,
|
||||
CallbackOptions opts = CallbackOptions::Defaults()) const {
|
||||
// We know impl_ will not be dangling when invoking callbacks because at least one
|
||||
// thread will be waiting for MarkFinished to return. Thus it's safe to keep a
|
||||
// weak reference to impl_ here
|
||||
impl_->AddCallback(Callback{std::move(on_complete)}, opts);
|
||||
}
|
||||
|
||||
/// \brief Overload of AddCallback that will return false instead of running
|
||||
/// synchronously
|
||||
///
|
||||
/// This overload will guarantee the callback is never run synchronously. If the future
|
||||
/// is already finished then it will simply return false. This can be useful to avoid
|
||||
/// stack overflow in a situation where you have recursive Futures. For an example
|
||||
/// see the Loop function
|
||||
///
|
||||
/// Takes in a callback factory function to allow moving callbacks (the factory function
|
||||
/// will only be called if the callback can successfully be added)
|
||||
///
|
||||
/// Returns true if a callback was actually added and false if the callback failed
|
||||
/// to add because the future was marked complete.
|
||||
template <typename CallbackFactory,
|
||||
typename OnComplete = detail::result_of_t<CallbackFactory()>,
|
||||
typename Callback = WrapOnComplete<OnComplete>>
|
||||
bool TryAddCallback(CallbackFactory callback_factory,
|
||||
CallbackOptions opts = CallbackOptions::Defaults()) const {
|
||||
return impl_->TryAddCallback([&]() { return Callback{callback_factory()}; }, opts);
|
||||
}
|
||||
|
||||
template <typename OnSuccess, typename OnFailure>
|
||||
struct ThenOnComplete {
|
||||
static constexpr bool has_no_args =
|
||||
internal::call_traits::argument_count<OnSuccess>::value == 0;
|
||||
|
||||
using ContinuedFuture = detail::ContinueFuture::ForSignature<
|
||||
detail::if_has_no_args<OnSuccess, OnSuccess && (), OnSuccess && (const T&)>>;
|
||||
|
||||
static_assert(
|
||||
std::is_same<detail::ContinueFuture::ForSignature<OnFailure && (const Status&)>,
|
||||
ContinuedFuture>::value,
|
||||
"OnSuccess and OnFailure must continue with the same future type");
|
||||
|
||||
struct DummyOnSuccess {
|
||||
void operator()(const T&);
|
||||
};
|
||||
using OnSuccessArg = typename std::decay<internal::call_traits::argument_type<
|
||||
0, detail::if_has_no_args<OnSuccess, DummyOnSuccess, OnSuccess>>>::type;
|
||||
|
||||
static_assert(
|
||||
!std::is_same<OnSuccessArg, typename EnsureResult<OnSuccessArg>::type>::value,
|
||||
"OnSuccess' argument should not be a Result");
|
||||
|
||||
void operator()(const Result<T>& result) && {
|
||||
detail::ContinueFuture continue_future;
|
||||
if (ARROW_PREDICT_TRUE(result.ok())) {
|
||||
// move on_failure to a(n immediately destroyed) temporary to free its resources
|
||||
ARROW_UNUSED(OnFailure(std::move(on_failure)));
|
||||
continue_future.IgnoringArgsIf(
|
||||
detail::if_has_no_args<OnSuccess, std::true_type, std::false_type>{},
|
||||
std::move(next), std::move(on_success), result.ValueOrDie());
|
||||
} else {
|
||||
ARROW_UNUSED(OnSuccess(std::move(on_success)));
|
||||
continue_future(std::move(next), std::move(on_failure), result.status());
|
||||
}
|
||||
}
|
||||
|
||||
OnSuccess on_success;
|
||||
OnFailure on_failure;
|
||||
ContinuedFuture next;
|
||||
};
|
||||
|
||||
template <typename OnSuccess>
|
||||
struct PassthruOnFailure {
|
||||
using ContinuedFuture = detail::ContinueFuture::ForSignature<
|
||||
detail::if_has_no_args<OnSuccess, OnSuccess && (), OnSuccess && (const T&)>>;
|
||||
|
||||
Result<typename ContinuedFuture::ValueType> operator()(const Status& s) { return s; }
|
||||
};
|
||||
|
||||
/// \brief Consumer API: Register a continuation to run when this future completes
|
||||
///
|
||||
/// The continuation will run in the same thread that called MarkFinished (whatever
|
||||
/// callback is registered with this function will run before MarkFinished returns).
|
||||
/// Avoid long-running callbacks in favor of submitting a task to an Executor and
|
||||
/// returning the future.
|
||||
///
|
||||
/// Two callbacks are supported:
|
||||
/// - OnSuccess, called with the result (const ValueType&) on successful completion.
|
||||
/// for an empty future this will be called with nothing ()
|
||||
/// - OnFailure, called with the error (const Status&) on failed completion.
|
||||
/// This callback is optional and defaults to a passthru of any errors.
|
||||
///
|
||||
/// Then() returns a Future whose ValueType is derived from the return type of the
|
||||
/// callbacks. If a callback returns:
|
||||
/// - void, a Future<> will be returned which will completes successfully as soon
|
||||
/// as the callback runs.
|
||||
/// - Status, a Future<> will be returned which will complete with the returned Status
|
||||
/// as soon as the callback runs.
|
||||
/// - V or Result<V>, a Future<V> will be returned which will complete with the result
|
||||
/// of invoking the callback as soon as the callback runs.
|
||||
/// - Future<V>, a Future<V> will be returned which will be marked complete when the
|
||||
/// future returned by the callback completes (and will complete with the same
|
||||
/// result).
|
||||
///
|
||||
/// The continued Future type must be the same for both callbacks.
|
||||
///
|
||||
/// Note that OnFailure can swallow errors, allowing continued Futures to successfully
|
||||
/// complete even if this Future fails.
|
||||
///
|
||||
/// If this future is already completed then the callback will be run immediately
|
||||
/// and the returned future may already be marked complete.
|
||||
///
|
||||
/// See AddCallback for general considerations when writing callbacks.
|
||||
template <typename OnSuccess, typename OnFailure = PassthruOnFailure<OnSuccess>,
|
||||
typename OnComplete = ThenOnComplete<OnSuccess, OnFailure>,
|
||||
typename ContinuedFuture = typename OnComplete::ContinuedFuture>
|
||||
ContinuedFuture Then(OnSuccess on_success, OnFailure on_failure = {},
|
||||
CallbackOptions options = CallbackOptions::Defaults()) const {
|
||||
auto next = ContinuedFuture::Make();
|
||||
AddCallback(OnComplete{std::forward<OnSuccess>(on_success),
|
||||
std::forward<OnFailure>(on_failure), next},
|
||||
options);
|
||||
return next;
|
||||
}
|
||||
|
||||
/// \brief Implicit constructor to create a finished future from a value
|
||||
Future(ValueType val) : Future() { // NOLINT runtime/explicit
|
||||
impl_ = FutureImpl::MakeFinished(FutureState::SUCCESS);
|
||||
SetResult(std::move(val));
|
||||
}
|
||||
|
||||
/// \brief Implicit constructor to create a future from a Result, enabling use
|
||||
/// of macros like ARROW_ASSIGN_OR_RAISE.
|
||||
Future(Result<ValueType> res) : Future() { // NOLINT runtime/explicit
|
||||
if (ARROW_PREDICT_TRUE(res.ok())) {
|
||||
impl_ = FutureImpl::MakeFinished(FutureState::SUCCESS);
|
||||
} else {
|
||||
impl_ = FutureImpl::MakeFinished(FutureState::FAILURE);
|
||||
}
|
||||
SetResult(std::move(res));
|
||||
}
|
||||
|
||||
/// \brief Implicit constructor to create a future from a Status, enabling use
|
||||
/// of macros like ARROW_RETURN_NOT_OK.
|
||||
Future(Status s) // NOLINT runtime/explicit
|
||||
: Future(Result<ValueType>(std::move(s))) {}
|
||||
|
||||
protected:
|
||||
void InitializeFromResult(Result<ValueType> res) {
|
||||
if (ARROW_PREDICT_TRUE(res.ok())) {
|
||||
impl_ = FutureImpl::MakeFinished(FutureState::SUCCESS);
|
||||
} else {
|
||||
impl_ = FutureImpl::MakeFinished(FutureState::FAILURE);
|
||||
}
|
||||
SetResult(std::move(res));
|
||||
}
|
||||
|
||||
void Initialize() { impl_ = FutureImpl::Make(); }
|
||||
|
||||
Result<ValueType>* GetResult() const { return impl_->CastResult<ValueType>(); }
|
||||
|
||||
void SetResult(Result<ValueType> res) {
|
||||
impl_->result_ = {new Result<ValueType>(std::move(res)),
|
||||
[](void* p) { delete static_cast<Result<ValueType>*>(p); }};
|
||||
}
|
||||
|
||||
void DoMarkFinished(Result<ValueType> res) {
|
||||
SetResult(std::move(res));
|
||||
|
||||
if (ARROW_PREDICT_TRUE(GetResult()->ok())) {
|
||||
impl_->MarkFinished();
|
||||
} else {
|
||||
impl_->MarkFailed();
|
||||
}
|
||||
}
|
||||
|
||||
void CheckValid() const {
|
||||
#ifndef NDEBUG
|
||||
if (!is_valid()) {
|
||||
Status::Invalid("Invalid Future (default-initialized?)").Abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
explicit Future(std::shared_ptr<FutureImpl> impl) : impl_(std::move(impl)) {}
|
||||
|
||||
std::shared_ptr<FutureImpl> impl_;
|
||||
|
||||
friend struct detail::ContinueFuture;
|
||||
|
||||
template <typename U>
|
||||
friend class Future;
|
||||
friend class WeakFuture<T>;
|
||||
|
||||
FRIEND_TEST(FutureRefTest, ChainRemoved);
|
||||
FRIEND_TEST(FutureRefTest, TailRemoved);
|
||||
FRIEND_TEST(FutureRefTest, HeadRemoved);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
typename Future<T>::SyncType FutureToSync(const Future<T>& fut) {
|
||||
return fut.result();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline typename Future<internal::Empty>::SyncType FutureToSync<internal::Empty>(
|
||||
const Future<internal::Empty>& fut) {
|
||||
return fut.status();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline Future<>::Future(Status s) : Future(internal::Empty::ToResult(std::move(s))) {}
|
||||
|
||||
template <typename T>
|
||||
class WeakFuture {
|
||||
public:
|
||||
explicit WeakFuture(const Future<T>& future) : impl_(future.impl_) {}
|
||||
|
||||
Future<T> get() { return Future<T>{impl_.lock()}; }
|
||||
|
||||
private:
|
||||
std::weak_ptr<FutureImpl> impl_;
|
||||
};
|
||||
|
||||
/// \defgroup future-utilities Functions for working with Futures
|
||||
/// @{
|
||||
|
||||
/// If a Result<Future> holds an error instead of a Future, construct a finished Future
|
||||
/// holding that error.
|
||||
template <typename T>
|
||||
static Future<T> DeferNotOk(Result<Future<T>> maybe_future) {
|
||||
if (ARROW_PREDICT_FALSE(!maybe_future.ok())) {
|
||||
return Future<T>::MakeFinished(std::move(maybe_future).status());
|
||||
}
|
||||
return std::move(maybe_future).MoveValueUnsafe();
|
||||
}
|
||||
|
||||
/// \brief Create a Future which completes when all of `futures` complete.
|
||||
///
|
||||
/// The future's result is a vector of the results of `futures`.
|
||||
/// Note that this future will never be marked "failed"; failed results
|
||||
/// will be stored in the result vector alongside successful results.
|
||||
template <typename T>
|
||||
Future<std::vector<Result<T>>> All(std::vector<Future<T>> futures) {
|
||||
struct State {
|
||||
explicit State(std::vector<Future<T>> f)
|
||||
: futures(std::move(f)), n_remaining(futures.size()) {}
|
||||
|
||||
std::vector<Future<T>> futures;
|
||||
std::atomic<size_t> n_remaining;
|
||||
};
|
||||
|
||||
if (futures.size() == 0) {
|
||||
return {std::vector<Result<T>>{}};
|
||||
}
|
||||
|
||||
auto state = std::make_shared<State>(std::move(futures));
|
||||
|
||||
auto out = Future<std::vector<Result<T>>>::Make();
|
||||
for (const Future<T>& future : state->futures) {
|
||||
future.AddCallback([state, out](const Result<T>&) mutable {
|
||||
if (state->n_remaining.fetch_sub(1) != 1) return;
|
||||
|
||||
std::vector<Result<T>> results(state->futures.size());
|
||||
for (size_t i = 0; i < results.size(); ++i) {
|
||||
results[i] = state->futures[i].result();
|
||||
}
|
||||
out.MarkFinished(std::move(results));
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// \brief Create a Future which completes when all of `futures` complete.
|
||||
///
|
||||
/// The future will be marked complete if all `futures` complete
|
||||
/// successfully. Otherwise, it will be marked failed with the status of
|
||||
/// the first failing future.
|
||||
ARROW_EXPORT
|
||||
Future<> AllComplete(const std::vector<Future<>>& futures);
|
||||
|
||||
/// \brief Create a Future which completes when all of `futures` complete.
|
||||
///
|
||||
/// The future will finish with an ok status if all `futures` finish with
|
||||
/// an ok status. Otherwise, it will be marked failed with the status of
|
||||
/// one of the failing futures.
|
||||
///
|
||||
/// Unlike AllComplete this Future will not complete immediately when a
|
||||
/// failure occurs. It will wait until all futures have finished.
|
||||
ARROW_EXPORT
|
||||
Future<> AllFinished(const std::vector<Future<>>& futures);
|
||||
|
||||
/// @}
|
||||
|
||||
struct Continue {
|
||||
template <typename T>
|
||||
operator std::optional<T>() && { // NOLINT explicit
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T = internal::Empty>
|
||||
std::optional<T> Break(T break_value = {}) {
|
||||
return std::optional<T>{std::move(break_value)};
|
||||
}
|
||||
|
||||
template <typename T = internal::Empty>
|
||||
using ControlFlow = std::optional<T>;
|
||||
|
||||
/// \brief Loop through an asynchronous sequence
|
||||
///
|
||||
/// \param[in] iterate A generator of Future<ControlFlow<BreakValue>>. On completion
|
||||
/// of each yielded future the resulting ControlFlow will be examined. A Break will
|
||||
/// terminate the loop, while a Continue will re-invoke `iterate`.
|
||||
///
|
||||
/// \return A future which will complete when a Future returned by iterate completes with
|
||||
/// a Break
|
||||
template <typename Iterate,
|
||||
typename Control = typename detail::result_of_t<Iterate()>::ValueType,
|
||||
typename BreakValueType = typename Control::value_type>
|
||||
Future<BreakValueType> Loop(Iterate iterate) {
|
||||
struct Callback {
|
||||
bool CheckForTermination(const Result<Control>& control_res) {
|
||||
if (!control_res.ok()) {
|
||||
break_fut.MarkFinished(control_res.status());
|
||||
return true;
|
||||
}
|
||||
if (control_res->has_value()) {
|
||||
break_fut.MarkFinished(**control_res);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void operator()(const Result<Control>& maybe_control) && {
|
||||
if (CheckForTermination(maybe_control)) return;
|
||||
|
||||
auto control_fut = iterate();
|
||||
while (true) {
|
||||
if (control_fut.TryAddCallback([this]() { return *this; })) {
|
||||
// Adding a callback succeeded; control_fut was not finished
|
||||
// and we must wait to CheckForTermination.
|
||||
return;
|
||||
}
|
||||
// Adding a callback failed; control_fut was finished and we
|
||||
// can CheckForTermination immediately. This also avoids recursion and potential
|
||||
// stack overflow.
|
||||
if (CheckForTermination(control_fut.result())) return;
|
||||
|
||||
control_fut = iterate();
|
||||
}
|
||||
}
|
||||
|
||||
Iterate iterate;
|
||||
|
||||
// If the future returned by control_fut is never completed then we will be hanging on
|
||||
// to break_fut forever even if the listener has given up listening on it. Instead we
|
||||
// rely on the fact that a producer (the caller of Future<>::Make) is always
|
||||
// responsible for completing the futures they create.
|
||||
// TODO: Could avoid this kind of situation with "future abandonment" similar to mesos
|
||||
Future<BreakValueType> break_fut;
|
||||
};
|
||||
|
||||
auto break_fut = Future<BreakValueType>::Make();
|
||||
auto control_fut = iterate();
|
||||
control_fut.AddCallback(Callback{std::move(iterate), break_fut});
|
||||
|
||||
return break_fut;
|
||||
}
|
||||
|
||||
inline Future<> ToFuture(Status status) {
|
||||
return Future<>::MakeFinished(std::move(status));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<T> ToFuture(T value) {
|
||||
return Future<T>::MakeFinished(std::move(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<T> ToFuture(Result<T> maybe_value) {
|
||||
return Future<T>::MakeFinished(std::move(maybe_value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Future<T> ToFuture(Future<T> fut) {
|
||||
return std::move(fut);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct EnsureFuture {
|
||||
using type = decltype(ToFuture(std::declval<T>()));
|
||||
};
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,66 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// BEGIN Hash utilities from Boost
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define ARROW_HASH_ROTL32(x, r) _rotl(x, r)
|
||||
#else
|
||||
#define ARROW_HASH_ROTL32(x, r) (x << r) | (x >> (32 - r))
|
||||
#endif
|
||||
|
||||
template <typename SizeT>
|
||||
inline void hash_combine_impl(SizeT& seed, SizeT value) {
|
||||
seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||
}
|
||||
|
||||
inline void hash_combine_impl(uint32_t& h1, uint32_t k1) {
|
||||
const uint32_t c1 = 0xcc9e2d51;
|
||||
const uint32_t c2 = 0x1b873593;
|
||||
|
||||
k1 *= c1;
|
||||
k1 = ARROW_HASH_ROTL32(k1, 15);
|
||||
k1 *= c2;
|
||||
|
||||
h1 ^= k1;
|
||||
h1 = ARROW_HASH_ROTL32(h1, 13);
|
||||
h1 = h1 * 5 + 0xe6546b64;
|
||||
}
|
||||
|
||||
#undef ARROW_HASH_ROTL32
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <class T>
|
||||
inline void hash_combine(std::size_t& seed, T const& v) {
|
||||
std::hash<T> hasher;
|
||||
return ::arrow::internal::detail::hash_combine_impl(seed, hasher(v));
|
||||
}
|
||||
|
||||
// END Hash utilities from Boost
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,927 @@
|
||||
// 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.
|
||||
|
||||
// Private header, not to be exported
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/array/builder_binary.h"
|
||||
#include "arrow/buffer_builder.h"
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/type_fwd.h"
|
||||
#include "arrow/type_traits.h"
|
||||
#include "arrow/util/bit_util.h"
|
||||
#include "arrow/util/bitmap_builders.h"
|
||||
#include "arrow/util/endian.h"
|
||||
#include "arrow/util/logging.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/ubsan.h"
|
||||
|
||||
#define XXH_INLINE_ALL
|
||||
|
||||
#include "arrow/vendored/xxhash.h" // IWYU pragma: keep
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
// XXX would it help to have a 32-bit hash value on large datasets?
|
||||
typedef uint64_t hash_t;
|
||||
|
||||
// Notes about the choice of a hash function.
|
||||
// - XXH3 is extremely fast on most data sizes, from small to huge;
|
||||
// faster even than HW CRC-based hashing schemes
|
||||
// - our custom hash function for tiny values (< 16 bytes) is still
|
||||
// significantly faster (~30%), at least on this machine and compiler
|
||||
|
||||
template <uint64_t AlgNum>
|
||||
inline hash_t ComputeStringHash(const void* data, int64_t length);
|
||||
|
||||
template <typename Scalar, uint64_t AlgNum>
|
||||
struct ScalarHelperBase {
|
||||
static bool CompareScalars(Scalar u, Scalar v) { return u == v; }
|
||||
|
||||
static hash_t ComputeHash(const Scalar& value) {
|
||||
// Generic hash computation for scalars. Simply apply the string hash
|
||||
// to the bit representation of the value.
|
||||
|
||||
// XXX in the case of FP values, we'd like equal values to have the same hash,
|
||||
// even if they have different bit representations...
|
||||
return ComputeStringHash<AlgNum>(&value, sizeof(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Scalar, uint64_t AlgNum = 0, typename Enable = void>
|
||||
struct ScalarHelper : public ScalarHelperBase<Scalar, AlgNum> {};
|
||||
|
||||
template <typename Scalar, uint64_t AlgNum>
|
||||
struct ScalarHelper<Scalar, AlgNum, enable_if_t<std::is_integral<Scalar>::value>>
|
||||
: public ScalarHelperBase<Scalar, AlgNum> {
|
||||
// ScalarHelper specialization for integers
|
||||
|
||||
static hash_t ComputeHash(const Scalar& value) {
|
||||
// Faster hash computation for integers.
|
||||
|
||||
// Two of xxhash's prime multipliers (which are chosen for their
|
||||
// bit dispersion properties)
|
||||
static constexpr uint64_t multipliers[] = {11400714785074694791ULL,
|
||||
14029467366897019727ULL};
|
||||
|
||||
// Multiplying by the prime number mixes the low bits into the high bits,
|
||||
// then byte-swapping (which is a single CPU instruction) allows the
|
||||
// combined high and low bits to participate in the initial hash table index.
|
||||
auto h = static_cast<hash_t>(value);
|
||||
return bit_util::ByteSwap(multipliers[AlgNum] * h);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Scalar, uint64_t AlgNum>
|
||||
struct ScalarHelper<Scalar, AlgNum,
|
||||
enable_if_t<std::is_same<std::string_view, Scalar>::value>>
|
||||
: public ScalarHelperBase<Scalar, AlgNum> {
|
||||
// ScalarHelper specialization for std::string_view
|
||||
|
||||
static hash_t ComputeHash(const std::string_view& value) {
|
||||
return ComputeStringHash<AlgNum>(value.data(), static_cast<int64_t>(value.size()));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Scalar, uint64_t AlgNum>
|
||||
struct ScalarHelper<Scalar, AlgNum, enable_if_t<std::is_floating_point<Scalar>::value>>
|
||||
: public ScalarHelperBase<Scalar, AlgNum> {
|
||||
// ScalarHelper specialization for reals
|
||||
|
||||
static bool CompareScalars(Scalar u, Scalar v) {
|
||||
if (std::isnan(u)) {
|
||||
// XXX should we do a bit-precise comparison?
|
||||
return std::isnan(v);
|
||||
}
|
||||
return u == v;
|
||||
}
|
||||
};
|
||||
|
||||
template <uint64_t AlgNum = 0>
|
||||
hash_t ComputeStringHash(const void* data, int64_t length) {
|
||||
if (ARROW_PREDICT_TRUE(length <= 16)) {
|
||||
// Specialize for small hash strings, as they are quite common as
|
||||
// hash table keys. Even XXH3 isn't quite as fast.
|
||||
auto p = reinterpret_cast<const uint8_t*>(data);
|
||||
auto n = static_cast<uint32_t>(length);
|
||||
if (n <= 8) {
|
||||
if (n <= 3) {
|
||||
if (n == 0) {
|
||||
return 1U;
|
||||
}
|
||||
uint32_t x = (n << 24) ^ (p[0] << 16) ^ (p[n / 2] << 8) ^ p[n - 1];
|
||||
return ScalarHelper<uint32_t, AlgNum>::ComputeHash(x);
|
||||
}
|
||||
// 4 <= length <= 8
|
||||
// We can read the string as two overlapping 32-bit ints, apply
|
||||
// different hash functions to each of them in parallel, then XOR
|
||||
// the results
|
||||
uint32_t x, y;
|
||||
hash_t hx, hy;
|
||||
x = util::SafeLoadAs<uint32_t>(p + n - 4);
|
||||
y = util::SafeLoadAs<uint32_t>(p);
|
||||
hx = ScalarHelper<uint32_t, AlgNum>::ComputeHash(x);
|
||||
hy = ScalarHelper<uint32_t, AlgNum ^ 1>::ComputeHash(y);
|
||||
return n ^ hx ^ hy;
|
||||
}
|
||||
// 8 <= length <= 16
|
||||
// Apply the same principle as above
|
||||
uint64_t x, y;
|
||||
hash_t hx, hy;
|
||||
x = util::SafeLoadAs<uint64_t>(p + n - 8);
|
||||
y = util::SafeLoadAs<uint64_t>(p);
|
||||
hx = ScalarHelper<uint64_t, AlgNum>::ComputeHash(x);
|
||||
hy = ScalarHelper<uint64_t, AlgNum ^ 1>::ComputeHash(y);
|
||||
return n ^ hx ^ hy;
|
||||
}
|
||||
|
||||
#if XXH3_SECRET_SIZE_MIN != 136
|
||||
#error XXH3_SECRET_SIZE_MIN changed, please fix kXxh3Secrets
|
||||
#endif
|
||||
|
||||
// XXH3_64bits_withSeed generates a secret based on the seed, which is too slow.
|
||||
// Instead, we use hard-coded random secrets. To maximize cache efficiency,
|
||||
// they reuse the same memory area.
|
||||
static constexpr unsigned char kXxh3Secrets[XXH3_SECRET_SIZE_MIN + 1] = {
|
||||
0xe7, 0x8b, 0x13, 0xf9, 0xfc, 0xb5, 0x8e, 0xef, 0x81, 0x48, 0x2c, 0xbf, 0xf9, 0x9f,
|
||||
0xc1, 0x1e, 0x43, 0x6d, 0xbf, 0xa6, 0x6d, 0xb5, 0x72, 0xbc, 0x97, 0xd8, 0x61, 0x24,
|
||||
0x0f, 0x12, 0xe3, 0x05, 0x21, 0xf7, 0x5c, 0x66, 0x67, 0xa5, 0x65, 0x03, 0x96, 0x26,
|
||||
0x69, 0xd8, 0x29, 0x20, 0xf8, 0xc7, 0xb0, 0x3d, 0xdd, 0x7d, 0x18, 0xa0, 0x60, 0x75,
|
||||
0x92, 0xa4, 0xce, 0xba, 0xc0, 0x77, 0xf4, 0xac, 0xb7, 0x03, 0x53, 0xf0, 0x98, 0xce,
|
||||
0xe6, 0x2b, 0x20, 0xc7, 0x82, 0x91, 0xab, 0xbf, 0x68, 0x5c, 0x62, 0x4d, 0x33, 0xa3,
|
||||
0xe1, 0xb3, 0xff, 0x97, 0x54, 0x4c, 0x44, 0x34, 0xb5, 0xb9, 0x32, 0x4c, 0x75, 0x42,
|
||||
0x89, 0x53, 0x94, 0xd4, 0x9f, 0x2b, 0x76, 0x4d, 0x4e, 0xe6, 0xfa, 0x15, 0x3e, 0xc1,
|
||||
0xdb, 0x71, 0x4b, 0x2c, 0x94, 0xf5, 0xfc, 0x8c, 0x89, 0x4b, 0xfb, 0xc1, 0x82, 0xa5,
|
||||
0x6a, 0x53, 0xf9, 0x4a, 0xba, 0xce, 0x1f, 0xc0, 0x97, 0x1a, 0x87};
|
||||
|
||||
static_assert(AlgNum < 2, "AlgNum too large");
|
||||
static constexpr auto secret = kXxh3Secrets + AlgNum;
|
||||
return XXH3_64bits_withSecret(data, static_cast<size_t>(length), secret,
|
||||
XXH3_SECRET_SIZE_MIN);
|
||||
}
|
||||
|
||||
// XXX add a HashEq<ArrowType> struct with both hash and compare functions?
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// An open-addressing insert-only hash table (no deletes)
|
||||
|
||||
template <typename Payload>
|
||||
class HashTable {
|
||||
public:
|
||||
static constexpr hash_t kSentinel = 0ULL;
|
||||
static constexpr int64_t kLoadFactor = 2UL;
|
||||
|
||||
struct Entry {
|
||||
hash_t h;
|
||||
Payload payload;
|
||||
|
||||
// An entry is valid if the hash is different from the sentinel value
|
||||
operator bool() const { return h != kSentinel; }
|
||||
};
|
||||
|
||||
HashTable(MemoryPool* pool, uint64_t capacity) : entries_builder_(pool) {
|
||||
DCHECK_NE(pool, nullptr);
|
||||
// Minimum of 32 elements
|
||||
capacity = std::max<uint64_t>(capacity, 32UL);
|
||||
capacity_ = bit_util::NextPower2(capacity);
|
||||
capacity_mask_ = capacity_ - 1;
|
||||
size_ = 0;
|
||||
|
||||
DCHECK_OK(UpsizeBuffer(capacity_));
|
||||
}
|
||||
|
||||
// Lookup with non-linear probing
|
||||
// cmp_func should have signature bool(const Payload*).
|
||||
// Return a (Entry*, found) pair.
|
||||
template <typename CmpFunc>
|
||||
std::pair<Entry*, bool> Lookup(hash_t h, CmpFunc&& cmp_func) {
|
||||
auto p = Lookup<DoCompare, CmpFunc>(h, entries_, capacity_mask_,
|
||||
std::forward<CmpFunc>(cmp_func));
|
||||
return {&entries_[p.first], p.second};
|
||||
}
|
||||
|
||||
template <typename CmpFunc>
|
||||
std::pair<const Entry*, bool> Lookup(hash_t h, CmpFunc&& cmp_func) const {
|
||||
auto p = Lookup<DoCompare, CmpFunc>(h, entries_, capacity_mask_,
|
||||
std::forward<CmpFunc>(cmp_func));
|
||||
return {&entries_[p.first], p.second};
|
||||
}
|
||||
|
||||
Status Insert(Entry* entry, hash_t h, const Payload& payload) {
|
||||
// Ensure entry is empty before inserting
|
||||
assert(!*entry);
|
||||
entry->h = FixHash(h);
|
||||
entry->payload = payload;
|
||||
++size_;
|
||||
|
||||
if (ARROW_PREDICT_FALSE(NeedUpsizing())) {
|
||||
// Resize less frequently since it is expensive
|
||||
return Upsize(capacity_ * kLoadFactor * 2);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
uint64_t size() const { return size_; }
|
||||
|
||||
// Visit all non-empty entries in the table
|
||||
// The visit_func should have signature void(const Entry*)
|
||||
template <typename VisitFunc>
|
||||
void VisitEntries(VisitFunc&& visit_func) const {
|
||||
for (uint64_t i = 0; i < capacity_; i++) {
|
||||
const auto& entry = entries_[i];
|
||||
if (entry) {
|
||||
visit_func(&entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// NoCompare is for when the value is known not to exist in the table
|
||||
enum CompareKind { DoCompare, NoCompare };
|
||||
|
||||
// The workhorse lookup function
|
||||
template <CompareKind CKind, typename CmpFunc>
|
||||
std::pair<uint64_t, bool> Lookup(hash_t h, const Entry* entries, uint64_t size_mask,
|
||||
CmpFunc&& cmp_func) const {
|
||||
static constexpr uint8_t perturb_shift = 5;
|
||||
|
||||
uint64_t index, perturb;
|
||||
const Entry* entry;
|
||||
|
||||
h = FixHash(h);
|
||||
index = h & size_mask;
|
||||
perturb = (h >> perturb_shift) + 1U;
|
||||
|
||||
while (true) {
|
||||
entry = &entries[index];
|
||||
if (CompareEntry<CKind, CmpFunc>(h, entry, std::forward<CmpFunc>(cmp_func))) {
|
||||
// Found
|
||||
return {index, true};
|
||||
}
|
||||
if (entry->h == kSentinel) {
|
||||
// Empty slot
|
||||
return {index, false};
|
||||
}
|
||||
|
||||
// Perturbation logic inspired from CPython's set / dict object.
|
||||
// The goal is that all 64 bits of the unmasked hash value eventually
|
||||
// participate in the probing sequence, to minimize clustering.
|
||||
index = (index + perturb) & size_mask;
|
||||
perturb = (perturb >> perturb_shift) + 1U;
|
||||
}
|
||||
}
|
||||
|
||||
template <CompareKind CKind, typename CmpFunc>
|
||||
bool CompareEntry(hash_t h, const Entry* entry, CmpFunc&& cmp_func) const {
|
||||
if (CKind == NoCompare) {
|
||||
return false;
|
||||
} else {
|
||||
return entry->h == h && cmp_func(&entry->payload);
|
||||
}
|
||||
}
|
||||
|
||||
bool NeedUpsizing() const {
|
||||
// Keep the load factor <= 1/2
|
||||
return size_ * kLoadFactor >= capacity_;
|
||||
}
|
||||
|
||||
Status UpsizeBuffer(uint64_t capacity) {
|
||||
RETURN_NOT_OK(entries_builder_.Resize(capacity));
|
||||
entries_ = entries_builder_.mutable_data();
|
||||
memset(static_cast<void*>(entries_), 0, capacity * sizeof(Entry));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status Upsize(uint64_t new_capacity) {
|
||||
assert(new_capacity > capacity_);
|
||||
uint64_t new_mask = new_capacity - 1;
|
||||
assert((new_capacity & new_mask) == 0); // it's a power of two
|
||||
|
||||
// Stash old entries and seal builder, effectively resetting the Buffer
|
||||
const Entry* old_entries = entries_;
|
||||
ARROW_ASSIGN_OR_RAISE(auto previous, entries_builder_.FinishWithLength(capacity_));
|
||||
// Allocate new buffer
|
||||
RETURN_NOT_OK(UpsizeBuffer(new_capacity));
|
||||
|
||||
for (uint64_t i = 0; i < capacity_; i++) {
|
||||
const auto& entry = old_entries[i];
|
||||
if (entry) {
|
||||
// Dummy compare function will not be called
|
||||
auto p = Lookup<NoCompare>(entry.h, entries_, new_mask,
|
||||
[](const Payload*) { return false; });
|
||||
// Lookup<NoCompare> (and CompareEntry<NoCompare>) ensure that an
|
||||
// empty slots is always returned
|
||||
assert(!p.second);
|
||||
entries_[p.first] = entry;
|
||||
}
|
||||
}
|
||||
capacity_ = new_capacity;
|
||||
capacity_mask_ = new_mask;
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
hash_t FixHash(hash_t h) const { return (h == kSentinel) ? 42U : h; }
|
||||
|
||||
// The number of slots available in the hash table array.
|
||||
uint64_t capacity_;
|
||||
uint64_t capacity_mask_;
|
||||
// The number of used slots in the hash table array.
|
||||
uint64_t size_;
|
||||
|
||||
Entry* entries_;
|
||||
TypedBufferBuilder<Entry> entries_builder_;
|
||||
};
|
||||
|
||||
// XXX typedef memo_index_t int32_t ?
|
||||
|
||||
constexpr int32_t kKeyNotFound = -1;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// A base class for memoization table.
|
||||
|
||||
class MemoTable {
|
||||
public:
|
||||
virtual ~MemoTable() = default;
|
||||
|
||||
virtual int32_t size() const = 0;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// A memoization table for memory-cheap scalar values.
|
||||
|
||||
// The memoization table remembers and allows to look up the insertion
|
||||
// index for each key.
|
||||
|
||||
template <typename Scalar, template <class> class HashTableTemplateType = HashTable>
|
||||
class ScalarMemoTable : public MemoTable {
|
||||
public:
|
||||
explicit ScalarMemoTable(MemoryPool* pool, int64_t entries = 0)
|
||||
: hash_table_(pool, static_cast<uint64_t>(entries)) {}
|
||||
|
||||
int32_t Get(const Scalar& value) const {
|
||||
auto cmp_func = [value](const Payload* payload) -> bool {
|
||||
return ScalarHelper<Scalar, 0>::CompareScalars(payload->value, value);
|
||||
};
|
||||
hash_t h = ComputeHash(value);
|
||||
auto p = hash_table_.Lookup(h, cmp_func);
|
||||
if (p.second) {
|
||||
return p.first->payload.memo_index;
|
||||
} else {
|
||||
return kKeyNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Func1, typename Func2>
|
||||
Status GetOrInsert(const Scalar& value, Func1&& on_found, Func2&& on_not_found,
|
||||
int32_t* out_memo_index) {
|
||||
auto cmp_func = [value](const Payload* payload) -> bool {
|
||||
return ScalarHelper<Scalar, 0>::CompareScalars(value, payload->value);
|
||||
};
|
||||
hash_t h = ComputeHash(value);
|
||||
auto p = hash_table_.Lookup(h, cmp_func);
|
||||
int32_t memo_index;
|
||||
if (p.second) {
|
||||
memo_index = p.first->payload.memo_index;
|
||||
on_found(memo_index);
|
||||
} else {
|
||||
memo_index = size();
|
||||
RETURN_NOT_OK(hash_table_.Insert(p.first, h, {value, memo_index}));
|
||||
on_not_found(memo_index);
|
||||
}
|
||||
*out_memo_index = memo_index;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status GetOrInsert(const Scalar& value, int32_t* out_memo_index) {
|
||||
return GetOrInsert(
|
||||
value, [](int32_t i) {}, [](int32_t i) {}, out_memo_index);
|
||||
}
|
||||
|
||||
int32_t GetNull() const { return null_index_; }
|
||||
|
||||
template <typename Func1, typename Func2>
|
||||
int32_t GetOrInsertNull(Func1&& on_found, Func2&& on_not_found) {
|
||||
int32_t memo_index = GetNull();
|
||||
if (memo_index != kKeyNotFound) {
|
||||
on_found(memo_index);
|
||||
} else {
|
||||
null_index_ = memo_index = size();
|
||||
on_not_found(memo_index);
|
||||
}
|
||||
return memo_index;
|
||||
}
|
||||
|
||||
int32_t GetOrInsertNull() {
|
||||
return GetOrInsertNull([](int32_t i) {}, [](int32_t i) {});
|
||||
}
|
||||
|
||||
// The number of entries in the memo table +1 if null was added.
|
||||
// (which is also 1 + the largest memo index)
|
||||
int32_t size() const override {
|
||||
return static_cast<int32_t>(hash_table_.size()) + (GetNull() != kKeyNotFound);
|
||||
}
|
||||
|
||||
// Copy values starting from index `start` into `out_data`
|
||||
void CopyValues(int32_t start, Scalar* out_data) const {
|
||||
hash_table_.VisitEntries([=](const HashTableEntry* entry) {
|
||||
int32_t index = entry->payload.memo_index - start;
|
||||
if (index >= 0) {
|
||||
out_data[index] = entry->payload.value;
|
||||
}
|
||||
});
|
||||
// Zero-initialize the null entry
|
||||
if (null_index_ != kKeyNotFound) {
|
||||
int32_t index = null_index_ - start;
|
||||
if (index >= 0) {
|
||||
out_data[index] = Scalar{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CopyValues(Scalar* out_data) const { CopyValues(0, out_data); }
|
||||
|
||||
protected:
|
||||
struct Payload {
|
||||
Scalar value;
|
||||
int32_t memo_index;
|
||||
};
|
||||
|
||||
using HashTableType = HashTableTemplateType<Payload>;
|
||||
using HashTableEntry = typename HashTableType::Entry;
|
||||
HashTableType hash_table_;
|
||||
int32_t null_index_ = kKeyNotFound;
|
||||
|
||||
hash_t ComputeHash(const Scalar& value) const {
|
||||
return ScalarHelper<Scalar, 0>::ComputeHash(value);
|
||||
}
|
||||
|
||||
public:
|
||||
// defined here so that `HashTableType` is visible
|
||||
// Merge entries from `other_table` into `this->hash_table_`.
|
||||
Status MergeTable(const ScalarMemoTable& other_table) {
|
||||
const HashTableType& other_hashtable = other_table.hash_table_;
|
||||
|
||||
other_hashtable.VisitEntries([this](const HashTableEntry* other_entry) {
|
||||
int32_t unused;
|
||||
DCHECK_OK(this->GetOrInsert(other_entry->payload.value, &unused));
|
||||
});
|
||||
// TODO: ARROW-17074 - implement proper error handling
|
||||
return Status::OK();
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// A memoization table for small scalar values, using direct indexing
|
||||
|
||||
template <typename Scalar, typename Enable = void>
|
||||
struct SmallScalarTraits {};
|
||||
|
||||
template <>
|
||||
struct SmallScalarTraits<bool> {
|
||||
static constexpr int32_t cardinality = 2;
|
||||
|
||||
static uint32_t AsIndex(bool value) { return value ? 1 : 0; }
|
||||
};
|
||||
|
||||
template <typename Scalar>
|
||||
struct SmallScalarTraits<Scalar, enable_if_t<std::is_integral<Scalar>::value>> {
|
||||
using Unsigned = typename std::make_unsigned<Scalar>::type;
|
||||
|
||||
static constexpr int32_t cardinality = 1U + std::numeric_limits<Unsigned>::max();
|
||||
|
||||
static uint32_t AsIndex(Scalar value) { return static_cast<Unsigned>(value); }
|
||||
};
|
||||
|
||||
template <typename Scalar, template <class> class HashTableTemplateType = HashTable>
|
||||
class SmallScalarMemoTable : public MemoTable {
|
||||
public:
|
||||
explicit SmallScalarMemoTable(MemoryPool* pool, int64_t entries = 0) {
|
||||
std::fill(value_to_index_, value_to_index_ + cardinality + 1, kKeyNotFound);
|
||||
index_to_value_.reserve(cardinality);
|
||||
}
|
||||
|
||||
int32_t Get(const Scalar value) const {
|
||||
auto value_index = AsIndex(value);
|
||||
return value_to_index_[value_index];
|
||||
}
|
||||
|
||||
template <typename Func1, typename Func2>
|
||||
Status GetOrInsert(const Scalar value, Func1&& on_found, Func2&& on_not_found,
|
||||
int32_t* out_memo_index) {
|
||||
auto value_index = AsIndex(value);
|
||||
auto memo_index = value_to_index_[value_index];
|
||||
if (memo_index == kKeyNotFound) {
|
||||
memo_index = static_cast<int32_t>(index_to_value_.size());
|
||||
index_to_value_.push_back(value);
|
||||
value_to_index_[value_index] = memo_index;
|
||||
DCHECK_LT(memo_index, cardinality + 1);
|
||||
on_not_found(memo_index);
|
||||
} else {
|
||||
on_found(memo_index);
|
||||
}
|
||||
*out_memo_index = memo_index;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status GetOrInsert(const Scalar value, int32_t* out_memo_index) {
|
||||
return GetOrInsert(
|
||||
value, [](int32_t i) {}, [](int32_t i) {}, out_memo_index);
|
||||
}
|
||||
|
||||
int32_t GetNull() const { return value_to_index_[cardinality]; }
|
||||
|
||||
template <typename Func1, typename Func2>
|
||||
int32_t GetOrInsertNull(Func1&& on_found, Func2&& on_not_found) {
|
||||
auto memo_index = GetNull();
|
||||
if (memo_index == kKeyNotFound) {
|
||||
memo_index = value_to_index_[cardinality] = size();
|
||||
index_to_value_.push_back(0);
|
||||
on_not_found(memo_index);
|
||||
} else {
|
||||
on_found(memo_index);
|
||||
}
|
||||
return memo_index;
|
||||
}
|
||||
|
||||
int32_t GetOrInsertNull() {
|
||||
return GetOrInsertNull([](int32_t i) {}, [](int32_t i) {});
|
||||
}
|
||||
|
||||
// The number of entries in the memo table
|
||||
// (which is also 1 + the largest memo index)
|
||||
int32_t size() const override { return static_cast<int32_t>(index_to_value_.size()); }
|
||||
|
||||
// Merge entries from `other_table` into `this`.
|
||||
Status MergeTable(const SmallScalarMemoTable& other_table) {
|
||||
for (const Scalar& other_val : other_table.index_to_value_) {
|
||||
int32_t unused;
|
||||
RETURN_NOT_OK(this->GetOrInsert(other_val, &unused));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Copy values starting from index `start` into `out_data`
|
||||
void CopyValues(int32_t start, Scalar* out_data) const {
|
||||
DCHECK_GE(start, 0);
|
||||
DCHECK_LE(static_cast<size_t>(start), index_to_value_.size());
|
||||
int64_t offset = start * static_cast<int32_t>(sizeof(Scalar));
|
||||
memcpy(out_data, index_to_value_.data() + offset, (size() - start) * sizeof(Scalar));
|
||||
}
|
||||
|
||||
void CopyValues(Scalar* out_data) const { CopyValues(0, out_data); }
|
||||
|
||||
const std::vector<Scalar>& values() const { return index_to_value_; }
|
||||
|
||||
protected:
|
||||
static constexpr auto cardinality = SmallScalarTraits<Scalar>::cardinality;
|
||||
static_assert(cardinality <= 256, "cardinality too large for direct-addressed table");
|
||||
|
||||
uint32_t AsIndex(Scalar value) const {
|
||||
return SmallScalarTraits<Scalar>::AsIndex(value);
|
||||
}
|
||||
|
||||
// The last index is reserved for the null element.
|
||||
int32_t value_to_index_[cardinality + 1];
|
||||
std::vector<Scalar> index_to_value_;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// A memoization table for variable-sized binary data.
|
||||
|
||||
template <typename BinaryBuilderT>
|
||||
class BinaryMemoTable : public MemoTable {
|
||||
public:
|
||||
using builder_offset_type = typename BinaryBuilderT::offset_type;
|
||||
explicit BinaryMemoTable(MemoryPool* pool, int64_t entries = 0,
|
||||
int64_t values_size = -1)
|
||||
: hash_table_(pool, static_cast<uint64_t>(entries)), binary_builder_(pool) {
|
||||
const int64_t data_size = (values_size < 0) ? entries * 4 : values_size;
|
||||
DCHECK_OK(binary_builder_.Resize(entries));
|
||||
DCHECK_OK(binary_builder_.ReserveData(data_size));
|
||||
}
|
||||
|
||||
int32_t Get(const void* data, builder_offset_type length) const {
|
||||
hash_t h = ComputeStringHash<0>(data, length);
|
||||
auto p = Lookup(h, data, length);
|
||||
if (p.second) {
|
||||
return p.first->payload.memo_index;
|
||||
} else {
|
||||
return kKeyNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Get(const std::string_view& value) const {
|
||||
return Get(value.data(), static_cast<builder_offset_type>(value.length()));
|
||||
}
|
||||
|
||||
template <typename Func1, typename Func2>
|
||||
Status GetOrInsert(const void* data, builder_offset_type length, Func1&& on_found,
|
||||
Func2&& on_not_found, int32_t* out_memo_index) {
|
||||
hash_t h = ComputeStringHash<0>(data, length);
|
||||
auto p = Lookup(h, data, length);
|
||||
int32_t memo_index;
|
||||
if (p.second) {
|
||||
memo_index = p.first->payload.memo_index;
|
||||
on_found(memo_index);
|
||||
} else {
|
||||
memo_index = size();
|
||||
// Insert string value
|
||||
RETURN_NOT_OK(binary_builder_.Append(static_cast<const char*>(data), length));
|
||||
// Insert hash entry
|
||||
RETURN_NOT_OK(
|
||||
hash_table_.Insert(const_cast<HashTableEntry*>(p.first), h, {memo_index}));
|
||||
|
||||
on_not_found(memo_index);
|
||||
}
|
||||
*out_memo_index = memo_index;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
template <typename Func1, typename Func2>
|
||||
Status GetOrInsert(const std::string_view& value, Func1&& on_found,
|
||||
Func2&& on_not_found, int32_t* out_memo_index) {
|
||||
return GetOrInsert(value.data(), static_cast<builder_offset_type>(value.length()),
|
||||
std::forward<Func1>(on_found), std::forward<Func2>(on_not_found),
|
||||
out_memo_index);
|
||||
}
|
||||
|
||||
Status GetOrInsert(const void* data, builder_offset_type length,
|
||||
int32_t* out_memo_index) {
|
||||
return GetOrInsert(
|
||||
data, length, [](int32_t i) {}, [](int32_t i) {}, out_memo_index);
|
||||
}
|
||||
|
||||
Status GetOrInsert(const std::string_view& value, int32_t* out_memo_index) {
|
||||
return GetOrInsert(value.data(), static_cast<builder_offset_type>(value.length()),
|
||||
out_memo_index);
|
||||
}
|
||||
|
||||
int32_t GetNull() const { return null_index_; }
|
||||
|
||||
template <typename Func1, typename Func2>
|
||||
int32_t GetOrInsertNull(Func1&& on_found, Func2&& on_not_found) {
|
||||
int32_t memo_index = GetNull();
|
||||
if (memo_index == kKeyNotFound) {
|
||||
memo_index = null_index_ = size();
|
||||
DCHECK_OK(binary_builder_.AppendNull());
|
||||
on_not_found(memo_index);
|
||||
} else {
|
||||
on_found(memo_index);
|
||||
}
|
||||
return memo_index;
|
||||
}
|
||||
|
||||
int32_t GetOrInsertNull() {
|
||||
return GetOrInsertNull([](int32_t i) {}, [](int32_t i) {});
|
||||
}
|
||||
|
||||
// The number of entries in the memo table
|
||||
// (which is also 1 + the largest memo index)
|
||||
int32_t size() const override {
|
||||
return static_cast<int32_t>(hash_table_.size() + (GetNull() != kKeyNotFound));
|
||||
}
|
||||
|
||||
int64_t values_size() const { return binary_builder_.value_data_length(); }
|
||||
|
||||
// Copy (n + 1) offsets starting from index `start` into `out_data`
|
||||
template <class Offset>
|
||||
void CopyOffsets(int32_t start, Offset* out_data) const {
|
||||
DCHECK_LE(start, size());
|
||||
|
||||
const builder_offset_type* offsets = binary_builder_.offsets_data();
|
||||
const builder_offset_type delta =
|
||||
start < binary_builder_.length() ? offsets[start] : 0;
|
||||
for (int32_t i = start; i < size(); ++i) {
|
||||
const builder_offset_type adjusted_offset = offsets[i] - delta;
|
||||
Offset cast_offset = static_cast<Offset>(adjusted_offset);
|
||||
assert(static_cast<builder_offset_type>(cast_offset) ==
|
||||
adjusted_offset); // avoid truncation
|
||||
*out_data++ = cast_offset;
|
||||
}
|
||||
|
||||
// Copy last value since BinaryBuilder only materializes it on in Finish()
|
||||
*out_data = static_cast<Offset>(binary_builder_.value_data_length() - delta);
|
||||
}
|
||||
|
||||
template <class Offset>
|
||||
void CopyOffsets(Offset* out_data) const {
|
||||
CopyOffsets(0, out_data);
|
||||
}
|
||||
|
||||
// Copy values starting from index `start` into `out_data`
|
||||
void CopyValues(int32_t start, uint8_t* out_data) const {
|
||||
CopyValues(start, -1, out_data);
|
||||
}
|
||||
|
||||
// Same as above, but check output size in debug mode
|
||||
void CopyValues(int32_t start, int64_t out_size, uint8_t* out_data) const {
|
||||
DCHECK_LE(start, size());
|
||||
|
||||
// The absolute byte offset of `start` value in the binary buffer.
|
||||
const builder_offset_type offset = binary_builder_.offset(start);
|
||||
const auto length = binary_builder_.value_data_length() - static_cast<size_t>(offset);
|
||||
|
||||
if (out_size != -1) {
|
||||
assert(static_cast<int64_t>(length) <= out_size);
|
||||
}
|
||||
|
||||
auto view = binary_builder_.GetView(start);
|
||||
memcpy(out_data, view.data(), length);
|
||||
}
|
||||
|
||||
void CopyValues(uint8_t* out_data) const { CopyValues(0, -1, out_data); }
|
||||
|
||||
void CopyValues(int64_t out_size, uint8_t* out_data) const {
|
||||
CopyValues(0, out_size, out_data);
|
||||
}
|
||||
|
||||
void CopyFixedWidthValues(int32_t start, int32_t width_size, int64_t out_size,
|
||||
uint8_t* out_data) const {
|
||||
// This method exists to cope with the fact that the BinaryMemoTable does
|
||||
// not know the fixed width when inserting the null value. The data
|
||||
// buffer hold a zero length string for the null value (if found).
|
||||
//
|
||||
// Thus, the method will properly inject an empty value of the proper width
|
||||
// in the output buffer.
|
||||
//
|
||||
if (start >= size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t null_index = GetNull();
|
||||
if (null_index < start) {
|
||||
// Nothing to skip, proceed as usual.
|
||||
CopyValues(start, out_size, out_data);
|
||||
return;
|
||||
}
|
||||
|
||||
builder_offset_type left_offset = binary_builder_.offset(start);
|
||||
|
||||
// Ensure that the data length is exactly missing width_size bytes to fit
|
||||
// in the expected output (n_values * width_size).
|
||||
#ifndef NDEBUG
|
||||
int64_t data_length = values_size() - static_cast<size_t>(left_offset);
|
||||
assert(data_length + width_size == out_size);
|
||||
ARROW_UNUSED(data_length);
|
||||
#endif
|
||||
|
||||
auto in_data = binary_builder_.value_data() + left_offset;
|
||||
// The null use 0-length in the data, slice the data in 2 and skip by
|
||||
// width_size in out_data. [part_1][width_size][part_2]
|
||||
auto null_data_offset = binary_builder_.offset(null_index);
|
||||
auto left_size = null_data_offset - left_offset;
|
||||
if (left_size > 0) {
|
||||
memcpy(out_data, in_data + left_offset, left_size);
|
||||
}
|
||||
// Zero-initialize the null entry
|
||||
memset(out_data + left_size, 0, width_size);
|
||||
|
||||
auto right_size = values_size() - static_cast<size_t>(null_data_offset);
|
||||
if (right_size > 0) {
|
||||
// skip the null fixed size value.
|
||||
auto out_offset = left_size + width_size;
|
||||
assert(out_data + out_offset + right_size == out_data + out_size);
|
||||
memcpy(out_data + out_offset, in_data + null_data_offset, right_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Visit the stored values in insertion order.
|
||||
// The visitor function should have the signature `void(std::string_view)`
|
||||
// or `void(const std::string_view&)`.
|
||||
template <typename VisitFunc>
|
||||
void VisitValues(int32_t start, VisitFunc&& visit) const {
|
||||
for (int32_t i = start; i < size(); ++i) {
|
||||
visit(binary_builder_.GetView(i));
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
struct Payload {
|
||||
int32_t memo_index;
|
||||
};
|
||||
|
||||
using HashTableType = HashTable<Payload>;
|
||||
using HashTableEntry = typename HashTable<Payload>::Entry;
|
||||
HashTableType hash_table_;
|
||||
BinaryBuilderT binary_builder_;
|
||||
|
||||
int32_t null_index_ = kKeyNotFound;
|
||||
|
||||
std::pair<const HashTableEntry*, bool> Lookup(hash_t h, const void* data,
|
||||
builder_offset_type length) const {
|
||||
auto cmp_func = [&](const Payload* payload) {
|
||||
std::string_view lhs = binary_builder_.GetView(payload->memo_index);
|
||||
std::string_view rhs(static_cast<const char*>(data), length);
|
||||
return lhs == rhs;
|
||||
};
|
||||
return hash_table_.Lookup(h, cmp_func);
|
||||
}
|
||||
|
||||
public:
|
||||
Status MergeTable(const BinaryMemoTable& other_table) {
|
||||
other_table.VisitValues(0, [this](const std::string_view& other_value) {
|
||||
int32_t unused;
|
||||
DCHECK_OK(this->GetOrInsert(other_value, &unused));
|
||||
});
|
||||
return Status::OK();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct HashTraits {};
|
||||
|
||||
template <>
|
||||
struct HashTraits<BooleanType> {
|
||||
using MemoTableType = SmallScalarMemoTable<bool>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct HashTraits<T, enable_if_8bit_int<T>> {
|
||||
using c_type = typename T::c_type;
|
||||
using MemoTableType = SmallScalarMemoTable<typename T::c_type>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct HashTraits<T, enable_if_t<has_c_type<T>::value && !is_8bit_int<T>::value>> {
|
||||
using c_type = typename T::c_type;
|
||||
using MemoTableType = ScalarMemoTable<c_type, HashTable>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct HashTraits<T, enable_if_t<has_string_view<T>::value &&
|
||||
!std::is_base_of<LargeBinaryType, T>::value>> {
|
||||
using MemoTableType = BinaryMemoTable<BinaryBuilder>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct HashTraits<T, enable_if_decimal<T>> {
|
||||
using MemoTableType = BinaryMemoTable<BinaryBuilder>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct HashTraits<T, enable_if_t<std::is_base_of<LargeBinaryType, T>::value>> {
|
||||
using MemoTableType = BinaryMemoTable<LargeBinaryBuilder>;
|
||||
};
|
||||
|
||||
template <typename MemoTableType>
|
||||
static inline Status ComputeNullBitmap(MemoryPool* pool, const MemoTableType& memo_table,
|
||||
int64_t start_offset, int64_t* null_count,
|
||||
std::shared_ptr<Buffer>* null_bitmap) {
|
||||
int64_t dict_length = static_cast<int64_t>(memo_table.size()) - start_offset;
|
||||
int64_t null_index = memo_table.GetNull();
|
||||
|
||||
*null_count = 0;
|
||||
*null_bitmap = nullptr;
|
||||
|
||||
if (null_index != kKeyNotFound && null_index >= start_offset) {
|
||||
null_index -= start_offset;
|
||||
*null_count = 1;
|
||||
ARROW_ASSIGN_OR_RAISE(*null_bitmap,
|
||||
internal::BitmapAllButOne(pool, dict_length, null_index));
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
struct StringViewHash {
|
||||
// std::hash compatible hasher for use with std::unordered_*
|
||||
// (the std::hash specialization provided by nonstd constructs std::string
|
||||
// temporaries then invokes std::hash<std::string> against those)
|
||||
hash_t operator()(const std::string_view& value) const {
|
||||
return ComputeStringHash<0>(value.data(), static_cast<int64_t>(value.size()));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,137 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#include "arrow/status.h"
|
||||
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
class DataType;
|
||||
struct ArraySpan;
|
||||
struct Scalar;
|
||||
|
||||
namespace internal {
|
||||
|
||||
ARROW_EXPORT
|
||||
uint8_t DetectUIntWidth(const uint64_t* values, int64_t length, uint8_t min_width = 1);
|
||||
|
||||
ARROW_EXPORT
|
||||
uint8_t DetectUIntWidth(const uint64_t* values, const uint8_t* valid_bytes,
|
||||
int64_t length, uint8_t min_width = 1);
|
||||
|
||||
ARROW_EXPORT
|
||||
uint8_t DetectIntWidth(const int64_t* values, int64_t length, uint8_t min_width = 1);
|
||||
|
||||
ARROW_EXPORT
|
||||
uint8_t DetectIntWidth(const int64_t* values, const uint8_t* valid_bytes, int64_t length,
|
||||
uint8_t min_width = 1);
|
||||
|
||||
ARROW_EXPORT
|
||||
void DowncastInts(const int64_t* source, int8_t* dest, int64_t length);
|
||||
|
||||
ARROW_EXPORT
|
||||
void DowncastInts(const int64_t* source, int16_t* dest, int64_t length);
|
||||
|
||||
ARROW_EXPORT
|
||||
void DowncastInts(const int64_t* source, int32_t* dest, int64_t length);
|
||||
|
||||
ARROW_EXPORT
|
||||
void DowncastInts(const int64_t* source, int64_t* dest, int64_t length);
|
||||
|
||||
ARROW_EXPORT
|
||||
void DowncastUInts(const uint64_t* source, uint8_t* dest, int64_t length);
|
||||
|
||||
ARROW_EXPORT
|
||||
void DowncastUInts(const uint64_t* source, uint16_t* dest, int64_t length);
|
||||
|
||||
ARROW_EXPORT
|
||||
void DowncastUInts(const uint64_t* source, uint32_t* dest, int64_t length);
|
||||
|
||||
ARROW_EXPORT
|
||||
void DowncastUInts(const uint64_t* source, uint64_t* dest, int64_t length);
|
||||
|
||||
ARROW_EXPORT
|
||||
void UpcastInts(const int32_t* source, int64_t* dest, int64_t length);
|
||||
|
||||
template <typename InputInt, typename OutputInt>
|
||||
inline typename std::enable_if<(sizeof(InputInt) >= sizeof(OutputInt))>::type CastInts(
|
||||
const InputInt* source, OutputInt* dest, int64_t length) {
|
||||
DowncastInts(source, dest, length);
|
||||
}
|
||||
|
||||
template <typename InputInt, typename OutputInt>
|
||||
inline typename std::enable_if<(sizeof(InputInt) < sizeof(OutputInt))>::type CastInts(
|
||||
const InputInt* source, OutputInt* dest, int64_t length) {
|
||||
UpcastInts(source, dest, length);
|
||||
}
|
||||
|
||||
template <typename InputInt, typename OutputInt>
|
||||
ARROW_EXPORT void TransposeInts(const InputInt* source, OutputInt* dest, int64_t length,
|
||||
const int32_t* transpose_map);
|
||||
|
||||
ARROW_EXPORT
|
||||
Status TransposeInts(const DataType& src_type, const DataType& dest_type,
|
||||
const uint8_t* src, uint8_t* dest, int64_t src_offset,
|
||||
int64_t dest_offset, int64_t length, const int32_t* transpose_map);
|
||||
|
||||
/// \brief Do vectorized boundschecking of integer-type array indices. The
|
||||
/// indices must be nonnegative and strictly less than the passed upper
|
||||
/// limit (which is usually the length of an array that is being indexed-into).
|
||||
ARROW_EXPORT
|
||||
Status CheckIndexBounds(const ArraySpan& values, uint64_t upper_limit);
|
||||
|
||||
/// \brief Boundscheck integer values to determine if they are all between the
|
||||
/// passed upper and lower limits (inclusive). Upper and lower bounds must be
|
||||
/// the same type as the data and are not currently casted.
|
||||
ARROW_EXPORT
|
||||
Status CheckIntegersInRange(const ArraySpan& values, const Scalar& bound_lower,
|
||||
const Scalar& bound_upper);
|
||||
|
||||
/// \brief Use CheckIntegersInRange to determine whether the passed integers
|
||||
/// can fit safely in the passed integer type. This helps quickly determine if
|
||||
/// integer narrowing (e.g. int64->int32) is safe to do.
|
||||
ARROW_EXPORT
|
||||
Status IntegersCanFit(const ArraySpan& values, const DataType& target_type);
|
||||
|
||||
/// \brief Convenience for boundschecking a single Scalar vlue
|
||||
ARROW_EXPORT
|
||||
Status IntegersCanFit(const Scalar& value, const DataType& target_type);
|
||||
|
||||
/// Upcast an integer to the largest possible width (currently 64 bits)
|
||||
|
||||
template <typename Integer>
|
||||
typename std::enable_if<
|
||||
std::is_integral<Integer>::value && std::is_signed<Integer>::value, int64_t>::type
|
||||
UpcastInt(Integer v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
template <typename Integer>
|
||||
typename std::enable_if<
|
||||
std::is_integral<Integer>::value && std::is_unsigned<Integer>::value, uint64_t>::type
|
||||
UpcastInt(Integer v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,118 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
// "safe-math.h" includes <intsafe.h> from the Windows headers.
|
||||
#include "arrow/util/windows_compatibility.h"
|
||||
#include "arrow/vendored/portable-snippets/safe-math.h"
|
||||
// clang-format off (avoid include reordering)
|
||||
#include "arrow/util/windows_fixup.h"
|
||||
// clang-format on
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
// Define functions AddWithOverflow, SubtractWithOverflow, MultiplyWithOverflow
|
||||
// with the signature `bool(T u, T v, T* out)` where T is an integer type.
|
||||
// On overflow, these functions return true. Otherwise, false is returned
|
||||
// and `out` is updated with the result of the operation.
|
||||
|
||||
#define OP_WITH_OVERFLOW(_func_name, _psnip_op, _type, _psnip_type) \
|
||||
static inline bool _func_name(_type u, _type v, _type* out) { \
|
||||
return !psnip_safe_##_psnip_type##_##_psnip_op(out, u, v); \
|
||||
}
|
||||
|
||||
#define OPS_WITH_OVERFLOW(_func_name, _psnip_op) \
|
||||
OP_WITH_OVERFLOW(_func_name, _psnip_op, int8_t, int8) \
|
||||
OP_WITH_OVERFLOW(_func_name, _psnip_op, int16_t, int16) \
|
||||
OP_WITH_OVERFLOW(_func_name, _psnip_op, int32_t, int32) \
|
||||
OP_WITH_OVERFLOW(_func_name, _psnip_op, int64_t, int64) \
|
||||
OP_WITH_OVERFLOW(_func_name, _psnip_op, uint8_t, uint8) \
|
||||
OP_WITH_OVERFLOW(_func_name, _psnip_op, uint16_t, uint16) \
|
||||
OP_WITH_OVERFLOW(_func_name, _psnip_op, uint32_t, uint32) \
|
||||
OP_WITH_OVERFLOW(_func_name, _psnip_op, uint64_t, uint64)
|
||||
|
||||
OPS_WITH_OVERFLOW(AddWithOverflow, add)
|
||||
OPS_WITH_OVERFLOW(SubtractWithOverflow, sub)
|
||||
OPS_WITH_OVERFLOW(MultiplyWithOverflow, mul)
|
||||
OPS_WITH_OVERFLOW(DivideWithOverflow, div)
|
||||
|
||||
#undef OP_WITH_OVERFLOW
|
||||
#undef OPS_WITH_OVERFLOW
|
||||
|
||||
// Define function NegateWithOverflow with the signature `bool(T u, T* out)`
|
||||
// where T is a signed integer type. On overflow, these functions return true.
|
||||
// Otherwise, false is returned and `out` is updated with the result of the
|
||||
// operation.
|
||||
|
||||
#define UNARY_OP_WITH_OVERFLOW(_func_name, _psnip_op, _type, _psnip_type) \
|
||||
static inline bool _func_name(_type u, _type* out) { \
|
||||
return !psnip_safe_##_psnip_type##_##_psnip_op(out, u); \
|
||||
}
|
||||
|
||||
#define SIGNED_UNARY_OPS_WITH_OVERFLOW(_func_name, _psnip_op) \
|
||||
UNARY_OP_WITH_OVERFLOW(_func_name, _psnip_op, int8_t, int8) \
|
||||
UNARY_OP_WITH_OVERFLOW(_func_name, _psnip_op, int16_t, int16) \
|
||||
UNARY_OP_WITH_OVERFLOW(_func_name, _psnip_op, int32_t, int32) \
|
||||
UNARY_OP_WITH_OVERFLOW(_func_name, _psnip_op, int64_t, int64)
|
||||
|
||||
SIGNED_UNARY_OPS_WITH_OVERFLOW(NegateWithOverflow, neg)
|
||||
|
||||
#undef UNARY_OP_WITH_OVERFLOW
|
||||
#undef SIGNED_UNARY_OPS_WITH_OVERFLOW
|
||||
|
||||
/// Signed addition with well-defined behaviour on overflow (as unsigned)
|
||||
template <typename SignedInt>
|
||||
SignedInt SafeSignedAdd(SignedInt u, SignedInt v) {
|
||||
using UnsignedInt = typename std::make_unsigned<SignedInt>::type;
|
||||
return static_cast<SignedInt>(static_cast<UnsignedInt>(u) +
|
||||
static_cast<UnsignedInt>(v));
|
||||
}
|
||||
|
||||
/// Signed subtraction with well-defined behaviour on overflow (as unsigned)
|
||||
template <typename SignedInt>
|
||||
SignedInt SafeSignedSubtract(SignedInt u, SignedInt v) {
|
||||
using UnsignedInt = typename std::make_unsigned<SignedInt>::type;
|
||||
return static_cast<SignedInt>(static_cast<UnsignedInt>(u) -
|
||||
static_cast<UnsignedInt>(v));
|
||||
}
|
||||
|
||||
/// Signed negation with well-defined behaviour on overflow (as unsigned)
|
||||
template <typename SignedInt>
|
||||
SignedInt SafeSignedNegate(SignedInt u) {
|
||||
using UnsignedInt = typename std::make_unsigned<SignedInt>::type;
|
||||
return static_cast<SignedInt>(~static_cast<UnsignedInt>(u) + 1);
|
||||
}
|
||||
|
||||
/// Signed left shift with well-defined behaviour on negative numbers or overflow
|
||||
template <typename SignedInt, typename Shift>
|
||||
SignedInt SafeLeftShift(SignedInt u, Shift shift) {
|
||||
using UnsignedInt = typename std::make_unsigned<SignedInt>::type;
|
||||
return static_cast<SignedInt>(static_cast<UnsignedInt>(u) << shift);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,420 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef _WIN32
|
||||
#define ARROW_HAVE_SIGACTION 1
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if ARROW_HAVE_SIGACTION
|
||||
#include <signal.h> // Needed for struct sigaction
|
||||
#endif
|
||||
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/type_fwd.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/windows_fixup.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
// NOTE: 8-bit path strings on Windows are encoded using UTF-8.
|
||||
// Using MBCS would fail encoding some paths.
|
||||
|
||||
#if defined(_WIN32)
|
||||
using NativePathString = std::wstring;
|
||||
#else
|
||||
using NativePathString = std::string;
|
||||
#endif
|
||||
|
||||
class ARROW_EXPORT PlatformFilename {
|
||||
public:
|
||||
struct Impl;
|
||||
|
||||
~PlatformFilename();
|
||||
PlatformFilename();
|
||||
PlatformFilename(const PlatformFilename&);
|
||||
PlatformFilename(PlatformFilename&&);
|
||||
PlatformFilename& operator=(const PlatformFilename&);
|
||||
PlatformFilename& operator=(PlatformFilename&&);
|
||||
explicit PlatformFilename(NativePathString path);
|
||||
explicit PlatformFilename(const NativePathString::value_type* path);
|
||||
|
||||
const NativePathString& ToNative() const;
|
||||
std::string ToString() const;
|
||||
|
||||
PlatformFilename Parent() const;
|
||||
Result<PlatformFilename> Real() const;
|
||||
|
||||
// These functions can fail for character encoding reasons.
|
||||
static Result<PlatformFilename> FromString(const std::string& file_name);
|
||||
Result<PlatformFilename> Join(const std::string& child_name) const;
|
||||
|
||||
PlatformFilename Join(const PlatformFilename& child_name) const;
|
||||
|
||||
bool operator==(const PlatformFilename& other) const;
|
||||
bool operator!=(const PlatformFilename& other) const;
|
||||
|
||||
// Made public to avoid the proliferation of friend declarations.
|
||||
const Impl* impl() const { return impl_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<Impl> impl_;
|
||||
|
||||
explicit PlatformFilename(Impl impl);
|
||||
};
|
||||
|
||||
/// Create a directory if it doesn't exist.
|
||||
///
|
||||
/// Return whether the directory was created.
|
||||
ARROW_EXPORT
|
||||
Result<bool> CreateDir(const PlatformFilename& dir_path);
|
||||
|
||||
/// Create a directory and its parents if it doesn't exist.
|
||||
///
|
||||
/// Return whether the directory was created.
|
||||
ARROW_EXPORT
|
||||
Result<bool> CreateDirTree(const PlatformFilename& dir_path);
|
||||
|
||||
/// Delete a directory's contents (but not the directory itself) if it exists.
|
||||
///
|
||||
/// Return whether the directory existed.
|
||||
ARROW_EXPORT
|
||||
Result<bool> DeleteDirContents(const PlatformFilename& dir_path,
|
||||
bool allow_not_found = true);
|
||||
|
||||
/// Delete a directory tree if it exists.
|
||||
///
|
||||
/// Return whether the directory existed.
|
||||
ARROW_EXPORT
|
||||
Result<bool> DeleteDirTree(const PlatformFilename& dir_path, bool allow_not_found = true);
|
||||
|
||||
// Non-recursively list the contents of the given directory.
|
||||
// The returned names are the children's base names, not including dir_path.
|
||||
ARROW_EXPORT
|
||||
Result<std::vector<PlatformFilename>> ListDir(const PlatformFilename& dir_path);
|
||||
|
||||
/// Delete a file if it exists.
|
||||
///
|
||||
/// Return whether the file existed.
|
||||
ARROW_EXPORT
|
||||
Result<bool> DeleteFile(const PlatformFilename& file_path, bool allow_not_found = true);
|
||||
|
||||
/// Return whether a file exists.
|
||||
ARROW_EXPORT
|
||||
Result<bool> FileExists(const PlatformFilename& path);
|
||||
|
||||
// TODO expose this more publicly to make it available from io/file.h?
|
||||
/// A RAII wrapper for a file descriptor.
|
||||
///
|
||||
/// The underlying file descriptor is automatically closed on destruction.
|
||||
/// Moving is supported with well-defined semantics.
|
||||
/// Furthermore, closing is idempotent.
|
||||
class ARROW_EXPORT FileDescriptor {
|
||||
public:
|
||||
FileDescriptor() = default;
|
||||
explicit FileDescriptor(int fd) : fd_(fd) {}
|
||||
FileDescriptor(FileDescriptor&&);
|
||||
FileDescriptor& operator=(FileDescriptor&&);
|
||||
|
||||
~FileDescriptor();
|
||||
|
||||
Status Close();
|
||||
|
||||
/// May return -1 if closed or default-initialized
|
||||
int fd() const { return fd_.load(); }
|
||||
|
||||
/// Detach and return the underlying file descriptor
|
||||
int Detach();
|
||||
|
||||
bool closed() const { return fd_.load() == -1; }
|
||||
|
||||
protected:
|
||||
static void CloseFromDestructor(int fd);
|
||||
|
||||
std::atomic<int> fd_{-1};
|
||||
};
|
||||
|
||||
/// Open a file for reading and return a file descriptor.
|
||||
ARROW_EXPORT
|
||||
Result<FileDescriptor> FileOpenReadable(const PlatformFilename& file_name);
|
||||
|
||||
/// Open a file for writing and return a file descriptor.
|
||||
ARROW_EXPORT
|
||||
Result<FileDescriptor> FileOpenWritable(const PlatformFilename& file_name,
|
||||
bool write_only = true, bool truncate = true,
|
||||
bool append = false);
|
||||
|
||||
/// Read from current file position. Return number of bytes read.
|
||||
ARROW_EXPORT
|
||||
Result<int64_t> FileRead(int fd, uint8_t* buffer, int64_t nbytes);
|
||||
/// Read from given file position. Return number of bytes read.
|
||||
ARROW_EXPORT
|
||||
Result<int64_t> FileReadAt(int fd, uint8_t* buffer, int64_t position, int64_t nbytes);
|
||||
|
||||
ARROW_EXPORT
|
||||
Status FileWrite(int fd, const uint8_t* buffer, const int64_t nbytes);
|
||||
ARROW_EXPORT
|
||||
Status FileTruncate(int fd, const int64_t size);
|
||||
|
||||
ARROW_EXPORT
|
||||
Status FileSeek(int fd, int64_t pos);
|
||||
ARROW_EXPORT
|
||||
Status FileSeek(int fd, int64_t pos, int whence);
|
||||
ARROW_EXPORT
|
||||
Result<int64_t> FileTell(int fd);
|
||||
ARROW_EXPORT
|
||||
Result<int64_t> FileGetSize(int fd);
|
||||
|
||||
ARROW_EXPORT
|
||||
Status FileClose(int fd);
|
||||
|
||||
struct Pipe {
|
||||
FileDescriptor rfd;
|
||||
FileDescriptor wfd;
|
||||
|
||||
Status Close() { return rfd.Close() & wfd.Close(); }
|
||||
};
|
||||
|
||||
ARROW_EXPORT
|
||||
Result<Pipe> CreatePipe();
|
||||
|
||||
ARROW_EXPORT
|
||||
Status SetPipeFileDescriptorNonBlocking(int fd);
|
||||
|
||||
class ARROW_EXPORT SelfPipe {
|
||||
public:
|
||||
static Result<std::shared_ptr<SelfPipe>> Make(bool signal_safe);
|
||||
virtual ~SelfPipe();
|
||||
|
||||
/// \brief Wait for a wakeup.
|
||||
///
|
||||
/// Status::Invalid is returned if the pipe has been shutdown.
|
||||
/// Otherwise the next sent payload is returned.
|
||||
virtual Result<uint64_t> Wait() = 0;
|
||||
|
||||
/// \brief Wake up the pipe by sending a payload.
|
||||
///
|
||||
/// This method is async-signal-safe if `signal_safe` was set to true.
|
||||
virtual void Send(uint64_t payload) = 0;
|
||||
|
||||
/// \brief Wake up the pipe and shut it down.
|
||||
virtual Status Shutdown() = 0;
|
||||
};
|
||||
|
||||
ARROW_EXPORT
|
||||
int64_t GetPageSize();
|
||||
|
||||
struct MemoryRegion {
|
||||
void* addr;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
ARROW_EXPORT
|
||||
Status MemoryMapRemap(void* addr, size_t old_size, size_t new_size, int fildes,
|
||||
void** new_addr);
|
||||
ARROW_EXPORT
|
||||
Status MemoryAdviseWillNeed(const std::vector<MemoryRegion>& regions);
|
||||
|
||||
ARROW_EXPORT
|
||||
Result<std::string> GetEnvVar(const char* name);
|
||||
ARROW_EXPORT
|
||||
Result<std::string> GetEnvVar(const std::string& name);
|
||||
ARROW_EXPORT
|
||||
Result<NativePathString> GetEnvVarNative(const char* name);
|
||||
ARROW_EXPORT
|
||||
Result<NativePathString> GetEnvVarNative(const std::string& name);
|
||||
|
||||
ARROW_EXPORT
|
||||
Status SetEnvVar(const char* name, const char* value);
|
||||
ARROW_EXPORT
|
||||
Status SetEnvVar(const std::string& name, const std::string& value);
|
||||
ARROW_EXPORT
|
||||
Status DelEnvVar(const char* name);
|
||||
ARROW_EXPORT
|
||||
Status DelEnvVar(const std::string& name);
|
||||
|
||||
ARROW_EXPORT
|
||||
std::string ErrnoMessage(int errnum);
|
||||
#if _WIN32
|
||||
ARROW_EXPORT
|
||||
std::string WinErrorMessage(int errnum);
|
||||
#endif
|
||||
|
||||
ARROW_EXPORT
|
||||
std::shared_ptr<StatusDetail> StatusDetailFromErrno(int errnum);
|
||||
#if _WIN32
|
||||
ARROW_EXPORT
|
||||
std::shared_ptr<StatusDetail> StatusDetailFromWinError(int errnum);
|
||||
#endif
|
||||
ARROW_EXPORT
|
||||
std::shared_ptr<StatusDetail> StatusDetailFromSignal(int signum);
|
||||
|
||||
template <typename... Args>
|
||||
Status StatusFromErrno(int errnum, StatusCode code, Args&&... args) {
|
||||
return Status::FromDetailAndArgs(code, StatusDetailFromErrno(errnum),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
Status IOErrorFromErrno(int errnum, Args&&... args) {
|
||||
return StatusFromErrno(errnum, StatusCode::IOError, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
template <typename... Args>
|
||||
Status StatusFromWinError(int errnum, StatusCode code, Args&&... args) {
|
||||
return Status::FromDetailAndArgs(code, StatusDetailFromWinError(errnum),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
Status IOErrorFromWinError(int errnum, Args&&... args) {
|
||||
return StatusFromWinError(errnum, StatusCode::IOError, std::forward<Args>(args)...);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename... Args>
|
||||
Status StatusFromSignal(int signum, StatusCode code, Args&&... args) {
|
||||
return Status::FromDetailAndArgs(code, StatusDetailFromSignal(signum),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
Status CancelledFromSignal(int signum, Args&&... args) {
|
||||
return StatusFromSignal(signum, StatusCode::Cancelled, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
ARROW_EXPORT
|
||||
int ErrnoFromStatus(const Status&);
|
||||
|
||||
// Always returns 0 on non-Windows platforms (for Python).
|
||||
ARROW_EXPORT
|
||||
int WinErrorFromStatus(const Status&);
|
||||
|
||||
ARROW_EXPORT
|
||||
int SignalFromStatus(const Status&);
|
||||
|
||||
class ARROW_EXPORT TemporaryDir {
|
||||
public:
|
||||
~TemporaryDir();
|
||||
|
||||
/// '/'-terminated path to the temporary dir
|
||||
const PlatformFilename& path() { return path_; }
|
||||
|
||||
/// Create a temporary subdirectory in the system temporary dir,
|
||||
/// named starting with `prefix`.
|
||||
static Result<std::unique_ptr<TemporaryDir>> Make(const std::string& prefix);
|
||||
|
||||
private:
|
||||
PlatformFilename path_;
|
||||
|
||||
explicit TemporaryDir(PlatformFilename&&);
|
||||
};
|
||||
|
||||
class ARROW_EXPORT SignalHandler {
|
||||
public:
|
||||
typedef void (*Callback)(int);
|
||||
|
||||
SignalHandler();
|
||||
explicit SignalHandler(Callback cb);
|
||||
#if ARROW_HAVE_SIGACTION
|
||||
explicit SignalHandler(const struct sigaction& sa);
|
||||
#endif
|
||||
|
||||
Callback callback() const;
|
||||
#if ARROW_HAVE_SIGACTION
|
||||
const struct sigaction& action() const;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
#if ARROW_HAVE_SIGACTION
|
||||
// Storing the full sigaction allows to restore the entire signal handling
|
||||
// configuration.
|
||||
struct sigaction sa_;
|
||||
#else
|
||||
Callback cb_;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// \brief Return the current handler for the given signal number.
|
||||
ARROW_EXPORT
|
||||
Result<SignalHandler> GetSignalHandler(int signum);
|
||||
|
||||
/// \brief Set a new handler for the given signal number.
|
||||
///
|
||||
/// The old signal handler is returned.
|
||||
ARROW_EXPORT
|
||||
Result<SignalHandler> SetSignalHandler(int signum, const SignalHandler& handler);
|
||||
|
||||
/// \brief Reinstate the signal handler
|
||||
///
|
||||
/// For use in signal handlers. This is needed on platforms without sigaction()
|
||||
/// such as Windows, as the default signal handler is restored there as
|
||||
/// soon as a signal is raised.
|
||||
ARROW_EXPORT
|
||||
void ReinstateSignalHandler(int signum, SignalHandler::Callback handler);
|
||||
|
||||
/// \brief Send a signal to the current process
|
||||
///
|
||||
/// The thread which will receive the signal is unspecified.
|
||||
ARROW_EXPORT
|
||||
Status SendSignal(int signum);
|
||||
|
||||
/// \brief Send a signal to the given thread
|
||||
///
|
||||
/// This function isn't supported on Windows.
|
||||
ARROW_EXPORT
|
||||
Status SendSignalToThread(int signum, uint64_t thread_id);
|
||||
|
||||
/// \brief Get an unpredictable random seed
|
||||
///
|
||||
/// This function may be slightly costly, so should only be used to initialize
|
||||
/// a PRNG, not to generate a large amount of random numbers.
|
||||
/// It is better to use this function rather than std::random_device, unless
|
||||
/// absolutely necessary (e.g. to generate a cryptographic secret).
|
||||
ARROW_EXPORT
|
||||
int64_t GetRandomSeed();
|
||||
|
||||
/// \brief Get the current thread id
|
||||
///
|
||||
/// In addition to having the same properties as std::thread, the returned value
|
||||
/// is a regular integer value, which is more convenient than an opaque type.
|
||||
ARROW_EXPORT
|
||||
uint64_t GetThreadId();
|
||||
|
||||
/// \brief Get the current memory used by the current process in bytes
|
||||
///
|
||||
/// This function supports Windows, Linux, and Mac and will return 0 otherwise
|
||||
ARROW_EXPORT
|
||||
int64_t GetCurrentRSS();
|
||||
|
||||
/// \brief Get the total memory available to the system in bytes
|
||||
///
|
||||
/// This function supports Windows, Linux, and Mac and will return 0 otherwise
|
||||
ARROW_EXPORT
|
||||
int64_t GetTotalMemoryBytes();
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,568 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/util/compare.h"
|
||||
#include "arrow/util/functional.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
template <typename T>
|
||||
class Iterator;
|
||||
|
||||
template <typename T>
|
||||
struct IterationTraits {
|
||||
/// \brief a reserved value which indicates the end of iteration. By
|
||||
/// default this is NULLPTR since most iterators yield pointer types.
|
||||
/// Specialize IterationTraits if different end semantics are required.
|
||||
///
|
||||
/// Note: This should not be used to determine if a given value is a
|
||||
/// terminal value. Use IsIterationEnd (which uses IsEnd) instead. This
|
||||
/// is only for returning terminal values.
|
||||
static T End() { return T(NULLPTR); }
|
||||
|
||||
/// \brief Checks to see if the value is a terminal value.
|
||||
/// A method is used here since T is not neccesarily comparable in many
|
||||
/// cases even though it has a distinct final value
|
||||
static bool IsEnd(const T& val) { return val == End(); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
T IterationEnd() {
|
||||
return IterationTraits<T>::End();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsIterationEnd(const T& val) {
|
||||
return IterationTraits<T>::IsEnd(val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct IterationTraits<std::optional<T>> {
|
||||
/// \brief by default when iterating through a sequence of optional,
|
||||
/// nullopt indicates the end of iteration.
|
||||
/// Specialize IterationTraits if different end semantics are required.
|
||||
static std::optional<T> End() { return std::nullopt; }
|
||||
|
||||
/// \brief by default when iterating through a sequence of optional,
|
||||
/// nullopt (!has_value()) indicates the end of iteration.
|
||||
/// Specialize IterationTraits if different end semantics are required.
|
||||
static bool IsEnd(const std::optional<T>& val) { return !val.has_value(); }
|
||||
|
||||
// TODO(bkietz) The range-for loop over Iterator<optional<T>> yields
|
||||
// Result<optional<T>> which is unnecessary (since only the unyielded end optional
|
||||
// is nullopt. Add IterationTraits::GetRangeElement() to handle this case
|
||||
};
|
||||
|
||||
/// \brief A generic Iterator that can return errors
|
||||
template <typename T>
|
||||
class Iterator : public util::EqualityComparable<Iterator<T>> {
|
||||
public:
|
||||
/// \brief Iterator may be constructed from any type which has a member function
|
||||
/// with signature Result<T> Next();
|
||||
/// End of iterator is signalled by returning IteratorTraits<T>::End();
|
||||
///
|
||||
/// The argument is moved or copied to the heap and kept in a unique_ptr<void>. Only
|
||||
/// its destructor and its Next method (which are stored in function pointers) are
|
||||
/// referenced after construction.
|
||||
///
|
||||
/// This approach is used to dodge MSVC linkage hell (ARROW-6244, ARROW-6558) when using
|
||||
/// an abstract template base class: instead of being inlined as usual for a template
|
||||
/// function the base's virtual destructor will be exported, leading to multiple
|
||||
/// definition errors when linking to any other TU where the base is instantiated.
|
||||
template <typename Wrapped>
|
||||
explicit Iterator(Wrapped has_next)
|
||||
: ptr_(new Wrapped(std::move(has_next)), Delete<Wrapped>), next_(Next<Wrapped>) {}
|
||||
|
||||
Iterator() : ptr_(NULLPTR, [](void*) {}) {}
|
||||
|
||||
/// \brief Return the next element of the sequence, IterationTraits<T>::End() when the
|
||||
/// iteration is completed. Calling this on a default constructed Iterator
|
||||
/// will result in undefined behavior.
|
||||
Result<T> Next() { return next_(ptr_.get()); }
|
||||
|
||||
/// Pass each element of the sequence to a visitor. Will return any error status
|
||||
/// returned by the visitor, terminating iteration.
|
||||
template <typename Visitor>
|
||||
Status Visit(Visitor&& visitor) {
|
||||
for (;;) {
|
||||
ARROW_ASSIGN_OR_RAISE(auto value, Next());
|
||||
|
||||
if (IsIterationEnd(value)) break;
|
||||
|
||||
ARROW_RETURN_NOT_OK(visitor(std::move(value)));
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
/// Iterators will only compare equal if they are both null.
|
||||
/// Equality comparability is required to make an Iterator of Iterators
|
||||
/// (to check for the end condition).
|
||||
bool Equals(const Iterator& other) const { return ptr_ == other.ptr_; }
|
||||
|
||||
explicit operator bool() const { return ptr_ != NULLPTR; }
|
||||
|
||||
class RangeIterator {
|
||||
public:
|
||||
RangeIterator() : value_(IterationTraits<T>::End()) {}
|
||||
|
||||
explicit RangeIterator(Iterator i)
|
||||
: value_(IterationTraits<T>::End()),
|
||||
iterator_(std::make_shared<Iterator>(std::move(i))) {
|
||||
Next();
|
||||
}
|
||||
|
||||
bool operator!=(const RangeIterator& other) const { return value_ != other.value_; }
|
||||
|
||||
RangeIterator& operator++() {
|
||||
Next();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Result<T> operator*() {
|
||||
ARROW_RETURN_NOT_OK(value_.status());
|
||||
|
||||
auto value = std::move(value_);
|
||||
value_ = IterationTraits<T>::End();
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
void Next() {
|
||||
if (!value_.ok()) {
|
||||
value_ = IterationTraits<T>::End();
|
||||
return;
|
||||
}
|
||||
value_ = iterator_->Next();
|
||||
}
|
||||
|
||||
Result<T> value_;
|
||||
std::shared_ptr<Iterator> iterator_;
|
||||
};
|
||||
|
||||
RangeIterator begin() { return RangeIterator(std::move(*this)); }
|
||||
|
||||
RangeIterator end() { return RangeIterator(); }
|
||||
|
||||
/// \brief Move every element of this iterator into a vector.
|
||||
Result<std::vector<T>> ToVector() {
|
||||
std::vector<T> out;
|
||||
for (auto maybe_element : *this) {
|
||||
ARROW_ASSIGN_OR_RAISE(auto element, maybe_element);
|
||||
out.push_back(std::move(element));
|
||||
}
|
||||
// ARROW-8193: On gcc-4.8 without the explicit move it tries to use the
|
||||
// copy constructor, which may be deleted on the elements of type T
|
||||
return std::move(out);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Implementation of deleter for ptr_: Casts from void* to the wrapped type and
|
||||
/// deletes that.
|
||||
template <typename HasNext>
|
||||
static void Delete(void* ptr) {
|
||||
delete static_cast<HasNext*>(ptr);
|
||||
}
|
||||
|
||||
/// Implementation of Next: Casts from void* to the wrapped type and invokes that
|
||||
/// type's Next member function.
|
||||
template <typename HasNext>
|
||||
static Result<T> Next(void* ptr) {
|
||||
return static_cast<HasNext*>(ptr)->Next();
|
||||
}
|
||||
|
||||
/// ptr_ is a unique_ptr to void with a custom deleter: a function pointer which first
|
||||
/// casts from void* to a pointer to the wrapped type then deletes that.
|
||||
std::unique_ptr<void, void (*)(void*)> ptr_;
|
||||
|
||||
/// next_ is a function pointer which first casts from void* to a pointer to the wrapped
|
||||
/// type then invokes its Next member function.
|
||||
Result<T> (*next_)(void*) = NULLPTR;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct TransformFlow {
|
||||
using YieldValueType = T;
|
||||
|
||||
TransformFlow(YieldValueType value, bool ready_for_next)
|
||||
: finished_(false),
|
||||
ready_for_next_(ready_for_next),
|
||||
yield_value_(std::move(value)) {}
|
||||
TransformFlow(bool finished, bool ready_for_next)
|
||||
: finished_(finished), ready_for_next_(ready_for_next), yield_value_() {}
|
||||
|
||||
bool HasValue() const { return yield_value_.has_value(); }
|
||||
bool Finished() const { return finished_; }
|
||||
bool ReadyForNext() const { return ready_for_next_; }
|
||||
T Value() const { return *yield_value_; }
|
||||
|
||||
bool finished_ = false;
|
||||
bool ready_for_next_ = false;
|
||||
std::optional<YieldValueType> yield_value_;
|
||||
};
|
||||
|
||||
struct TransformFinish {
|
||||
template <typename T>
|
||||
operator TransformFlow<T>() && { // NOLINT explicit
|
||||
return TransformFlow<T>(true, true);
|
||||
}
|
||||
};
|
||||
|
||||
struct TransformSkip {
|
||||
template <typename T>
|
||||
operator TransformFlow<T>() && { // NOLINT explicit
|
||||
return TransformFlow<T>(false, true);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
TransformFlow<T> TransformYield(T value = {}, bool ready_for_next = true) {
|
||||
return TransformFlow<T>(std::move(value), ready_for_next);
|
||||
}
|
||||
|
||||
template <typename T, typename V>
|
||||
using Transformer = std::function<Result<TransformFlow<V>>(T)>;
|
||||
|
||||
template <typename T, typename V>
|
||||
class TransformIterator {
|
||||
public:
|
||||
explicit TransformIterator(Iterator<T> it, Transformer<T, V> transformer)
|
||||
: it_(std::move(it)),
|
||||
transformer_(std::move(transformer)),
|
||||
last_value_(),
|
||||
finished_() {}
|
||||
|
||||
Result<V> Next() {
|
||||
while (!finished_) {
|
||||
ARROW_ASSIGN_OR_RAISE(std::optional<V> next, Pump());
|
||||
if (next.has_value()) {
|
||||
return std::move(*next);
|
||||
}
|
||||
ARROW_ASSIGN_OR_RAISE(last_value_, it_.Next());
|
||||
}
|
||||
return IterationTraits<V>::End();
|
||||
}
|
||||
|
||||
private:
|
||||
// Calls the transform function on the current value. Can return in several ways
|
||||
// * If the next value is requested (e.g. skip) it will return an empty optional
|
||||
// * If an invalid status is encountered that will be returned
|
||||
// * If finished it will return IterationTraits<V>::End()
|
||||
// * If a value is returned by the transformer that will be returned
|
||||
Result<std::optional<V>> Pump() {
|
||||
if (!finished_ && last_value_.has_value()) {
|
||||
auto next_res = transformer_(*last_value_);
|
||||
if (!next_res.ok()) {
|
||||
finished_ = true;
|
||||
return next_res.status();
|
||||
}
|
||||
auto next = *next_res;
|
||||
if (next.ReadyForNext()) {
|
||||
if (IsIterationEnd(*last_value_)) {
|
||||
finished_ = true;
|
||||
}
|
||||
last_value_.reset();
|
||||
}
|
||||
if (next.Finished()) {
|
||||
finished_ = true;
|
||||
}
|
||||
if (next.HasValue()) {
|
||||
return next.Value();
|
||||
}
|
||||
}
|
||||
if (finished_) {
|
||||
return IterationTraits<V>::End();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Iterator<T> it_;
|
||||
Transformer<T, V> transformer_;
|
||||
std::optional<T> last_value_;
|
||||
bool finished_ = false;
|
||||
};
|
||||
|
||||
/// \brief Transforms an iterator according to a transformer, returning a new Iterator.
|
||||
///
|
||||
/// The transformer will be called on each element of the source iterator and for each
|
||||
/// call it can yield a value, skip, or finish the iteration. When yielding a value the
|
||||
/// transformer can choose to consume the source item (the default, ready_for_next = true)
|
||||
/// or to keep it and it will be called again on the same value.
|
||||
///
|
||||
/// This is essentially a more generic form of the map operation that can return 0, 1, or
|
||||
/// many values for each of the source items.
|
||||
///
|
||||
/// The transformer will be exposed to the end of the source sequence
|
||||
/// (IterationTraits::End) in case it needs to return some penultimate item(s).
|
||||
///
|
||||
/// Any invalid status returned by the transformer will be returned immediately.
|
||||
template <typename T, typename V>
|
||||
Iterator<V> MakeTransformedIterator(Iterator<T> it, Transformer<T, V> op) {
|
||||
return Iterator<V>(TransformIterator<T, V>(std::move(it), std::move(op)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct IterationTraits<Iterator<T>> {
|
||||
// The end condition for an Iterator of Iterators is a default constructed (null)
|
||||
// Iterator.
|
||||
static Iterator<T> End() { return Iterator<T>(); }
|
||||
static bool IsEnd(const Iterator<T>& val) { return !val; }
|
||||
};
|
||||
|
||||
template <typename Fn, typename T>
|
||||
class FunctionIterator {
|
||||
public:
|
||||
explicit FunctionIterator(Fn fn) : fn_(std::move(fn)) {}
|
||||
|
||||
Result<T> Next() { return fn_(); }
|
||||
|
||||
private:
|
||||
Fn fn_;
|
||||
};
|
||||
|
||||
/// \brief Construct an Iterator which invokes a callable on Next()
|
||||
template <typename Fn,
|
||||
typename Ret = typename internal::call_traits::return_type<Fn>::ValueType>
|
||||
Iterator<Ret> MakeFunctionIterator(Fn fn) {
|
||||
return Iterator<Ret>(FunctionIterator<Fn, Ret>(std::move(fn)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Iterator<T> MakeEmptyIterator() {
|
||||
return MakeFunctionIterator([]() -> Result<T> { return IterationTraits<T>::End(); });
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Iterator<T> MakeErrorIterator(Status s) {
|
||||
return MakeFunctionIterator([s]() -> Result<T> {
|
||||
ARROW_RETURN_NOT_OK(s);
|
||||
return IterationTraits<T>::End();
|
||||
});
|
||||
}
|
||||
|
||||
/// \brief Simple iterator which yields the elements of a std::vector
|
||||
template <typename T>
|
||||
class VectorIterator {
|
||||
public:
|
||||
explicit VectorIterator(std::vector<T> v) : elements_(std::move(v)) {}
|
||||
|
||||
Result<T> Next() {
|
||||
if (i_ == elements_.size()) {
|
||||
return IterationTraits<T>::End();
|
||||
}
|
||||
return std::move(elements_[i_++]);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> elements_;
|
||||
size_t i_ = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Iterator<T> MakeVectorIterator(std::vector<T> v) {
|
||||
return Iterator<T>(VectorIterator<T>(std::move(v)));
|
||||
}
|
||||
|
||||
/// \brief Simple iterator which yields *pointers* to the elements of a std::vector<T>.
|
||||
/// This is provided to support T where IterationTraits<T>::End is not specialized
|
||||
template <typename T>
|
||||
class VectorPointingIterator {
|
||||
public:
|
||||
explicit VectorPointingIterator(std::vector<T> v) : elements_(std::move(v)) {}
|
||||
|
||||
Result<T*> Next() {
|
||||
if (i_ == elements_.size()) {
|
||||
return NULLPTR;
|
||||
}
|
||||
return &elements_[i_++];
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> elements_;
|
||||
size_t i_ = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Iterator<T*> MakeVectorPointingIterator(std::vector<T> v) {
|
||||
return Iterator<T*>(VectorPointingIterator<T>(std::move(v)));
|
||||
}
|
||||
|
||||
/// \brief MapIterator takes ownership of an iterator and a function to apply
|
||||
/// on every element. The mapped function is not allowed to fail.
|
||||
template <typename Fn, typename I, typename O>
|
||||
class MapIterator {
|
||||
public:
|
||||
explicit MapIterator(Fn map, Iterator<I> it)
|
||||
: map_(std::move(map)), it_(std::move(it)) {}
|
||||
|
||||
Result<O> Next() {
|
||||
ARROW_ASSIGN_OR_RAISE(I i, it_.Next());
|
||||
|
||||
if (IsIterationEnd(i)) {
|
||||
return IterationTraits<O>::End();
|
||||
}
|
||||
|
||||
return map_(std::move(i));
|
||||
}
|
||||
|
||||
private:
|
||||
Fn map_;
|
||||
Iterator<I> it_;
|
||||
};
|
||||
|
||||
/// \brief MapIterator takes ownership of an iterator and a function to apply
|
||||
/// on every element. The mapped function is not allowed to fail.
|
||||
template <typename Fn, typename From = internal::call_traits::argument_type<0, Fn>,
|
||||
typename To = internal::call_traits::return_type<Fn>>
|
||||
Iterator<To> MakeMapIterator(Fn map, Iterator<From> it) {
|
||||
return Iterator<To>(MapIterator<Fn, From, To>(std::move(map), std::move(it)));
|
||||
}
|
||||
|
||||
/// \brief Like MapIterator, but where the function can fail.
|
||||
template <typename Fn, typename From = internal::call_traits::argument_type<0, Fn>,
|
||||
typename To = typename internal::call_traits::return_type<Fn>::ValueType>
|
||||
Iterator<To> MakeMaybeMapIterator(Fn map, Iterator<From> it) {
|
||||
return Iterator<To>(MapIterator<Fn, From, To>(std::move(map), std::move(it)));
|
||||
}
|
||||
|
||||
struct FilterIterator {
|
||||
enum Action { ACCEPT, REJECT };
|
||||
|
||||
template <typename To>
|
||||
static Result<std::pair<To, Action>> Reject() {
|
||||
return std::make_pair(IterationTraits<To>::End(), REJECT);
|
||||
}
|
||||
|
||||
template <typename To>
|
||||
static Result<std::pair<To, Action>> Accept(To out) {
|
||||
return std::make_pair(std::move(out), ACCEPT);
|
||||
}
|
||||
|
||||
template <typename To>
|
||||
static Result<std::pair<To, Action>> MaybeAccept(Result<To> maybe_out) {
|
||||
return std::move(maybe_out).Map(Accept<To>);
|
||||
}
|
||||
|
||||
template <typename To>
|
||||
static Result<std::pair<To, Action>> Error(Status s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename Fn, typename From, typename To>
|
||||
class Impl {
|
||||
public:
|
||||
explicit Impl(Fn filter, Iterator<From> it) : filter_(filter), it_(std::move(it)) {}
|
||||
|
||||
Result<To> Next() {
|
||||
To out = IterationTraits<To>::End();
|
||||
Action action;
|
||||
|
||||
for (;;) {
|
||||
ARROW_ASSIGN_OR_RAISE(From i, it_.Next());
|
||||
|
||||
if (IsIterationEnd(i)) {
|
||||
return IterationTraits<To>::End();
|
||||
}
|
||||
|
||||
ARROW_ASSIGN_OR_RAISE(std::tie(out, action), filter_(std::move(i)));
|
||||
|
||||
if (action == ACCEPT) return out;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Fn filter_;
|
||||
Iterator<From> it_;
|
||||
};
|
||||
};
|
||||
|
||||
/// \brief Like MapIterator, but where the function can fail or reject elements.
|
||||
template <
|
||||
typename Fn, typename From = typename internal::call_traits::argument_type<0, Fn>,
|
||||
typename Ret = typename internal::call_traits::return_type<Fn>::ValueType,
|
||||
typename To = typename std::tuple_element<0, Ret>::type,
|
||||
typename Enable = typename std::enable_if<std::is_same<
|
||||
typename std::tuple_element<1, Ret>::type, FilterIterator::Action>::value>::type>
|
||||
Iterator<To> MakeFilterIterator(Fn filter, Iterator<From> it) {
|
||||
return Iterator<To>(
|
||||
FilterIterator::Impl<Fn, From, To>(std::move(filter), std::move(it)));
|
||||
}
|
||||
|
||||
/// \brief FlattenIterator takes an iterator generating iterators and yields a
|
||||
/// unified iterator that flattens/concatenates in a single stream.
|
||||
template <typename T>
|
||||
class FlattenIterator {
|
||||
public:
|
||||
explicit FlattenIterator(Iterator<Iterator<T>> it) : parent_(std::move(it)) {}
|
||||
|
||||
Result<T> Next() {
|
||||
if (IsIterationEnd(child_)) {
|
||||
// Pop from parent's iterator.
|
||||
ARROW_ASSIGN_OR_RAISE(child_, parent_.Next());
|
||||
|
||||
// Check if final iteration reached.
|
||||
if (IsIterationEnd(child_)) {
|
||||
return IterationTraits<T>::End();
|
||||
}
|
||||
|
||||
return Next();
|
||||
}
|
||||
|
||||
// Pop from child_ and check for depletion.
|
||||
ARROW_ASSIGN_OR_RAISE(T out, child_.Next());
|
||||
if (IsIterationEnd(out)) {
|
||||
// Reset state such that we pop from parent on the recursive call
|
||||
child_ = IterationTraits<Iterator<T>>::End();
|
||||
|
||||
return Next();
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
Iterator<Iterator<T>> parent_;
|
||||
Iterator<T> child_ = IterationTraits<Iterator<T>>::End();
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Iterator<T> MakeFlattenIterator(Iterator<Iterator<T>> it) {
|
||||
return Iterator<T>(FlattenIterator<T>(std::move(it)));
|
||||
}
|
||||
|
||||
template <typename Reader>
|
||||
Iterator<typename Reader::ValueType> MakeIteratorFromReader(
|
||||
const std::shared_ptr<Reader>& reader) {
|
||||
return MakeFunctionIterator([reader] { return reader->Next(); });
|
||||
}
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
/// \brief A container for key-value pair type metadata. Not thread-safe
|
||||
class ARROW_EXPORT KeyValueMetadata {
|
||||
public:
|
||||
KeyValueMetadata();
|
||||
KeyValueMetadata(std::vector<std::string> keys, std::vector<std::string> values);
|
||||
explicit KeyValueMetadata(const std::unordered_map<std::string, std::string>& map);
|
||||
|
||||
static std::shared_ptr<KeyValueMetadata> Make(std::vector<std::string> keys,
|
||||
std::vector<std::string> values);
|
||||
|
||||
void ToUnorderedMap(std::unordered_map<std::string, std::string>* out) const;
|
||||
void Append(std::string key, std::string value);
|
||||
|
||||
Result<std::string> Get(const std::string& key) const;
|
||||
bool Contains(const std::string& key) const;
|
||||
// Note that deleting may invalidate known indices
|
||||
Status Delete(const std::string& key);
|
||||
Status Delete(int64_t index);
|
||||
Status DeleteMany(std::vector<int64_t> indices);
|
||||
Status Set(const std::string& key, const std::string& value);
|
||||
|
||||
void reserve(int64_t n);
|
||||
|
||||
int64_t size() const;
|
||||
const std::string& key(int64_t i) const;
|
||||
const std::string& value(int64_t i) const;
|
||||
const std::vector<std::string>& keys() const { return keys_; }
|
||||
const std::vector<std::string>& values() const { return values_; }
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> sorted_pairs() const;
|
||||
|
||||
/// \brief Perform linear search for key, returning -1 if not found
|
||||
int FindKey(const std::string& key) const;
|
||||
|
||||
std::shared_ptr<KeyValueMetadata> Copy() const;
|
||||
|
||||
/// \brief Return a new KeyValueMetadata by combining the passed metadata
|
||||
/// with this KeyValueMetadata. Colliding keys will be overridden by the
|
||||
/// passed metadata. Assumes keys in both containers are unique
|
||||
std::shared_ptr<KeyValueMetadata> Merge(const KeyValueMetadata& other) const;
|
||||
|
||||
bool Equals(const KeyValueMetadata& other) const;
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
std::vector<std::string> keys_;
|
||||
std::vector<std::string> values_;
|
||||
|
||||
ARROW_DISALLOW_COPY_AND_ASSIGN(KeyValueMetadata);
|
||||
};
|
||||
|
||||
/// \brief Create a KeyValueMetadata instance
|
||||
///
|
||||
/// \param pairs key-value mapping
|
||||
ARROW_EXPORT std::shared_ptr<KeyValueMetadata> key_value_metadata(
|
||||
const std::unordered_map<std::string, std::string>& pairs);
|
||||
|
||||
/// \brief Create a KeyValueMetadata instance
|
||||
///
|
||||
/// \param keys sequence of metadata keys
|
||||
/// \param values sequence of corresponding metadata values
|
||||
ARROW_EXPORT std::shared_ptr<KeyValueMetadata> key_value_metadata(
|
||||
std::vector<std::string> keys, std::vector<std::string> values);
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <new>
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
#if __cpp_lib_launder
|
||||
using std::launder;
|
||||
#else
|
||||
template <class T>
|
||||
constexpr T* launder(T* p) noexcept {
|
||||
return p;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,259 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef GANDIVA_IR
|
||||
|
||||
// The LLVM IR code doesn't have an NDEBUG mode. And, it shouldn't include references to
|
||||
// streams or stdc++. So, making the DCHECK calls void in that case.
|
||||
|
||||
#define ARROW_IGNORE_EXPR(expr) ((void)(expr))
|
||||
|
||||
#define DCHECK(condition) ARROW_IGNORE_EXPR(condition)
|
||||
#define DCHECK_OK(status) ARROW_IGNORE_EXPR(status)
|
||||
#define DCHECK_EQ(val1, val2) ARROW_IGNORE_EXPR(val1)
|
||||
#define DCHECK_NE(val1, val2) ARROW_IGNORE_EXPR(val1)
|
||||
#define DCHECK_LE(val1, val2) ARROW_IGNORE_EXPR(val1)
|
||||
#define DCHECK_LT(val1, val2) ARROW_IGNORE_EXPR(val1)
|
||||
#define DCHECK_GE(val1, val2) ARROW_IGNORE_EXPR(val1)
|
||||
#define DCHECK_GT(val1, val2) ARROW_IGNORE_EXPR(val1)
|
||||
|
||||
#else // !GANDIVA_IR
|
||||
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
enum class ArrowLogLevel : int {
|
||||
ARROW_DEBUG = -1,
|
||||
ARROW_INFO = 0,
|
||||
ARROW_WARNING = 1,
|
||||
ARROW_ERROR = 2,
|
||||
ARROW_FATAL = 3
|
||||
};
|
||||
|
||||
#define ARROW_LOG_INTERNAL(level) ::arrow::util::ArrowLog(__FILE__, __LINE__, level)
|
||||
#define ARROW_LOG(level) ARROW_LOG_INTERNAL(::arrow::util::ArrowLogLevel::ARROW_##level)
|
||||
|
||||
#define ARROW_IGNORE_EXPR(expr) ((void)(expr))
|
||||
|
||||
#define ARROW_CHECK_OR_LOG(condition, level) \
|
||||
ARROW_PREDICT_TRUE(condition) \
|
||||
? ARROW_IGNORE_EXPR(0) \
|
||||
: ::arrow::util::Voidify() & ARROW_LOG(level) << " Check failed: " #condition " "
|
||||
|
||||
#define ARROW_CHECK(condition) ARROW_CHECK_OR_LOG(condition, FATAL)
|
||||
|
||||
// If 'to_call' returns a bad status, CHECK immediately with a logged message
|
||||
// of 'msg' followed by the status.
|
||||
#define ARROW_CHECK_OK_PREPEND(to_call, msg, level) \
|
||||
do { \
|
||||
::arrow::Status _s = (to_call); \
|
||||
ARROW_CHECK_OR_LOG(_s.ok(), level) \
|
||||
<< "Operation failed: " << ARROW_STRINGIFY(to_call) << "\n" \
|
||||
<< (msg) << ": " << _s.ToString(); \
|
||||
} while (false)
|
||||
|
||||
// If the status is bad, CHECK immediately, appending the status to the
|
||||
// logged message.
|
||||
#define ARROW_CHECK_OK(s) ARROW_CHECK_OK_PREPEND(s, "Bad status", FATAL)
|
||||
|
||||
#define ARROW_CHECK_EQ(val1, val2) ARROW_CHECK((val1) == (val2))
|
||||
#define ARROW_CHECK_NE(val1, val2) ARROW_CHECK((val1) != (val2))
|
||||
#define ARROW_CHECK_LE(val1, val2) ARROW_CHECK((val1) <= (val2))
|
||||
#define ARROW_CHECK_LT(val1, val2) ARROW_CHECK((val1) < (val2))
|
||||
#define ARROW_CHECK_GE(val1, val2) ARROW_CHECK((val1) >= (val2))
|
||||
#define ARROW_CHECK_GT(val1, val2) ARROW_CHECK((val1) > (val2))
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define ARROW_DFATAL ::arrow::util::ArrowLogLevel::ARROW_WARNING
|
||||
|
||||
// CAUTION: DCHECK_OK() always evaluates its argument, but other DCHECK*() macros
|
||||
// only do so in debug mode.
|
||||
|
||||
#define ARROW_DCHECK(condition) \
|
||||
while (false) ARROW_IGNORE_EXPR(condition); \
|
||||
while (false) ::arrow::util::detail::NullLog()
|
||||
#define ARROW_DCHECK_OK(s) \
|
||||
ARROW_IGNORE_EXPR(s); \
|
||||
while (false) ::arrow::util::detail::NullLog()
|
||||
#define ARROW_DCHECK_EQ(val1, val2) \
|
||||
while (false) ARROW_IGNORE_EXPR(val1); \
|
||||
while (false) ARROW_IGNORE_EXPR(val2); \
|
||||
while (false) ::arrow::util::detail::NullLog()
|
||||
#define ARROW_DCHECK_NE(val1, val2) \
|
||||
while (false) ARROW_IGNORE_EXPR(val1); \
|
||||
while (false) ARROW_IGNORE_EXPR(val2); \
|
||||
while (false) ::arrow::util::detail::NullLog()
|
||||
#define ARROW_DCHECK_LE(val1, val2) \
|
||||
while (false) ARROW_IGNORE_EXPR(val1); \
|
||||
while (false) ARROW_IGNORE_EXPR(val2); \
|
||||
while (false) ::arrow::util::detail::NullLog()
|
||||
#define ARROW_DCHECK_LT(val1, val2) \
|
||||
while (false) ARROW_IGNORE_EXPR(val1); \
|
||||
while (false) ARROW_IGNORE_EXPR(val2); \
|
||||
while (false) ::arrow::util::detail::NullLog()
|
||||
#define ARROW_DCHECK_GE(val1, val2) \
|
||||
while (false) ARROW_IGNORE_EXPR(val1); \
|
||||
while (false) ARROW_IGNORE_EXPR(val2); \
|
||||
while (false) ::arrow::util::detail::NullLog()
|
||||
#define ARROW_DCHECK_GT(val1, val2) \
|
||||
while (false) ARROW_IGNORE_EXPR(val1); \
|
||||
while (false) ARROW_IGNORE_EXPR(val2); \
|
||||
while (false) ::arrow::util::detail::NullLog()
|
||||
|
||||
#else
|
||||
#define ARROW_DFATAL ::arrow::util::ArrowLogLevel::ARROW_FATAL
|
||||
|
||||
#define ARROW_DCHECK ARROW_CHECK
|
||||
#define ARROW_DCHECK_OK ARROW_CHECK_OK
|
||||
#define ARROW_DCHECK_EQ ARROW_CHECK_EQ
|
||||
#define ARROW_DCHECK_NE ARROW_CHECK_NE
|
||||
#define ARROW_DCHECK_LE ARROW_CHECK_LE
|
||||
#define ARROW_DCHECK_LT ARROW_CHECK_LT
|
||||
#define ARROW_DCHECK_GE ARROW_CHECK_GE
|
||||
#define ARROW_DCHECK_GT ARROW_CHECK_GT
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
#define DCHECK ARROW_DCHECK
|
||||
#define DCHECK_OK ARROW_DCHECK_OK
|
||||
#define DCHECK_EQ ARROW_DCHECK_EQ
|
||||
#define DCHECK_NE ARROW_DCHECK_NE
|
||||
#define DCHECK_LE ARROW_DCHECK_LE
|
||||
#define DCHECK_LT ARROW_DCHECK_LT
|
||||
#define DCHECK_GE ARROW_DCHECK_GE
|
||||
#define DCHECK_GT ARROW_DCHECK_GT
|
||||
|
||||
// This code is adapted from
|
||||
// https://github.com/ray-project/ray/blob/master/src/ray/util/logging.h.
|
||||
|
||||
// To make the logging lib pluggable with other logging libs and make
|
||||
// the implementation unawared by the user, ArrowLog is only a declaration
|
||||
// which hide the implementation into logging.cc file.
|
||||
// In logging.cc, we can choose different log libs using different macros.
|
||||
|
||||
// This is also a null log which does not output anything.
|
||||
class ARROW_EXPORT ArrowLogBase {
|
||||
public:
|
||||
virtual ~ArrowLogBase() {}
|
||||
|
||||
virtual bool IsEnabled() const { return false; }
|
||||
|
||||
template <typename T>
|
||||
ArrowLogBase& operator<<(const T& t) {
|
||||
if (IsEnabled()) {
|
||||
Stream() << t;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual std::ostream& Stream() = 0;
|
||||
};
|
||||
|
||||
class ARROW_EXPORT ArrowLog : public ArrowLogBase {
|
||||
public:
|
||||
ArrowLog(const char* file_name, int line_number, ArrowLogLevel severity);
|
||||
~ArrowLog() override;
|
||||
|
||||
/// Return whether or not current logging instance is enabled.
|
||||
///
|
||||
/// \return True if logging is enabled and false otherwise.
|
||||
bool IsEnabled() const override;
|
||||
|
||||
/// The init function of arrow log for a program which should be called only once.
|
||||
///
|
||||
/// \param appName The app name which starts the log.
|
||||
/// \param severity_threshold Logging threshold for the program.
|
||||
/// \param logDir Logging output file name. If empty, the log won't output to file.
|
||||
static void StartArrowLog(const std::string& appName,
|
||||
ArrowLogLevel severity_threshold = ArrowLogLevel::ARROW_INFO,
|
||||
const std::string& logDir = "");
|
||||
|
||||
/// The shutdown function of arrow log, it should be used with StartArrowLog as a pair.
|
||||
static void ShutDownArrowLog();
|
||||
|
||||
/// Install the failure signal handler to output call stack when crash.
|
||||
/// If glog is not installed, this function won't do anything.
|
||||
static void InstallFailureSignalHandler();
|
||||
|
||||
/// Uninstall the signal actions installed by InstallFailureSignalHandler.
|
||||
static void UninstallSignalAction();
|
||||
|
||||
/// Return whether or not the log level is enabled in current setting.
|
||||
///
|
||||
/// \param log_level The input log level to test.
|
||||
/// \return True if input log level is not lower than the threshold.
|
||||
static bool IsLevelEnabled(ArrowLogLevel log_level);
|
||||
|
||||
private:
|
||||
ARROW_DISALLOW_COPY_AND_ASSIGN(ArrowLog);
|
||||
|
||||
// Hide the implementation of log provider by void *.
|
||||
// Otherwise, lib user may define the same macro to use the correct header file.
|
||||
void* logging_provider_;
|
||||
/// True if log messages should be logged and false if they should be ignored.
|
||||
bool is_enabled_;
|
||||
|
||||
static ArrowLogLevel severity_threshold_;
|
||||
|
||||
protected:
|
||||
std::ostream& Stream() override;
|
||||
};
|
||||
|
||||
// This class make ARROW_CHECK compilation pass to change the << operator to void.
|
||||
// This class is copied from glog.
|
||||
class ARROW_EXPORT Voidify {
|
||||
public:
|
||||
Voidify() {}
|
||||
// This has to be an operator with a precedence lower than << but
|
||||
// higher than ?:
|
||||
void operator&(ArrowLogBase&) {}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
/// @brief A helper for the nil log sink.
|
||||
///
|
||||
/// Using this helper is analogous to sending log messages to /dev/null:
|
||||
/// nothing gets logged.
|
||||
class NullLog {
|
||||
public:
|
||||
/// The no-op output operator.
|
||||
///
|
||||
/// @param [in] t
|
||||
/// The object to send into the nil sink.
|
||||
/// @return Reference to the updated object.
|
||||
template <class T>
|
||||
NullLog& operator<<(const T& t) {
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
|
||||
#endif // GANDIVA_IR
|
||||
@@ -0,0 +1,191 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#define ARROW_EXPAND(x) x
|
||||
#define ARROW_STRINGIFY(x) #x
|
||||
#define ARROW_CONCAT(x, y) x##y
|
||||
|
||||
// From Google gutil
|
||||
#ifndef ARROW_DISALLOW_COPY_AND_ASSIGN
|
||||
#define ARROW_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&) = delete; \
|
||||
void operator=(const TypeName&) = delete
|
||||
#endif
|
||||
|
||||
#ifndef ARROW_DEFAULT_MOVE_AND_ASSIGN
|
||||
#define ARROW_DEFAULT_MOVE_AND_ASSIGN(TypeName) \
|
||||
TypeName(TypeName&&) = default; \
|
||||
TypeName& operator=(TypeName&&) = default
|
||||
#endif
|
||||
|
||||
#define ARROW_UNUSED(x) (void)(x)
|
||||
#define ARROW_ARG_UNUSED(x)
|
||||
//
|
||||
// GCC can be told that a certain branch is not likely to be taken (for
|
||||
// instance, a CHECK failure), and use that information in static analysis.
|
||||
// Giving it this information can help it optimize for the common case in
|
||||
// the absence of better information (ie. -fprofile-arcs).
|
||||
//
|
||||
#if defined(__GNUC__)
|
||||
#define ARROW_PREDICT_FALSE(x) (__builtin_expect(!!(x), 0))
|
||||
#define ARROW_PREDICT_TRUE(x) (__builtin_expect(!!(x), 1))
|
||||
#define ARROW_NORETURN __attribute__((noreturn))
|
||||
#define ARROW_NOINLINE __attribute__((noinline))
|
||||
#define ARROW_PREFETCH(addr) __builtin_prefetch(addr)
|
||||
#elif defined(_MSC_VER)
|
||||
#define ARROW_NORETURN __declspec(noreturn)
|
||||
#define ARROW_NOINLINE __declspec(noinline)
|
||||
#define ARROW_PREDICT_FALSE(x) (x)
|
||||
#define ARROW_PREDICT_TRUE(x) (x)
|
||||
#define ARROW_PREFETCH(addr)
|
||||
#else
|
||||
#define ARROW_NORETURN
|
||||
#define ARROW_PREDICT_FALSE(x) (x)
|
||||
#define ARROW_PREDICT_TRUE(x) (x)
|
||||
#define ARROW_PREFETCH(addr)
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER)
|
||||
#define ARROW_RESTRICT __restrict
|
||||
#else
|
||||
#define ARROW_RESTRICT
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// C++/CLI support macros (see ARROW-1134)
|
||||
|
||||
#ifndef NULLPTR
|
||||
|
||||
#ifdef __cplusplus_cli
|
||||
#define NULLPTR __nullptr
|
||||
#else
|
||||
#define NULLPTR nullptr
|
||||
#endif
|
||||
|
||||
#endif // ifndef NULLPTR
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// clang-format off
|
||||
// [[deprecated]] is only available in C++14, use this for the time being
|
||||
// This macro takes an optional deprecation message
|
||||
#ifdef __COVERITY__
|
||||
# define ARROW_DEPRECATED(...)
|
||||
#else
|
||||
# define ARROW_DEPRECATED(...) [[deprecated(__VA_ARGS__)]]
|
||||
#endif
|
||||
|
||||
#ifdef __COVERITY__
|
||||
# define ARROW_DEPRECATED_ENUM_VALUE(...)
|
||||
#else
|
||||
# define ARROW_DEPRECATED_ENUM_VALUE(...) [[deprecated(__VA_ARGS__)]]
|
||||
#endif
|
||||
|
||||
// clang-format on
|
||||
|
||||
// Macros to disable deprecation warnings
|
||||
|
||||
#ifdef __clang__
|
||||
#define ARROW_SUPPRESS_DEPRECATION_WARNING \
|
||||
_Pragma("clang diagnostic push"); \
|
||||
_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
|
||||
#define ARROW_UNSUPPRESS_DEPRECATION_WARNING _Pragma("clang diagnostic pop")
|
||||
#elif defined(__GNUC__)
|
||||
#define ARROW_SUPPRESS_DEPRECATION_WARNING \
|
||||
_Pragma("GCC diagnostic push"); \
|
||||
_Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
|
||||
#define ARROW_UNSUPPRESS_DEPRECATION_WARNING _Pragma("GCC diagnostic pop")
|
||||
#elif defined(_MSC_VER)
|
||||
#define ARROW_SUPPRESS_DEPRECATION_WARNING \
|
||||
__pragma(warning(push)) __pragma(warning(disable : 4996))
|
||||
#define ARROW_UNSUPPRESS_DEPRECATION_WARNING __pragma(warning(pop))
|
||||
#else
|
||||
#define ARROW_SUPPRESS_DEPRECATION_WARNING
|
||||
#define ARROW_UNSUPPRESS_DEPRECATION_WARNING
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// macros to disable padding
|
||||
// these macros are portable across different compilers and platforms
|
||||
//[https://github.com/google/flatbuffers/blob/master/include/flatbuffers/flatbuffers.h#L1355]
|
||||
#if !defined(MANUALLY_ALIGNED_STRUCT)
|
||||
#if defined(_MSC_VER)
|
||||
#define MANUALLY_ALIGNED_STRUCT(alignment) \
|
||||
__pragma(pack(1)); \
|
||||
struct __declspec(align(alignment))
|
||||
#define STRUCT_END(name, size) \
|
||||
__pragma(pack()); \
|
||||
static_assert(sizeof(name) == size, "compiler breaks packing rules")
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#define MANUALLY_ALIGNED_STRUCT(alignment) \
|
||||
_Pragma("pack(1)") struct __attribute__((aligned(alignment)))
|
||||
#define STRUCT_END(name, size) \
|
||||
_Pragma("pack()") static_assert(sizeof(name) == size, "compiler breaks packing rules")
|
||||
#else
|
||||
#error Unknown compiler, please define structure alignment macros
|
||||
#endif
|
||||
#endif // !defined(MANUALLY_ALIGNED_STRUCT)
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Convenience macro disabling a particular UBSan check in a function
|
||||
|
||||
#if defined(__clang__)
|
||||
#define ARROW_DISABLE_UBSAN(feature) __attribute__((no_sanitize(feature)))
|
||||
#else
|
||||
#define ARROW_DISABLE_UBSAN(feature)
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Machine information
|
||||
|
||||
#if INTPTR_MAX == INT64_MAX
|
||||
#define ARROW_BITNESS 64
|
||||
#elif INTPTR_MAX == INT32_MAX
|
||||
#define ARROW_BITNESS 32
|
||||
#else
|
||||
#error Unexpected INTPTR_MAX
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// From googletest
|
||||
// (also in parquet-cpp)
|
||||
|
||||
// When you need to test the private or protected members of a class,
|
||||
// use the FRIEND_TEST macro to declare your tests as friends of the
|
||||
// class. For example:
|
||||
//
|
||||
// class MyClass {
|
||||
// private:
|
||||
// void MyMethod();
|
||||
// FRIEND_TEST(MyClassTest, MyMethod);
|
||||
// };
|
||||
//
|
||||
// class MyClassTest : public testing::Test {
|
||||
// // ...
|
||||
// };
|
||||
//
|
||||
// TEST_F(MyClassTest, MyMethod) {
|
||||
// // Can call MyClass::MyMethod() here.
|
||||
// }
|
||||
|
||||
#define FRIEND_TEST(test_case_name, test_name) \
|
||||
friend class test_case_name##_##test_name##_Test
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/result.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
/// Helper providing single-lookup conditional insertion into std::map or
|
||||
/// std::unordered_map. If `key` exists in the container, an iterator to that pair
|
||||
/// will be returned. If `key` does not exist in the container, `gen(key)` will be
|
||||
/// invoked and its return value inserted.
|
||||
template <typename Map, typename Gen>
|
||||
auto GetOrInsertGenerated(Map* map, typename Map::key_type key, Gen&& gen)
|
||||
-> decltype(map->begin()->second = gen(map->begin()->first), map->begin()) {
|
||||
decltype(gen(map->begin()->first)) placeholder{};
|
||||
|
||||
auto it_success = map->emplace(std::move(key), std::move(placeholder));
|
||||
if (it_success.second) {
|
||||
// insertion of placeholder succeeded, overwrite it with gen()
|
||||
const auto& inserted_key = it_success.first->first;
|
||||
auto* value = &it_success.first->second;
|
||||
*value = gen(inserted_key);
|
||||
}
|
||||
return it_success.first;
|
||||
}
|
||||
|
||||
template <typename Map, typename Gen>
|
||||
auto GetOrInsertGenerated(Map* map, typename Map::key_type key, Gen&& gen)
|
||||
-> Result<decltype(map->begin()->second = gen(map->begin()->first).ValueOrDie(),
|
||||
map->begin())> {
|
||||
decltype(gen(map->begin()->first).ValueOrDie()) placeholder{};
|
||||
|
||||
auto it_success = map->emplace(std::move(key), std::move(placeholder));
|
||||
if (it_success.second) {
|
||||
// insertion of placeholder succeeded, overwrite it with gen()
|
||||
const auto& inserted_key = it_success.first->first;
|
||||
auto* value = &it_success.first->second;
|
||||
ARROW_ASSIGN_OR_RAISE(*value, gen(inserted_key));
|
||||
}
|
||||
return it_success.first;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
// Not provided by default in MSVC,
|
||||
// and _USE_MATH_DEFINES is not reliable with unity builds
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
#ifndef M_PI_2
|
||||
#define M_PI_2 1.57079632679489661923
|
||||
#endif
|
||||
#ifndef M_PI_4
|
||||
#define M_PI_4 0.785398163397448309616
|
||||
#endif
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "arrow/util/macros.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
// A helper function for doing memcpy with multiple threads. This is required
|
||||
// to saturate the memory bandwidth of modern cpus.
|
||||
void parallel_memcopy(uint8_t* dst, const uint8_t* src, int64_t nbytes,
|
||||
uintptr_t block_size, int num_threads);
|
||||
|
||||
// A helper function for checking if two wrapped objects implementing `Equals`
|
||||
// are equal.
|
||||
template <typename T>
|
||||
bool SharedPtrEquals(const std::shared_ptr<T>& left, const std::shared_ptr<T>& right) {
|
||||
if (left == right) return true;
|
||||
if (left == NULLPTR || right == NULLPTR) return false;
|
||||
return left->Equals(*right);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
/// A wrapper around std::mutex since we can't use it directly in
|
||||
/// public headers due to C++/CLI.
|
||||
/// https://docs.microsoft.com/en-us/cpp/standard-library/mutex#remarks
|
||||
class ARROW_EXPORT Mutex {
|
||||
public:
|
||||
Mutex();
|
||||
Mutex(Mutex&&) = default;
|
||||
Mutex& operator=(Mutex&&) = default;
|
||||
|
||||
/// A Guard is falsy if a lock could not be acquired.
|
||||
class ARROW_EXPORT Guard {
|
||||
public:
|
||||
Guard() : locked_(NULLPTR, [](Mutex* mutex) {}) {}
|
||||
Guard(Guard&&) = default;
|
||||
Guard& operator=(Guard&&) = default;
|
||||
|
||||
explicit operator bool() const { return bool(locked_); }
|
||||
|
||||
void Unlock() { locked_.reset(); }
|
||||
|
||||
private:
|
||||
explicit Guard(Mutex* locked);
|
||||
|
||||
std::unique_ptr<Mutex, void (*)(Mutex*)> locked_;
|
||||
friend Mutex;
|
||||
};
|
||||
|
||||
Guard TryLock();
|
||||
Guard Lock();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl, void (*)(Impl*)> impl_;
|
||||
};
|
||||
|
||||
#ifndef _WIN32
|
||||
/// Return a pointer to a process-wide, process-specific Mutex that can be used
|
||||
/// at any point in a child process. NULL is returned when called in the parent.
|
||||
///
|
||||
/// The rule is to first check that getpid() corresponds to the parent process pid
|
||||
/// and, if not, call this function to lock any after-fork reinitialization code.
|
||||
/// Like this:
|
||||
///
|
||||
/// std::atomic<pid_t> pid{getpid()};
|
||||
/// ...
|
||||
/// if (pid.load() != getpid()) {
|
||||
/// // In child process
|
||||
/// auto lock = GlobalForkSafeMutex()->Lock();
|
||||
/// if (pid.load() != getpid()) {
|
||||
/// // Reinitialize internal structures after fork
|
||||
/// ...
|
||||
/// pid.store(getpid());
|
||||
ARROW_EXPORT
|
||||
Mutex* GlobalForkSafeMutex();
|
||||
#endif
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,102 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/util/functional.h"
|
||||
#include "arrow/util/thread_pool.h"
|
||||
#include "arrow/util/vector.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
// A parallelizer that takes a `Status(int)` function and calls it with
|
||||
// arguments between 0 and `num_tasks - 1`, on an arbitrary number of threads.
|
||||
|
||||
template <class FUNCTION>
|
||||
Status ParallelFor(int num_tasks, FUNCTION&& func,
|
||||
Executor* executor = internal::GetCpuThreadPool()) {
|
||||
std::vector<Future<>> futures(num_tasks);
|
||||
|
||||
for (int i = 0; i < num_tasks; ++i) {
|
||||
ARROW_ASSIGN_OR_RAISE(futures[i], executor->Submit(func, i));
|
||||
}
|
||||
auto st = Status::OK();
|
||||
for (auto& fut : futures) {
|
||||
st &= fut.status();
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
template <class FUNCTION, typename T,
|
||||
typename R = typename internal::call_traits::return_type<FUNCTION>::ValueType>
|
||||
Future<std::vector<R>> ParallelForAsync(
|
||||
std::vector<T> inputs, FUNCTION&& func,
|
||||
Executor* executor = internal::GetCpuThreadPool()) {
|
||||
std::vector<Future<R>> futures(inputs.size());
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
ARROW_ASSIGN_OR_RAISE(futures[i], executor->Submit(func, i, std::move(inputs[i])));
|
||||
}
|
||||
return All(std::move(futures))
|
||||
.Then([](const std::vector<Result<R>>& results) -> Result<std::vector<R>> {
|
||||
return UnwrapOrRaise(results);
|
||||
});
|
||||
}
|
||||
|
||||
// A parallelizer that takes a `Status(int)` function and calls it with
|
||||
// arguments between 0 and `num_tasks - 1`, in sequence or in parallel,
|
||||
// depending on the input boolean.
|
||||
|
||||
template <class FUNCTION>
|
||||
Status OptionalParallelFor(bool use_threads, int num_tasks, FUNCTION&& func,
|
||||
Executor* executor = internal::GetCpuThreadPool()) {
|
||||
if (use_threads) {
|
||||
return ParallelFor(num_tasks, std::forward<FUNCTION>(func), executor);
|
||||
} else {
|
||||
for (int i = 0; i < num_tasks; ++i) {
|
||||
RETURN_NOT_OK(func(i));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
// A parallelizer that takes a `Result<R>(int index, T item)` function and
|
||||
// calls it with each item from the input array, in sequence or in parallel,
|
||||
// depending on the input boolean.
|
||||
|
||||
template <class FUNCTION, typename T,
|
||||
typename R = typename internal::call_traits::return_type<FUNCTION>::ValueType>
|
||||
Future<std::vector<R>> OptionalParallelForAsync(
|
||||
bool use_threads, std::vector<T> inputs, FUNCTION&& func,
|
||||
Executor* executor = internal::GetCpuThreadPool()) {
|
||||
if (use_threads) {
|
||||
return ParallelForAsync(std::move(inputs), std::forward<FUNCTION>(func), executor);
|
||||
} else {
|
||||
std::vector<R> result(inputs.size());
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
ARROW_ASSIGN_OR_RAISE(result[i], func(i, inputs[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arrow/vendored/pcg/pcg_random.hpp" // IWYU pragma: export
|
||||
|
||||
namespace arrow {
|
||||
namespace random {
|
||||
|
||||
using pcg32 = ::arrow_vendored::pcg32;
|
||||
using pcg64 = ::arrow_vendored::pcg64;
|
||||
using pcg32_fast = ::arrow_vendored::pcg32_fast;
|
||||
using pcg64_fast = ::arrow_vendored::pcg64_fast;
|
||||
using pcg32_oneseq = ::arrow_vendored::pcg32_oneseq;
|
||||
using pcg64_oneseq = ::arrow_vendored::pcg64_oneseq;
|
||||
|
||||
} // namespace random
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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. template <typename T>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename OStream, typename Tuple, size_t N>
|
||||
struct TuplePrinter {
|
||||
static void Print(OStream* os, const Tuple& t) {
|
||||
TuplePrinter<OStream, Tuple, N - 1>::Print(os, t);
|
||||
*os << std::get<N - 1>(t);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename OStream, typename Tuple>
|
||||
struct TuplePrinter<OStream, Tuple, 0> {
|
||||
static void Print(OStream* os, const Tuple& t) {}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Print elements from a tuple to a stream, in order.
|
||||
// Typical use is to pack a bunch of existing values with std::forward_as_tuple()
|
||||
// before passing it to this function.
|
||||
template <typename OStream, typename... Args>
|
||||
void PrintTuple(OStream* os, const std::tuple<Args&...>& tup) {
|
||||
detail::TuplePrinter<OStream, std::tuple<Args&...>, sizeof...(Args)>::Print(os, tup);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arrow/vendored/ProducerConsumerQueue.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
template <typename T>
|
||||
using SpscQueue = arrow_vendored::folly::ProducerConsumerQueue<T>;
|
||||
|
||||
}
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,155 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
/// Create a vector containing the values from start up to stop
|
||||
template <typename T>
|
||||
std::vector<T> Iota(T start, T stop) {
|
||||
if (start > stop) {
|
||||
return {};
|
||||
}
|
||||
std::vector<T> result(static_cast<size_t>(stop - start));
|
||||
std::iota(result.begin(), result.end(), start);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Create a vector containing the values from 0 up to length
|
||||
template <typename T>
|
||||
std::vector<T> Iota(T length) {
|
||||
return Iota(static_cast<T>(0), length);
|
||||
}
|
||||
|
||||
/// Create a range from a callable which takes a single index parameter
|
||||
/// and returns the value of iterator on each call and a length.
|
||||
/// Only iterators obtained from the same range should be compared, the
|
||||
/// behaviour generally similar to other STL containers.
|
||||
template <typename Generator>
|
||||
class LazyRange {
|
||||
private:
|
||||
// callable which generates the values
|
||||
// has to be defined at the beginning of the class for type deduction
|
||||
const Generator gen_;
|
||||
// the length of the range
|
||||
int64_t length_;
|
||||
#ifdef _MSC_VER
|
||||
// workaround to VS2010 not supporting decltype properly
|
||||
// see https://stackoverflow.com/questions/21782846/decltype-for-class-member-function
|
||||
static Generator gen_static_;
|
||||
#endif
|
||||
|
||||
public:
|
||||
#ifdef _MSC_VER
|
||||
using return_type = decltype(gen_static_(0));
|
||||
#else
|
||||
using return_type = decltype(gen_(0));
|
||||
#endif
|
||||
|
||||
/// Construct a new range from a callable and length
|
||||
LazyRange(Generator gen, int64_t length) : gen_(gen), length_(length) {}
|
||||
|
||||
// Class of the dependent iterator, created implicitly by begin and end
|
||||
class RangeIter {
|
||||
public:
|
||||
using difference_type = int64_t;
|
||||
using value_type = return_type;
|
||||
using reference = const value_type&;
|
||||
using pointer = const value_type*;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// msvc complains about unchecked iterators,
|
||||
// see https://stackoverflow.com/questions/21655496/error-c4996-checked-iterators
|
||||
using _Unchecked_type = typename LazyRange<Generator>::RangeIter;
|
||||
#endif
|
||||
|
||||
RangeIter() = delete;
|
||||
RangeIter(const RangeIter& other) = default;
|
||||
RangeIter& operator=(const RangeIter& other) = default;
|
||||
|
||||
RangeIter(const LazyRange<Generator>& range, int64_t index)
|
||||
: range_(&range), index_(index) {}
|
||||
|
||||
const return_type operator*() const { return range_->gen_(index_); }
|
||||
|
||||
RangeIter operator+(difference_type length) const {
|
||||
return RangeIter(*range_, index_ + length);
|
||||
}
|
||||
|
||||
// pre-increment
|
||||
RangeIter& operator++() {
|
||||
++index_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// post-increment
|
||||
RangeIter operator++(int) {
|
||||
auto copy = RangeIter(*this);
|
||||
++index_;
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool operator==(const typename LazyRange<Generator>::RangeIter& other) const {
|
||||
return this->index_ == other.index_ && this->range_ == other.range_;
|
||||
}
|
||||
|
||||
bool operator!=(const typename LazyRange<Generator>::RangeIter& other) const {
|
||||
return this->index_ != other.index_ || this->range_ != other.range_;
|
||||
}
|
||||
|
||||
int64_t operator-(const typename LazyRange<Generator>::RangeIter& other) const {
|
||||
return this->index_ - other.index_;
|
||||
}
|
||||
|
||||
bool operator<(const typename LazyRange<Generator>::RangeIter& other) const {
|
||||
return this->index_ < other.index_;
|
||||
}
|
||||
|
||||
private:
|
||||
// parent range reference
|
||||
const LazyRange* range_;
|
||||
// current index
|
||||
int64_t index_;
|
||||
};
|
||||
|
||||
friend class RangeIter;
|
||||
|
||||
// Create a new begin const iterator
|
||||
RangeIter begin() { return RangeIter(*this, 0); }
|
||||
|
||||
// Create a new end const iterator
|
||||
RangeIter end() { return RangeIter(*this, length_); }
|
||||
};
|
||||
|
||||
/// Helper function to create a lazy range from a callable (e.g. lambda) and length
|
||||
template <typename Generator>
|
||||
LazyRange<Generator> MakeLazyRange(Generator&& gen, int64_t length) {
|
||||
return LazyRange<Generator>(std::forward<Generator>(gen), length);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <initializer_list>
|
||||
#include <regex>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
/// Match regex against target and produce string_views out of matches.
|
||||
inline bool RegexMatch(const std::regex& regex, std::string_view target,
|
||||
std::initializer_list<std::string_view*> out_matches) {
|
||||
assert(regex.mark_count() == out_matches.size());
|
||||
|
||||
std::match_results<decltype(target.begin())> match;
|
||||
if (!std::regex_match(target.begin(), target.end(), match, regex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match #0 is the whole matched sequence
|
||||
assert(regex.mark_count() + 1 == match.size());
|
||||
auto out_it = out_matches.begin();
|
||||
for (size_t i = 1; i < match.size(); ++i) {
|
||||
**out_it++ = target.substr(match.position(i), match.length(i));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,827 @@
|
||||
// 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.
|
||||
|
||||
// Imported from Apache Impala (incubating) on 2016-01-29 and modified for use
|
||||
// in parquet-cpp, Arrow
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/util/bit_block_counter.h"
|
||||
#include "arrow/util/bit_run_reader.h"
|
||||
#include "arrow/util/bit_stream_utils.h"
|
||||
#include "arrow/util/bit_util.h"
|
||||
#include "arrow/util/macros.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
/// Utility classes to do run length encoding (RLE) for fixed bit width values. If runs
|
||||
/// are sufficiently long, RLE is used, otherwise, the values are just bit-packed
|
||||
/// (literal encoding).
|
||||
/// For both types of runs, there is a byte-aligned indicator which encodes the length
|
||||
/// of the run and the type of the run.
|
||||
/// This encoding has the benefit that when there aren't any long enough runs, values
|
||||
/// are always decoded at fixed (can be precomputed) bit offsets OR both the value and
|
||||
/// the run length are byte aligned. This allows for very efficient decoding
|
||||
/// implementations.
|
||||
/// The encoding is:
|
||||
/// encoded-block := run*
|
||||
/// run := literal-run | repeated-run
|
||||
/// literal-run := literal-indicator < literal bytes >
|
||||
/// repeated-run := repeated-indicator < repeated value. padded to byte boundary >
|
||||
/// literal-indicator := varint_encode( number_of_groups << 1 | 1)
|
||||
/// repeated-indicator := varint_encode( number_of_repetitions << 1 )
|
||||
//
|
||||
/// Each run is preceded by a varint. The varint's least significant bit is
|
||||
/// used to indicate whether the run is a literal run or a repeated run. The rest
|
||||
/// of the varint is used to determine the length of the run (eg how many times the
|
||||
/// value repeats).
|
||||
//
|
||||
/// In the case of literal runs, the run length is always a multiple of 8 (i.e. encode
|
||||
/// in groups of 8), so that no matter the bit-width of the value, the sequence will end
|
||||
/// on a byte boundary without padding.
|
||||
/// Given that we know it is a multiple of 8, we store the number of 8-groups rather than
|
||||
/// the actual number of encoded ints. (This means that the total number of encoded values
|
||||
/// can not be determined from the encoded data, since the number of values in the last
|
||||
/// group may not be a multiple of 8). For the last group of literal runs, we pad
|
||||
/// the group to 8 with zeros. This allows for 8 at a time decoding on the read side
|
||||
/// without the need for additional checks.
|
||||
//
|
||||
/// There is a break-even point when it is more storage efficient to do run length
|
||||
/// encoding. For 1 bit-width values, that point is 8 values. They require 2 bytes
|
||||
/// for both the repeated encoding or the literal encoding. This value can always
|
||||
/// be computed based on the bit-width.
|
||||
/// TODO: think about how to use this for strings. The bit packing isn't quite the same.
|
||||
//
|
||||
/// Examples with bit-width 1 (eg encoding booleans):
|
||||
/// ----------------------------------------
|
||||
/// 100 1s followed by 100 0s:
|
||||
/// <varint(100 << 1)> <1, padded to 1 byte> <varint(100 << 1)> <0, padded to 1 byte>
|
||||
/// - (total 4 bytes)
|
||||
//
|
||||
/// alternating 1s and 0s (200 total):
|
||||
/// 200 ints = 25 groups of 8
|
||||
/// <varint((25 << 1) | 1)> <25 bytes of values, bitpacked>
|
||||
/// (total 26 bytes, 1 byte overhead)
|
||||
//
|
||||
|
||||
/// Decoder class for RLE encoded data.
|
||||
class RleDecoder {
|
||||
public:
|
||||
/// Create a decoder object. buffer/buffer_len is the decoded data.
|
||||
/// bit_width is the width of each value (before encoding).
|
||||
RleDecoder(const uint8_t* buffer, int buffer_len, int bit_width)
|
||||
: bit_reader_(buffer, buffer_len),
|
||||
bit_width_(bit_width),
|
||||
current_value_(0),
|
||||
repeat_count_(0),
|
||||
literal_count_(0) {
|
||||
DCHECK_GE(bit_width_, 0);
|
||||
DCHECK_LE(bit_width_, 64);
|
||||
}
|
||||
|
||||
RleDecoder() : bit_width_(-1) {}
|
||||
|
||||
void Reset(const uint8_t* buffer, int buffer_len, int bit_width) {
|
||||
DCHECK_GE(bit_width, 0);
|
||||
DCHECK_LE(bit_width, 64);
|
||||
bit_reader_.Reset(buffer, buffer_len);
|
||||
bit_width_ = bit_width;
|
||||
current_value_ = 0;
|
||||
repeat_count_ = 0;
|
||||
literal_count_ = 0;
|
||||
}
|
||||
|
||||
/// Gets the next value. Returns false if there are no more.
|
||||
template <typename T>
|
||||
bool Get(T* val);
|
||||
|
||||
/// Gets a batch of values. Returns the number of decoded elements.
|
||||
template <typename T>
|
||||
int GetBatch(T* values, int batch_size);
|
||||
|
||||
/// Like GetBatch but add spacing for null entries
|
||||
template <typename T>
|
||||
int GetBatchSpaced(int batch_size, int null_count, const uint8_t* valid_bits,
|
||||
int64_t valid_bits_offset, T* out);
|
||||
|
||||
/// Like GetBatch but the values are then decoded using the provided dictionary
|
||||
template <typename T>
|
||||
int GetBatchWithDict(const T* dictionary, int32_t dictionary_length, T* values,
|
||||
int batch_size);
|
||||
|
||||
/// Like GetBatchWithDict but add spacing for null entries
|
||||
///
|
||||
/// Null entries will be zero-initialized in `values` to avoid leaking
|
||||
/// private data.
|
||||
template <typename T>
|
||||
int GetBatchWithDictSpaced(const T* dictionary, int32_t dictionary_length, T* values,
|
||||
int batch_size, int null_count, const uint8_t* valid_bits,
|
||||
int64_t valid_bits_offset);
|
||||
|
||||
protected:
|
||||
::arrow::bit_util::BitReader bit_reader_;
|
||||
/// Number of bits needed to encode the value. Must be between 0 and 64.
|
||||
int bit_width_;
|
||||
uint64_t current_value_;
|
||||
int32_t repeat_count_;
|
||||
int32_t literal_count_;
|
||||
|
||||
private:
|
||||
/// Fills literal_count_ and repeat_count_ with next values. Returns false if there
|
||||
/// are no more.
|
||||
template <typename T>
|
||||
bool NextCounts();
|
||||
|
||||
/// Utility methods for retrieving spaced values.
|
||||
template <typename T, typename RunType, typename Converter>
|
||||
int GetSpaced(Converter converter, int batch_size, int null_count,
|
||||
const uint8_t* valid_bits, int64_t valid_bits_offset, T* out);
|
||||
};
|
||||
|
||||
/// Class to incrementally build the rle data. This class does not allocate any memory.
|
||||
/// The encoding has two modes: encoding repeated runs and literal runs.
|
||||
/// If the run is sufficiently short, it is more efficient to encode as a literal run.
|
||||
/// This class does so by buffering 8 values at a time. If they are not all the same
|
||||
/// they are added to the literal run. If they are the same, they are added to the
|
||||
/// repeated run. When we switch modes, the previous run is flushed out.
|
||||
class RleEncoder {
|
||||
public:
|
||||
/// buffer/buffer_len: preallocated output buffer.
|
||||
/// bit_width: max number of bits for value.
|
||||
/// TODO: consider adding a min_repeated_run_length so the caller can control
|
||||
/// when values should be encoded as repeated runs. Currently this is derived
|
||||
/// based on the bit_width, which can determine a storage optimal choice.
|
||||
/// TODO: allow 0 bit_width (and have dict encoder use it)
|
||||
RleEncoder(uint8_t* buffer, int buffer_len, int bit_width)
|
||||
: bit_width_(bit_width), bit_writer_(buffer, buffer_len) {
|
||||
DCHECK_GE(bit_width_, 0);
|
||||
DCHECK_LE(bit_width_, 64);
|
||||
max_run_byte_size_ = MinBufferSize(bit_width);
|
||||
DCHECK_GE(buffer_len, max_run_byte_size_) << "Input buffer not big enough.";
|
||||
Clear();
|
||||
}
|
||||
|
||||
/// Returns the minimum buffer size needed to use the encoder for 'bit_width'
|
||||
/// This is the maximum length of a single run for 'bit_width'.
|
||||
/// It is not valid to pass a buffer less than this length.
|
||||
static int MinBufferSize(int bit_width) {
|
||||
/// 1 indicator byte and MAX_VALUES_PER_LITERAL_RUN 'bit_width' values.
|
||||
int max_literal_run_size = 1 + static_cast<int>(::arrow::bit_util::BytesForBits(
|
||||
MAX_VALUES_PER_LITERAL_RUN * bit_width));
|
||||
/// Up to kMaxVlqByteLength indicator and a single 'bit_width' value.
|
||||
int max_repeated_run_size =
|
||||
::arrow::bit_util::BitReader::kMaxVlqByteLength +
|
||||
static_cast<int>(::arrow::bit_util::BytesForBits(bit_width));
|
||||
return std::max(max_literal_run_size, max_repeated_run_size);
|
||||
}
|
||||
|
||||
/// Returns the maximum byte size it could take to encode 'num_values'.
|
||||
static int MaxBufferSize(int bit_width, int num_values) {
|
||||
// For a bit_width > 1, the worst case is the repetition of "literal run of length 8
|
||||
// and then a repeated run of length 8".
|
||||
// 8 values per smallest run, 8 bits per byte
|
||||
int bytes_per_run = bit_width;
|
||||
int num_runs = static_cast<int>(::arrow::bit_util::CeilDiv(num_values, 8));
|
||||
int literal_max_size = num_runs + num_runs * bytes_per_run;
|
||||
|
||||
// In the very worst case scenario, the data is a concatenation of repeated
|
||||
// runs of 8 values. Repeated run has a 1 byte varint followed by the
|
||||
// bit-packed repeated value
|
||||
int min_repeated_run_size =
|
||||
1 + static_cast<int>(::arrow::bit_util::BytesForBits(bit_width));
|
||||
int repeated_max_size = static_cast<int>(::arrow::bit_util::CeilDiv(num_values, 8)) *
|
||||
min_repeated_run_size;
|
||||
|
||||
return std::max(literal_max_size, repeated_max_size);
|
||||
}
|
||||
|
||||
/// Encode value. Returns true if the value fits in buffer, false otherwise.
|
||||
/// This value must be representable with bit_width_ bits.
|
||||
bool Put(uint64_t value);
|
||||
|
||||
/// Flushes any pending values to the underlying buffer.
|
||||
/// Returns the total number of bytes written
|
||||
int Flush();
|
||||
|
||||
/// Resets all the state in the encoder.
|
||||
void Clear();
|
||||
|
||||
/// Returns pointer to underlying buffer
|
||||
uint8_t* buffer() { return bit_writer_.buffer(); }
|
||||
int32_t len() { return bit_writer_.bytes_written(); }
|
||||
|
||||
private:
|
||||
/// Flushes any buffered values. If this is part of a repeated run, this is largely
|
||||
/// a no-op.
|
||||
/// If it is part of a literal run, this will call FlushLiteralRun, which writes
|
||||
/// out the buffered literal values.
|
||||
/// If 'done' is true, the current run would be written even if it would normally
|
||||
/// have been buffered more. This should only be called at the end, when the
|
||||
/// encoder has received all values even if it would normally continue to be
|
||||
/// buffered.
|
||||
void FlushBufferedValues(bool done);
|
||||
|
||||
/// Flushes literal values to the underlying buffer. If update_indicator_byte,
|
||||
/// then the current literal run is complete and the indicator byte is updated.
|
||||
void FlushLiteralRun(bool update_indicator_byte);
|
||||
|
||||
/// Flushes a repeated run to the underlying buffer.
|
||||
void FlushRepeatedRun();
|
||||
|
||||
/// Checks and sets buffer_full_. This must be called after flushing a run to
|
||||
/// make sure there are enough bytes remaining to encode the next run.
|
||||
void CheckBufferFull();
|
||||
|
||||
/// The maximum number of values in a single literal run
|
||||
/// (number of groups encodable by a 1-byte indicator * 8)
|
||||
static const int MAX_VALUES_PER_LITERAL_RUN = (1 << 6) * 8;
|
||||
|
||||
/// Number of bits needed to encode the value. Must be between 0 and 64.
|
||||
const int bit_width_;
|
||||
|
||||
/// Underlying buffer.
|
||||
::arrow::bit_util::BitWriter bit_writer_;
|
||||
|
||||
/// If true, the buffer is full and subsequent Put()'s will fail.
|
||||
bool buffer_full_;
|
||||
|
||||
/// The maximum byte size a single run can take.
|
||||
int max_run_byte_size_;
|
||||
|
||||
/// We need to buffer at most 8 values for literals. This happens when the
|
||||
/// bit_width is 1 (so 8 values fit in one byte).
|
||||
/// TODO: generalize this to other bit widths
|
||||
int64_t buffered_values_[8];
|
||||
|
||||
/// Number of values in buffered_values_
|
||||
int num_buffered_values_;
|
||||
|
||||
/// The current (also last) value that was written and the count of how
|
||||
/// many times in a row that value has been seen. This is maintained even
|
||||
/// if we are in a literal run. If the repeat_count_ get high enough, we switch
|
||||
/// to encoding repeated runs.
|
||||
uint64_t current_value_;
|
||||
int repeat_count_;
|
||||
|
||||
/// Number of literals in the current run. This does not include the literals
|
||||
/// that might be in buffered_values_. Only after we've got a group big enough
|
||||
/// can we decide if they should part of the literal_count_ or repeat_count_
|
||||
int literal_count_;
|
||||
|
||||
/// Pointer to a byte in the underlying buffer that stores the indicator byte.
|
||||
/// This is reserved as soon as we need a literal run but the value is written
|
||||
/// when the literal run is complete.
|
||||
uint8_t* literal_indicator_byte_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline bool RleDecoder::Get(T* val) {
|
||||
return GetBatch(val, 1) == 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline int RleDecoder::GetBatch(T* values, int batch_size) {
|
||||
DCHECK_GE(bit_width_, 0);
|
||||
int values_read = 0;
|
||||
|
||||
auto* out = values;
|
||||
|
||||
while (values_read < batch_size) {
|
||||
int remaining = batch_size - values_read;
|
||||
|
||||
if (repeat_count_ > 0) { // Repeated value case.
|
||||
int repeat_batch = std::min(remaining, repeat_count_);
|
||||
std::fill(out, out + repeat_batch, static_cast<T>(current_value_));
|
||||
|
||||
repeat_count_ -= repeat_batch;
|
||||
values_read += repeat_batch;
|
||||
out += repeat_batch;
|
||||
} else if (literal_count_ > 0) {
|
||||
int literal_batch = std::min(remaining, literal_count_);
|
||||
int actual_read = bit_reader_.GetBatch(bit_width_, out, literal_batch);
|
||||
if (actual_read != literal_batch) {
|
||||
return values_read;
|
||||
}
|
||||
|
||||
literal_count_ -= literal_batch;
|
||||
values_read += literal_batch;
|
||||
out += literal_batch;
|
||||
} else {
|
||||
if (!NextCounts<T>()) return values_read;
|
||||
}
|
||||
}
|
||||
|
||||
return values_read;
|
||||
}
|
||||
|
||||
template <typename T, typename RunType, typename Converter>
|
||||
inline int RleDecoder::GetSpaced(Converter converter, int batch_size, int null_count,
|
||||
const uint8_t* valid_bits, int64_t valid_bits_offset,
|
||||
T* out) {
|
||||
if (ARROW_PREDICT_FALSE(null_count == batch_size)) {
|
||||
converter.FillZero(out, out + batch_size);
|
||||
return batch_size;
|
||||
}
|
||||
|
||||
DCHECK_GE(bit_width_, 0);
|
||||
int values_read = 0;
|
||||
int values_remaining = batch_size - null_count;
|
||||
|
||||
// Assume no bits to start.
|
||||
arrow::internal::BitRunReader bit_reader(valid_bits, valid_bits_offset,
|
||||
/*length=*/batch_size);
|
||||
arrow::internal::BitRun valid_run = bit_reader.NextRun();
|
||||
while (values_read < batch_size) {
|
||||
if (ARROW_PREDICT_FALSE(valid_run.length == 0)) {
|
||||
valid_run = bit_reader.NextRun();
|
||||
}
|
||||
|
||||
DCHECK_GT(batch_size, 0);
|
||||
DCHECK_GT(valid_run.length, 0);
|
||||
|
||||
if (valid_run.set) {
|
||||
if ((repeat_count_ == 0) && (literal_count_ == 0)) {
|
||||
if (!NextCounts<RunType>()) return values_read;
|
||||
DCHECK((repeat_count_ > 0) ^ (literal_count_ > 0));
|
||||
}
|
||||
|
||||
if (repeat_count_ > 0) {
|
||||
int repeat_batch = 0;
|
||||
// Consume the entire repeat counts incrementing repeat_batch to
|
||||
// be the total of nulls + values consumed, we only need to
|
||||
// get the total count because we can fill in the same value for
|
||||
// nulls and non-nulls. This proves to be a big efficiency win.
|
||||
while (repeat_count_ > 0 && (values_read + repeat_batch) < batch_size) {
|
||||
DCHECK_GT(valid_run.length, 0);
|
||||
if (valid_run.set) {
|
||||
int update_size = std::min(static_cast<int>(valid_run.length), repeat_count_);
|
||||
repeat_count_ -= update_size;
|
||||
repeat_batch += update_size;
|
||||
valid_run.length -= update_size;
|
||||
values_remaining -= update_size;
|
||||
} else {
|
||||
// We can consume all nulls here because we would do so on
|
||||
// the next loop anyways.
|
||||
repeat_batch += static_cast<int>(valid_run.length);
|
||||
valid_run.length = 0;
|
||||
}
|
||||
if (valid_run.length == 0) {
|
||||
valid_run = bit_reader.NextRun();
|
||||
}
|
||||
}
|
||||
RunType current_value = static_cast<RunType>(current_value_);
|
||||
if (ARROW_PREDICT_FALSE(!converter.IsValid(current_value))) {
|
||||
return values_read;
|
||||
}
|
||||
converter.Fill(out, out + repeat_batch, current_value);
|
||||
out += repeat_batch;
|
||||
values_read += repeat_batch;
|
||||
} else if (literal_count_ > 0) {
|
||||
int literal_batch = std::min(values_remaining, literal_count_);
|
||||
DCHECK_GT(literal_batch, 0);
|
||||
|
||||
// Decode the literals
|
||||
constexpr int kBufferSize = 1024;
|
||||
RunType indices[kBufferSize];
|
||||
literal_batch = std::min(literal_batch, kBufferSize);
|
||||
int actual_read = bit_reader_.GetBatch(bit_width_, indices, literal_batch);
|
||||
if (ARROW_PREDICT_FALSE(actual_read != literal_batch)) {
|
||||
return values_read;
|
||||
}
|
||||
if (!converter.IsValid(indices, /*length=*/actual_read)) {
|
||||
return values_read;
|
||||
}
|
||||
int skipped = 0;
|
||||
int literals_read = 0;
|
||||
while (literals_read < literal_batch) {
|
||||
if (valid_run.set) {
|
||||
int update_size = std::min(literal_batch - literals_read,
|
||||
static_cast<int>(valid_run.length));
|
||||
converter.Copy(out, indices + literals_read, update_size);
|
||||
literals_read += update_size;
|
||||
out += update_size;
|
||||
valid_run.length -= update_size;
|
||||
} else {
|
||||
converter.FillZero(out, out + valid_run.length);
|
||||
out += valid_run.length;
|
||||
skipped += static_cast<int>(valid_run.length);
|
||||
valid_run.length = 0;
|
||||
}
|
||||
if (valid_run.length == 0) {
|
||||
valid_run = bit_reader.NextRun();
|
||||
}
|
||||
}
|
||||
literal_count_ -= literal_batch;
|
||||
values_remaining -= literal_batch;
|
||||
values_read += literal_batch + skipped;
|
||||
}
|
||||
} else {
|
||||
converter.FillZero(out, out + valid_run.length);
|
||||
out += valid_run.length;
|
||||
values_read += static_cast<int>(valid_run.length);
|
||||
valid_run.length = 0;
|
||||
}
|
||||
}
|
||||
DCHECK_EQ(valid_run.length, 0);
|
||||
DCHECK_EQ(values_remaining, 0);
|
||||
return values_read;
|
||||
}
|
||||
|
||||
// Converter for GetSpaced that handles runs that get returned
|
||||
// directly as output.
|
||||
template <typename T>
|
||||
struct PlainRleConverter {
|
||||
T kZero = {};
|
||||
inline bool IsValid(const T& values) const { return true; }
|
||||
inline bool IsValid(const T* values, int32_t length) const { return true; }
|
||||
inline void Fill(T* begin, T* end, const T& run_value) const {
|
||||
std::fill(begin, end, run_value);
|
||||
}
|
||||
inline void FillZero(T* begin, T* end) { std::fill(begin, end, kZero); }
|
||||
inline void Copy(T* out, const T* values, int length) const {
|
||||
std::memcpy(out, values, length * sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline int RleDecoder::GetBatchSpaced(int batch_size, int null_count,
|
||||
const uint8_t* valid_bits,
|
||||
int64_t valid_bits_offset, T* out) {
|
||||
if (null_count == 0) {
|
||||
return GetBatch<T>(out, batch_size);
|
||||
}
|
||||
|
||||
PlainRleConverter<T> converter;
|
||||
arrow::internal::BitBlockCounter block_counter(valid_bits, valid_bits_offset,
|
||||
batch_size);
|
||||
|
||||
int total_processed = 0;
|
||||
int processed = 0;
|
||||
arrow::internal::BitBlockCount block;
|
||||
|
||||
do {
|
||||
block = block_counter.NextFourWords();
|
||||
if (block.length == 0) {
|
||||
break;
|
||||
}
|
||||
if (block.AllSet()) {
|
||||
processed = GetBatch<T>(out, block.length);
|
||||
} else if (block.NoneSet()) {
|
||||
converter.FillZero(out, out + block.length);
|
||||
processed = block.length;
|
||||
} else {
|
||||
processed = GetSpaced<T, /*RunType=*/T, PlainRleConverter<T>>(
|
||||
converter, block.length, block.length - block.popcount, valid_bits,
|
||||
valid_bits_offset, out);
|
||||
}
|
||||
total_processed += processed;
|
||||
out += block.length;
|
||||
valid_bits_offset += block.length;
|
||||
} while (processed == block.length);
|
||||
return total_processed;
|
||||
}
|
||||
|
||||
static inline bool IndexInRange(int32_t idx, int32_t dictionary_length) {
|
||||
return idx >= 0 && idx < dictionary_length;
|
||||
}
|
||||
|
||||
// Converter for GetSpaced that handles runs of returned dictionary
|
||||
// indices.
|
||||
template <typename T>
|
||||
struct DictionaryConverter {
|
||||
T kZero = {};
|
||||
const T* dictionary;
|
||||
int32_t dictionary_length;
|
||||
|
||||
inline bool IsValid(int32_t value) { return IndexInRange(value, dictionary_length); }
|
||||
|
||||
inline bool IsValid(const int32_t* values, int32_t length) const {
|
||||
using IndexType = int32_t;
|
||||
IndexType min_index = std::numeric_limits<IndexType>::max();
|
||||
IndexType max_index = std::numeric_limits<IndexType>::min();
|
||||
for (int x = 0; x < length; x++) {
|
||||
min_index = std::min(values[x], min_index);
|
||||
max_index = std::max(values[x], max_index);
|
||||
}
|
||||
|
||||
return IndexInRange(min_index, dictionary_length) &&
|
||||
IndexInRange(max_index, dictionary_length);
|
||||
}
|
||||
inline void Fill(T* begin, T* end, const int32_t& run_value) const {
|
||||
std::fill(begin, end, dictionary[run_value]);
|
||||
}
|
||||
inline void FillZero(T* begin, T* end) { std::fill(begin, end, kZero); }
|
||||
|
||||
inline void Copy(T* out, const int32_t* values, int length) const {
|
||||
for (int x = 0; x < length; x++) {
|
||||
out[x] = dictionary[values[x]];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline int RleDecoder::GetBatchWithDict(const T* dictionary, int32_t dictionary_length,
|
||||
T* values, int batch_size) {
|
||||
// Per https://github.com/apache/parquet-format/blob/master/Encodings.md,
|
||||
// the maximum dictionary index width in Parquet is 32 bits.
|
||||
using IndexType = int32_t;
|
||||
DictionaryConverter<T> converter;
|
||||
converter.dictionary = dictionary;
|
||||
converter.dictionary_length = dictionary_length;
|
||||
|
||||
DCHECK_GE(bit_width_, 0);
|
||||
int values_read = 0;
|
||||
|
||||
auto* out = values;
|
||||
|
||||
while (values_read < batch_size) {
|
||||
int remaining = batch_size - values_read;
|
||||
|
||||
if (repeat_count_ > 0) {
|
||||
auto idx = static_cast<IndexType>(current_value_);
|
||||
if (ARROW_PREDICT_FALSE(!IndexInRange(idx, dictionary_length))) {
|
||||
return values_read;
|
||||
}
|
||||
T val = dictionary[idx];
|
||||
|
||||
int repeat_batch = std::min(remaining, repeat_count_);
|
||||
std::fill(out, out + repeat_batch, val);
|
||||
|
||||
/* Upkeep counters */
|
||||
repeat_count_ -= repeat_batch;
|
||||
values_read += repeat_batch;
|
||||
out += repeat_batch;
|
||||
} else if (literal_count_ > 0) {
|
||||
constexpr int kBufferSize = 1024;
|
||||
IndexType indices[kBufferSize];
|
||||
|
||||
int literal_batch = std::min(remaining, literal_count_);
|
||||
literal_batch = std::min(literal_batch, kBufferSize);
|
||||
|
||||
int actual_read = bit_reader_.GetBatch(bit_width_, indices, literal_batch);
|
||||
if (ARROW_PREDICT_FALSE(actual_read != literal_batch)) {
|
||||
return values_read;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(!converter.IsValid(indices, /*length=*/literal_batch))) {
|
||||
return values_read;
|
||||
}
|
||||
converter.Copy(out, indices, literal_batch);
|
||||
|
||||
/* Upkeep counters */
|
||||
literal_count_ -= literal_batch;
|
||||
values_read += literal_batch;
|
||||
out += literal_batch;
|
||||
} else {
|
||||
if (!NextCounts<IndexType>()) return values_read;
|
||||
}
|
||||
}
|
||||
|
||||
return values_read;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline int RleDecoder::GetBatchWithDictSpaced(const T* dictionary,
|
||||
int32_t dictionary_length, T* out,
|
||||
int batch_size, int null_count,
|
||||
const uint8_t* valid_bits,
|
||||
int64_t valid_bits_offset) {
|
||||
if (null_count == 0) {
|
||||
return GetBatchWithDict<T>(dictionary, dictionary_length, out, batch_size);
|
||||
}
|
||||
arrow::internal::BitBlockCounter block_counter(valid_bits, valid_bits_offset,
|
||||
batch_size);
|
||||
using IndexType = int32_t;
|
||||
DictionaryConverter<T> converter;
|
||||
converter.dictionary = dictionary;
|
||||
converter.dictionary_length = dictionary_length;
|
||||
|
||||
int total_processed = 0;
|
||||
int processed = 0;
|
||||
arrow::internal::BitBlockCount block;
|
||||
do {
|
||||
block = block_counter.NextFourWords();
|
||||
if (block.length == 0) {
|
||||
break;
|
||||
}
|
||||
if (block.AllSet()) {
|
||||
processed = GetBatchWithDict<T>(dictionary, dictionary_length, out, block.length);
|
||||
} else if (block.NoneSet()) {
|
||||
converter.FillZero(out, out + block.length);
|
||||
processed = block.length;
|
||||
} else {
|
||||
processed = GetSpaced<T, /*RunType=*/IndexType, DictionaryConverter<T>>(
|
||||
converter, block.length, block.length - block.popcount, valid_bits,
|
||||
valid_bits_offset, out);
|
||||
}
|
||||
total_processed += processed;
|
||||
out += block.length;
|
||||
valid_bits_offset += block.length;
|
||||
} while (processed == block.length);
|
||||
return total_processed;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool RleDecoder::NextCounts() {
|
||||
// Read the next run's indicator int, it could be a literal or repeated run.
|
||||
// The int is encoded as a vlq-encoded value.
|
||||
uint32_t indicator_value = 0;
|
||||
if (!bit_reader_.GetVlqInt(&indicator_value)) return false;
|
||||
|
||||
// lsb indicates if it is a literal run or repeated run
|
||||
bool is_literal = indicator_value & 1;
|
||||
uint32_t count = indicator_value >> 1;
|
||||
if (is_literal) {
|
||||
if (ARROW_PREDICT_FALSE(count == 0 || count > static_cast<uint32_t>(INT32_MAX) / 8)) {
|
||||
return false;
|
||||
}
|
||||
literal_count_ = count * 8;
|
||||
} else {
|
||||
if (ARROW_PREDICT_FALSE(count == 0 || count > static_cast<uint32_t>(INT32_MAX))) {
|
||||
return false;
|
||||
}
|
||||
repeat_count_ = count;
|
||||
T value = {};
|
||||
if (!bit_reader_.GetAligned<T>(
|
||||
static_cast<int>(::arrow::bit_util::CeilDiv(bit_width_, 8)), &value)) {
|
||||
return false;
|
||||
}
|
||||
current_value_ = static_cast<uint64_t>(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// This function buffers input values 8 at a time. After seeing all 8 values,
|
||||
/// it decides whether they should be encoded as a literal or repeated run.
|
||||
inline bool RleEncoder::Put(uint64_t value) {
|
||||
DCHECK(bit_width_ == 64 || value < (1ULL << bit_width_));
|
||||
if (ARROW_PREDICT_FALSE(buffer_full_)) return false;
|
||||
|
||||
if (ARROW_PREDICT_TRUE(current_value_ == value)) {
|
||||
++repeat_count_;
|
||||
if (repeat_count_ > 8) {
|
||||
// This is just a continuation of the current run, no need to buffer the
|
||||
// values.
|
||||
// Note that this is the fast path for long repeated runs.
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (repeat_count_ >= 8) {
|
||||
// We had a run that was long enough but it has ended. Flush the
|
||||
// current repeated run.
|
||||
DCHECK_EQ(literal_count_, 0);
|
||||
FlushRepeatedRun();
|
||||
}
|
||||
repeat_count_ = 1;
|
||||
current_value_ = value;
|
||||
}
|
||||
|
||||
buffered_values_[num_buffered_values_] = value;
|
||||
if (++num_buffered_values_ == 8) {
|
||||
DCHECK_EQ(literal_count_ % 8, 0);
|
||||
FlushBufferedValues(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void RleEncoder::FlushLiteralRun(bool update_indicator_byte) {
|
||||
if (literal_indicator_byte_ == NULL) {
|
||||
// The literal indicator byte has not been reserved yet, get one now.
|
||||
literal_indicator_byte_ = bit_writer_.GetNextBytePtr();
|
||||
DCHECK(literal_indicator_byte_ != NULL);
|
||||
}
|
||||
|
||||
// Write all the buffered values as bit packed literals
|
||||
for (int i = 0; i < num_buffered_values_; ++i) {
|
||||
bool success = bit_writer_.PutValue(buffered_values_[i], bit_width_);
|
||||
DCHECK(success) << "There is a bug in using CheckBufferFull()";
|
||||
}
|
||||
num_buffered_values_ = 0;
|
||||
|
||||
if (update_indicator_byte) {
|
||||
// At this point we need to write the indicator byte for the literal run.
|
||||
// We only reserve one byte, to allow for streaming writes of literal values.
|
||||
// The logic makes sure we flush literal runs often enough to not overrun
|
||||
// the 1 byte.
|
||||
DCHECK_EQ(literal_count_ % 8, 0);
|
||||
int num_groups = literal_count_ / 8;
|
||||
int32_t indicator_value = (num_groups << 1) | 1;
|
||||
DCHECK_EQ(indicator_value & 0xFFFFFF00, 0);
|
||||
*literal_indicator_byte_ = static_cast<uint8_t>(indicator_value);
|
||||
literal_indicator_byte_ = NULL;
|
||||
literal_count_ = 0;
|
||||
CheckBufferFull();
|
||||
}
|
||||
}
|
||||
|
||||
inline void RleEncoder::FlushRepeatedRun() {
|
||||
DCHECK_GT(repeat_count_, 0);
|
||||
bool result = true;
|
||||
// The lsb of 0 indicates this is a repeated run
|
||||
int32_t indicator_value = repeat_count_ << 1 | 0;
|
||||
result &= bit_writer_.PutVlqInt(static_cast<uint32_t>(indicator_value));
|
||||
result &= bit_writer_.PutAligned(
|
||||
current_value_, static_cast<int>(::arrow::bit_util::CeilDiv(bit_width_, 8)));
|
||||
DCHECK(result);
|
||||
num_buffered_values_ = 0;
|
||||
repeat_count_ = 0;
|
||||
CheckBufferFull();
|
||||
}
|
||||
|
||||
/// Flush the values that have been buffered. At this point we decide whether
|
||||
/// we need to switch between the run types or continue the current one.
|
||||
inline void RleEncoder::FlushBufferedValues(bool done) {
|
||||
if (repeat_count_ >= 8) {
|
||||
// Clear the buffered values. They are part of the repeated run now and we
|
||||
// don't want to flush them out as literals.
|
||||
num_buffered_values_ = 0;
|
||||
if (literal_count_ != 0) {
|
||||
// There was a current literal run. All the values in it have been flushed
|
||||
// but we still need to update the indicator byte.
|
||||
DCHECK_EQ(literal_count_ % 8, 0);
|
||||
DCHECK_EQ(repeat_count_, 8);
|
||||
FlushLiteralRun(true);
|
||||
}
|
||||
DCHECK_EQ(literal_count_, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
literal_count_ += num_buffered_values_;
|
||||
DCHECK_EQ(literal_count_ % 8, 0);
|
||||
int num_groups = literal_count_ / 8;
|
||||
if (num_groups + 1 >= (1 << 6)) {
|
||||
// We need to start a new literal run because the indicator byte we've reserved
|
||||
// cannot store more values.
|
||||
DCHECK(literal_indicator_byte_ != NULL);
|
||||
FlushLiteralRun(true);
|
||||
} else {
|
||||
FlushLiteralRun(done);
|
||||
}
|
||||
repeat_count_ = 0;
|
||||
}
|
||||
|
||||
inline int RleEncoder::Flush() {
|
||||
if (literal_count_ > 0 || repeat_count_ > 0 || num_buffered_values_ > 0) {
|
||||
bool all_repeat = literal_count_ == 0 && (repeat_count_ == num_buffered_values_ ||
|
||||
num_buffered_values_ == 0);
|
||||
// There is something pending, figure out if it's a repeated or literal run
|
||||
if (repeat_count_ > 0 && all_repeat) {
|
||||
FlushRepeatedRun();
|
||||
} else {
|
||||
DCHECK_EQ(literal_count_ % 8, 0);
|
||||
// Buffer the last group of literals to 8 by padding with 0s.
|
||||
for (; num_buffered_values_ != 0 && num_buffered_values_ < 8;
|
||||
++num_buffered_values_) {
|
||||
buffered_values_[num_buffered_values_] = 0;
|
||||
}
|
||||
literal_count_ += num_buffered_values_;
|
||||
FlushLiteralRun(true);
|
||||
repeat_count_ = 0;
|
||||
}
|
||||
}
|
||||
bit_writer_.Flush();
|
||||
DCHECK_EQ(num_buffered_values_, 0);
|
||||
DCHECK_EQ(literal_count_, 0);
|
||||
DCHECK_EQ(repeat_count_, 0);
|
||||
|
||||
return bit_writer_.bytes_written();
|
||||
}
|
||||
|
||||
inline void RleEncoder::CheckBufferFull() {
|
||||
int bytes_written = bit_writer_.bytes_written();
|
||||
if (bytes_written + max_run_byte_size_ > bit_writer_.buffer_len()) {
|
||||
buffer_full_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
inline void RleEncoder::Clear() {
|
||||
buffer_full_ = false;
|
||||
current_value_ = 0;
|
||||
repeat_count_ = 0;
|
||||
num_buffered_values_ = 0;
|
||||
literal_count_ = 0;
|
||||
literal_indicator_byte_ = NULL;
|
||||
bit_writer_.Clear();
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// MSVC x86_64/arm64
|
||||
|
||||
#if defined(_M_AMD64) || defined(_M_X64)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#else
|
||||
// gcc/clang (possibly others)
|
||||
|
||||
#if defined(ARROW_HAVE_BMI2)
|
||||
#include <x86intrin.h>
|
||||
#endif
|
||||
|
||||
#if defined(ARROW_HAVE_AVX2) || defined(ARROW_HAVE_AVX512)
|
||||
#include <immintrin.h>
|
||||
#elif defined(ARROW_HAVE_SSE4_2)
|
||||
#include <nmmintrin.h>
|
||||
#endif
|
||||
|
||||
#ifdef ARROW_HAVE_NEON
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,511 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/util/aligned_storage.h"
|
||||
#include "arrow/util/macros.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
template <typename T, size_t N, bool NonTrivialDestructor>
|
||||
struct StaticVectorStorageBase {
|
||||
using storage_type = AlignedStorage<T>;
|
||||
|
||||
storage_type static_data_[N];
|
||||
size_t size_ = 0;
|
||||
|
||||
void destroy() noexcept {}
|
||||
};
|
||||
|
||||
template <typename T, size_t N>
|
||||
struct StaticVectorStorageBase<T, N, true> {
|
||||
using storage_type = AlignedStorage<T>;
|
||||
|
||||
storage_type static_data_[N];
|
||||
size_t size_ = 0;
|
||||
|
||||
~StaticVectorStorageBase() noexcept { destroy(); }
|
||||
|
||||
void destroy() noexcept { storage_type::destroy_several(static_data_, size_); }
|
||||
};
|
||||
|
||||
template <typename T, size_t N, bool D = !std::is_trivially_destructible<T>::value>
|
||||
struct StaticVectorStorage : public StaticVectorStorageBase<T, N, D> {
|
||||
using Base = StaticVectorStorageBase<T, N, D>;
|
||||
using typename Base::storage_type;
|
||||
|
||||
using Base::size_;
|
||||
using Base::static_data_;
|
||||
|
||||
StaticVectorStorage() noexcept = default;
|
||||
|
||||
constexpr storage_type* storage_ptr() { return static_data_; }
|
||||
|
||||
constexpr const storage_type* const_storage_ptr() const { return static_data_; }
|
||||
|
||||
// Adjust storage size, but don't initialize any objects
|
||||
void bump_size(size_t addend) {
|
||||
assert(size_ + addend <= N);
|
||||
size_ += addend;
|
||||
}
|
||||
|
||||
void ensure_capacity(size_t min_capacity) { assert(min_capacity <= N); }
|
||||
|
||||
// Adjust storage size, but don't destroy any objects
|
||||
void reduce_size(size_t reduce_by) {
|
||||
assert(reduce_by <= size_);
|
||||
size_ -= reduce_by;
|
||||
}
|
||||
|
||||
// Move objects from another storage, but don't destroy any objects currently
|
||||
// stored in *this.
|
||||
// You need to call destroy() first if necessary (e.g. in a
|
||||
// move assignment operator).
|
||||
void move_construct(StaticVectorStorage&& other) noexcept {
|
||||
size_ = other.size_;
|
||||
if (size_ != 0) {
|
||||
// Use a compile-time memcpy size (N) for trivial types
|
||||
storage_type::move_construct_several(other.static_data_, static_data_, size_, N);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t capacity() const { return N; }
|
||||
|
||||
constexpr size_t max_size() const { return N; }
|
||||
|
||||
void reserve(size_t n) {}
|
||||
|
||||
void clear() {
|
||||
storage_type::destroy_several(static_data_, size_);
|
||||
size_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, size_t N>
|
||||
struct SmallVectorStorage {
|
||||
using storage_type = AlignedStorage<T>;
|
||||
|
||||
storage_type static_data_[N];
|
||||
size_t size_ = 0;
|
||||
storage_type* data_ = static_data_;
|
||||
size_t dynamic_capacity_ = 0;
|
||||
|
||||
SmallVectorStorage() noexcept = default;
|
||||
|
||||
~SmallVectorStorage() { destroy(); }
|
||||
|
||||
constexpr storage_type* storage_ptr() { return data_; }
|
||||
|
||||
constexpr const storage_type* const_storage_ptr() const { return data_; }
|
||||
|
||||
void bump_size(size_t addend) {
|
||||
const size_t new_size = size_ + addend;
|
||||
ensure_capacity(new_size);
|
||||
size_ = new_size;
|
||||
}
|
||||
|
||||
void ensure_capacity(size_t min_capacity) {
|
||||
if (dynamic_capacity_) {
|
||||
// Grow dynamic storage if necessary
|
||||
if (min_capacity > dynamic_capacity_) {
|
||||
size_t new_capacity = std::max(dynamic_capacity_ * 2, min_capacity);
|
||||
reallocate_dynamic(new_capacity);
|
||||
}
|
||||
} else if (min_capacity > N) {
|
||||
switch_to_dynamic(min_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
void reduce_size(size_t reduce_by) {
|
||||
assert(reduce_by <= size_);
|
||||
size_ -= reduce_by;
|
||||
}
|
||||
|
||||
void destroy() noexcept {
|
||||
storage_type::destroy_several(data_, size_);
|
||||
if (dynamic_capacity_) {
|
||||
delete[] data_;
|
||||
}
|
||||
}
|
||||
|
||||
void move_construct(SmallVectorStorage&& other) noexcept {
|
||||
size_ = other.size_;
|
||||
dynamic_capacity_ = other.dynamic_capacity_;
|
||||
if (dynamic_capacity_) {
|
||||
data_ = other.data_;
|
||||
other.data_ = other.static_data_;
|
||||
other.dynamic_capacity_ = 0;
|
||||
other.size_ = 0;
|
||||
} else if (size_ != 0) {
|
||||
// Use a compile-time memcpy size (N) for trivial types
|
||||
storage_type::move_construct_several(other.static_data_, static_data_, size_, N);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t capacity() const { return dynamic_capacity_ ? dynamic_capacity_ : N; }
|
||||
|
||||
constexpr size_t max_size() const { return std::numeric_limits<size_t>::max(); }
|
||||
|
||||
void reserve(size_t n) {
|
||||
if (dynamic_capacity_) {
|
||||
if (n > dynamic_capacity_) {
|
||||
reallocate_dynamic(n);
|
||||
}
|
||||
} else if (n > N) {
|
||||
switch_to_dynamic(n);
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
storage_type::destroy_several(data_, size_);
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
void switch_to_dynamic(size_t new_capacity) {
|
||||
dynamic_capacity_ = new_capacity;
|
||||
data_ = new storage_type[new_capacity];
|
||||
storage_type::move_construct_several_and_destroy_source(static_data_, data_, size_);
|
||||
}
|
||||
|
||||
void reallocate_dynamic(size_t new_capacity) {
|
||||
assert(new_capacity >= size_);
|
||||
auto new_data = new storage_type[new_capacity];
|
||||
storage_type::move_construct_several_and_destroy_source(data_, new_data, size_);
|
||||
delete[] data_;
|
||||
dynamic_capacity_ = new_capacity;
|
||||
data_ = new_data;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, size_t N, typename Storage>
|
||||
class StaticVectorImpl {
|
||||
private:
|
||||
Storage storage_;
|
||||
|
||||
T* data_ptr() { return storage_.storage_ptr()->get(); }
|
||||
|
||||
constexpr const T* const_data_ptr() const {
|
||||
return storage_.const_storage_ptr()->get();
|
||||
}
|
||||
|
||||
public:
|
||||
using size_type = size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using iterator = T*;
|
||||
using const_iterator = const T*;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
constexpr StaticVectorImpl() noexcept = default;
|
||||
|
||||
// Move and copy constructors
|
||||
StaticVectorImpl(StaticVectorImpl&& other) noexcept {
|
||||
storage_.move_construct(std::move(other.storage_));
|
||||
}
|
||||
|
||||
StaticVectorImpl& operator=(StaticVectorImpl&& other) noexcept {
|
||||
if (ARROW_PREDICT_TRUE(&other != this)) {
|
||||
// TODO move_assign?
|
||||
storage_.destroy();
|
||||
storage_.move_construct(std::move(other.storage_));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
StaticVectorImpl(const StaticVectorImpl& other) {
|
||||
init_by_copying(other.storage_.size_, other.const_data_ptr());
|
||||
}
|
||||
|
||||
StaticVectorImpl& operator=(const StaticVectorImpl& other) noexcept {
|
||||
if (ARROW_PREDICT_TRUE(&other != this)) {
|
||||
assign_by_copying(other.storage_.size_, other.data());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Automatic conversion from std::vector<T>, for convenience
|
||||
StaticVectorImpl(const std::vector<T>& other) { // NOLINT: explicit
|
||||
init_by_copying(other.size(), other.data());
|
||||
}
|
||||
|
||||
StaticVectorImpl(std::vector<T>&& other) noexcept { // NOLINT: explicit
|
||||
init_by_moving(other.size(), other.data());
|
||||
}
|
||||
|
||||
StaticVectorImpl& operator=(const std::vector<T>& other) {
|
||||
assign_by_copying(other.size(), other.data());
|
||||
return *this;
|
||||
}
|
||||
|
||||
StaticVectorImpl& operator=(std::vector<T>&& other) noexcept {
|
||||
assign_by_moving(other.size(), other.data());
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Constructing from count and optional initialization value
|
||||
explicit StaticVectorImpl(size_t count) {
|
||||
storage_.bump_size(count);
|
||||
auto* p = storage_.storage_ptr();
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
p[i].construct();
|
||||
}
|
||||
}
|
||||
|
||||
StaticVectorImpl(size_t count, const T& value) {
|
||||
storage_.bump_size(count);
|
||||
auto* p = storage_.storage_ptr();
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
p[i].construct(value);
|
||||
}
|
||||
}
|
||||
|
||||
StaticVectorImpl(std::initializer_list<T> values) {
|
||||
storage_.bump_size(values.size());
|
||||
auto* p = storage_.storage_ptr();
|
||||
for (auto&& v : values) {
|
||||
// Unfortunately, cannot move initializer values
|
||||
p++->construct(v);
|
||||
}
|
||||
}
|
||||
|
||||
// Size inspection
|
||||
|
||||
constexpr bool empty() const { return storage_.size_ == 0; }
|
||||
|
||||
constexpr size_t size() const { return storage_.size_; }
|
||||
|
||||
constexpr size_t capacity() const { return storage_.capacity(); }
|
||||
|
||||
constexpr size_t max_size() const { return storage_.max_size(); }
|
||||
|
||||
// Data access
|
||||
|
||||
T& operator[](size_t i) { return data_ptr()[i]; }
|
||||
|
||||
constexpr const T& operator[](size_t i) const { return const_data_ptr()[i]; }
|
||||
|
||||
T& front() { return data_ptr()[0]; }
|
||||
|
||||
constexpr const T& front() const { return const_data_ptr()[0]; }
|
||||
|
||||
T& back() { return data_ptr()[storage_.size_ - 1]; }
|
||||
|
||||
constexpr const T& back() const { return const_data_ptr()[storage_.size_ - 1]; }
|
||||
|
||||
T* data() { return data_ptr(); }
|
||||
|
||||
constexpr const T* data() const { return const_data_ptr(); }
|
||||
|
||||
// Iterators
|
||||
|
||||
iterator begin() { return iterator(data_ptr()); }
|
||||
|
||||
constexpr const_iterator begin() const { return const_iterator(const_data_ptr()); }
|
||||
|
||||
constexpr const_iterator cbegin() const { return const_iterator(const_data_ptr()); }
|
||||
|
||||
iterator end() { return iterator(data_ptr() + storage_.size_); }
|
||||
|
||||
constexpr const_iterator end() const {
|
||||
return const_iterator(const_data_ptr() + storage_.size_);
|
||||
}
|
||||
|
||||
constexpr const_iterator cend() const {
|
||||
return const_iterator(const_data_ptr() + storage_.size_);
|
||||
}
|
||||
|
||||
reverse_iterator rbegin() { return reverse_iterator(end()); }
|
||||
|
||||
constexpr const_reverse_iterator rbegin() const {
|
||||
return const_reverse_iterator(end());
|
||||
}
|
||||
|
||||
constexpr const_reverse_iterator crbegin() const {
|
||||
return const_reverse_iterator(end());
|
||||
}
|
||||
|
||||
reverse_iterator rend() { return reverse_iterator(begin()); }
|
||||
|
||||
constexpr const_reverse_iterator rend() const {
|
||||
return const_reverse_iterator(begin());
|
||||
}
|
||||
|
||||
constexpr const_reverse_iterator crend() const {
|
||||
return const_reverse_iterator(begin());
|
||||
}
|
||||
|
||||
// Mutations
|
||||
|
||||
void reserve(size_t n) { storage_.reserve(n); }
|
||||
|
||||
void clear() { storage_.clear(); }
|
||||
|
||||
void push_back(const T& value) {
|
||||
storage_.bump_size(1);
|
||||
storage_.storage_ptr()[storage_.size_ - 1].construct(value);
|
||||
}
|
||||
|
||||
void push_back(T&& value) {
|
||||
storage_.bump_size(1);
|
||||
storage_.storage_ptr()[storage_.size_ - 1].construct(std::move(value));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void emplace_back(Args&&... args) {
|
||||
storage_.bump_size(1);
|
||||
storage_.storage_ptr()[storage_.size_ - 1].construct(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename InputIt>
|
||||
iterator insert(const_iterator insert_at, InputIt first, InputIt last) {
|
||||
const size_t n = storage_.size_;
|
||||
const size_t it_size = static_cast<size_t>(last - first); // XXX might be O(n)?
|
||||
const size_t pos = static_cast<size_t>(insert_at - const_data_ptr());
|
||||
storage_.bump_size(it_size);
|
||||
auto* p = storage_.storage_ptr();
|
||||
if (it_size == 0) {
|
||||
return p[pos].get();
|
||||
}
|
||||
const size_t end_pos = pos + it_size;
|
||||
|
||||
// Move [pos; n) to [end_pos; end_pos + n - pos)
|
||||
size_t i = n;
|
||||
size_t j = end_pos + n - pos;
|
||||
while (j > std::max(n, end_pos)) {
|
||||
p[--j].move_construct(&p[--i]);
|
||||
}
|
||||
while (j > end_pos) {
|
||||
p[--j].move_assign(&p[--i]);
|
||||
}
|
||||
assert(j == end_pos);
|
||||
// Copy [first; last) to [pos; end_pos)
|
||||
j = pos;
|
||||
while (j < std::min(n, end_pos)) {
|
||||
p[j++].assign(*first++);
|
||||
}
|
||||
while (j < end_pos) {
|
||||
p[j++].construct(*first++);
|
||||
}
|
||||
assert(first == last);
|
||||
return p[pos].get();
|
||||
}
|
||||
|
||||
void resize(size_t n) {
|
||||
const size_t old_size = storage_.size_;
|
||||
if (n > storage_.size_) {
|
||||
storage_.bump_size(n - old_size);
|
||||
auto* p = storage_.storage_ptr();
|
||||
for (size_t i = old_size; i < n; ++i) {
|
||||
p[i].construct(T{});
|
||||
}
|
||||
} else {
|
||||
auto* p = storage_.storage_ptr();
|
||||
for (size_t i = n; i < old_size; ++i) {
|
||||
p[i].destroy();
|
||||
}
|
||||
storage_.reduce_size(old_size - n);
|
||||
}
|
||||
}
|
||||
|
||||
void resize(size_t n, const T& value) {
|
||||
const size_t old_size = storage_.size_;
|
||||
if (n > storage_.size_) {
|
||||
storage_.bump_size(n - old_size);
|
||||
auto* p = storage_.storage_ptr();
|
||||
for (size_t i = old_size; i < n; ++i) {
|
||||
p[i].construct(value);
|
||||
}
|
||||
} else {
|
||||
auto* p = storage_.storage_ptr();
|
||||
for (size_t i = n; i < old_size; ++i) {
|
||||
p[i].destroy();
|
||||
}
|
||||
storage_.reduce_size(old_size - n);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename InputIt>
|
||||
void init_by_copying(size_t n, InputIt src) {
|
||||
storage_.bump_size(n);
|
||||
auto* dest = storage_.storage_ptr();
|
||||
for (size_t i = 0; i < n; ++i, ++src) {
|
||||
dest[i].construct(*src);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename InputIt>
|
||||
void init_by_moving(size_t n, InputIt src) {
|
||||
init_by_copying(n, std::make_move_iterator(src));
|
||||
}
|
||||
|
||||
template <typename InputIt>
|
||||
void assign_by_copying(size_t n, InputIt src) {
|
||||
const size_t old_size = storage_.size_;
|
||||
if (n > old_size) {
|
||||
storage_.bump_size(n - old_size);
|
||||
auto* dest = storage_.storage_ptr();
|
||||
for (size_t i = 0; i < old_size; ++i, ++src) {
|
||||
dest[i].assign(*src);
|
||||
}
|
||||
for (size_t i = old_size; i < n; ++i, ++src) {
|
||||
dest[i].construct(*src);
|
||||
}
|
||||
} else {
|
||||
auto* dest = storage_.storage_ptr();
|
||||
for (size_t i = 0; i < n; ++i, ++src) {
|
||||
dest[i].assign(*src);
|
||||
}
|
||||
for (size_t i = n; i < old_size; ++i) {
|
||||
dest[i].destroy();
|
||||
}
|
||||
storage_.reduce_size(old_size - n);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename InputIt>
|
||||
void assign_by_moving(size_t n, InputIt src) {
|
||||
assign_by_copying(n, std::make_move_iterator(src));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, size_t N>
|
||||
using StaticVector = StaticVectorImpl<T, N, StaticVectorStorage<T, N>>;
|
||||
|
||||
template <typename T, size_t N>
|
||||
using SmallVector = StaticVectorImpl<T, N, SmallVectorStorage<T, N>>;
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
template <typename T, typename Cmp = std::less<T>>
|
||||
std::vector<int64_t> ArgSort(const std::vector<T>& values, Cmp&& cmp = {}) {
|
||||
std::vector<int64_t> indices(values.size());
|
||||
std::iota(indices.begin(), indices.end(), 0);
|
||||
std::sort(indices.begin(), indices.end(),
|
||||
[&](int64_t i, int64_t j) -> bool { return cmp(values[i], values[j]); });
|
||||
return indices;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t Permute(const std::vector<int64_t>& indices, std::vector<T>* values) {
|
||||
if (indices.size() <= 1) {
|
||||
return indices.size();
|
||||
}
|
||||
|
||||
// mask indicating which of values are in the correct location
|
||||
std::vector<bool> sorted(indices.size(), false);
|
||||
|
||||
size_t cycle_count = 0;
|
||||
|
||||
for (auto cycle_start = sorted.begin(); cycle_start != sorted.end();
|
||||
cycle_start = std::find(cycle_start, sorted.end(), false)) {
|
||||
++cycle_count;
|
||||
|
||||
// position in which an element belongs WRT sort
|
||||
auto sort_into = static_cast<int64_t>(cycle_start - sorted.begin());
|
||||
|
||||
if (indices[sort_into] == sort_into) {
|
||||
// trivial cycle
|
||||
sorted[sort_into] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// resolve this cycle
|
||||
const auto end = sort_into;
|
||||
for (int64_t take_from = indices[sort_into]; take_from != end;
|
||||
take_from = indices[sort_into]) {
|
||||
std::swap(values->at(sort_into), values->at(take_from));
|
||||
sorted[sort_into] = true;
|
||||
sort_into = take_from;
|
||||
}
|
||||
sorted[sort_into] = true;
|
||||
}
|
||||
|
||||
return cycle_count;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "arrow/util/bit_run_reader.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
namespace internal {
|
||||
|
||||
/// \brief Compress the buffer to spaced, excluding the null entries.
|
||||
///
|
||||
/// \param[in] src the source buffer
|
||||
/// \param[in] num_values the size of source buffer
|
||||
/// \param[in] valid_bits bitmap data indicating position of valid slots
|
||||
/// \param[in] valid_bits_offset offset into valid_bits
|
||||
/// \param[out] output the output buffer spaced
|
||||
/// \return The size of spaced buffer.
|
||||
template <typename T>
|
||||
inline int SpacedCompress(const T* src, int num_values, const uint8_t* valid_bits,
|
||||
int64_t valid_bits_offset, T* output) {
|
||||
int num_valid_values = 0;
|
||||
|
||||
arrow::internal::SetBitRunReader reader(valid_bits, valid_bits_offset, num_values);
|
||||
while (true) {
|
||||
const auto run = reader.NextRun();
|
||||
if (run.length == 0) {
|
||||
break;
|
||||
}
|
||||
std::memcpy(output + num_valid_values, src + run.position, run.length * sizeof(T));
|
||||
num_valid_values += static_cast<int32_t>(run.length);
|
||||
}
|
||||
|
||||
return num_valid_values;
|
||||
}
|
||||
|
||||
/// \brief Relocate values in buffer into positions of non-null values as indicated by
|
||||
/// a validity bitmap.
|
||||
///
|
||||
/// \param[in, out] buffer the in-place buffer
|
||||
/// \param[in] num_values total size of buffer including null slots
|
||||
/// \param[in] null_count number of null slots
|
||||
/// \param[in] valid_bits bitmap data indicating position of valid slots
|
||||
/// \param[in] valid_bits_offset offset into valid_bits
|
||||
/// \return The number of values expanded, including nulls.
|
||||
template <typename T>
|
||||
inline int SpacedExpand(T* buffer, int num_values, int null_count,
|
||||
const uint8_t* valid_bits, int64_t valid_bits_offset) {
|
||||
// Point to end as we add the spacing from the back.
|
||||
int idx_decode = num_values - null_count;
|
||||
|
||||
// Depending on the number of nulls, some of the value slots in buffer may
|
||||
// be uninitialized, and this will cause valgrind warnings / potentially UB
|
||||
std::memset(static_cast<void*>(buffer + idx_decode), 0, null_count * sizeof(T));
|
||||
if (idx_decode == 0) {
|
||||
// All nulls, nothing more to do
|
||||
return num_values;
|
||||
}
|
||||
|
||||
arrow::internal::ReverseSetBitRunReader reader(valid_bits, valid_bits_offset,
|
||||
num_values);
|
||||
while (true) {
|
||||
const auto run = reader.NextRun();
|
||||
if (run.length == 0) {
|
||||
break;
|
||||
}
|
||||
idx_decode -= static_cast<int32_t>(run.length);
|
||||
assert(idx_decode >= 0);
|
||||
std::memmove(buffer + run.position, buffer + idx_decode, run.length * sizeof(T));
|
||||
}
|
||||
|
||||
// Otherwise caller gave an incorrect null_count
|
||||
assert(idx_decode == 0);
|
||||
return num_values;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
class StopWatch {
|
||||
// This clock should give us wall clock time
|
||||
using ClockType = std::chrono::steady_clock;
|
||||
|
||||
public:
|
||||
StopWatch() {}
|
||||
|
||||
void Start() { start_ = ClockType::now(); }
|
||||
|
||||
// Returns time in nanoseconds.
|
||||
uint64_t Stop() {
|
||||
auto stop = ClockType::now();
|
||||
std::chrono::nanoseconds d = stop - start_;
|
||||
assert(d.count() >= 0);
|
||||
return static_cast<uint64_t>(d.count());
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::time_point<ClockType> start_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,171 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if __has_include(<charconv>)
|
||||
#include <charconv>
|
||||
#endif
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
class Status;
|
||||
|
||||
ARROW_EXPORT std::string HexEncode(const uint8_t* data, size_t length);
|
||||
|
||||
ARROW_EXPORT std::string Escape(const char* data, size_t length);
|
||||
|
||||
ARROW_EXPORT std::string HexEncode(const char* data, size_t length);
|
||||
|
||||
ARROW_EXPORT std::string HexEncode(std::string_view str);
|
||||
|
||||
ARROW_EXPORT std::string Escape(std::string_view str);
|
||||
|
||||
ARROW_EXPORT Status ParseHexValue(const char* data, uint8_t* out);
|
||||
|
||||
namespace internal {
|
||||
|
||||
/// Like std::string_view::starts_with in C++20
|
||||
inline bool StartsWith(std::string_view s, std::string_view prefix) {
|
||||
return s.length() >= prefix.length() &&
|
||||
(s.empty() || s.substr(0, prefix.length()) == prefix);
|
||||
}
|
||||
|
||||
/// Like std::string_view::ends_with in C++20
|
||||
inline bool EndsWith(std::string_view s, std::string_view suffix) {
|
||||
return s.length() >= suffix.length() &&
|
||||
(s.empty() || s.substr(s.length() - suffix.length()) == suffix);
|
||||
}
|
||||
|
||||
/// \brief Split a string with a delimiter
|
||||
ARROW_EXPORT
|
||||
std::vector<std::string_view> SplitString(std::string_view v, char delim,
|
||||
int64_t limit = 0);
|
||||
|
||||
/// \brief Join strings with a delimiter
|
||||
ARROW_EXPORT
|
||||
std::string JoinStrings(const std::vector<std::string_view>& strings,
|
||||
std::string_view delimiter);
|
||||
|
||||
/// \brief Join strings with a delimiter
|
||||
ARROW_EXPORT
|
||||
std::string JoinStrings(const std::vector<std::string>& strings,
|
||||
std::string_view delimiter);
|
||||
|
||||
/// \brief Trim whitespace from left and right sides of string
|
||||
ARROW_EXPORT
|
||||
std::string TrimString(std::string value);
|
||||
|
||||
ARROW_EXPORT
|
||||
bool AsciiEqualsCaseInsensitive(std::string_view left, std::string_view right);
|
||||
|
||||
ARROW_EXPORT
|
||||
std::string AsciiToLower(std::string_view value);
|
||||
|
||||
ARROW_EXPORT
|
||||
std::string AsciiToUpper(std::string_view value);
|
||||
|
||||
/// \brief Search for the first instance of a token and replace it or return nullopt if
|
||||
/// the token is not found.
|
||||
ARROW_EXPORT
|
||||
std::optional<std::string> Replace(std::string_view s, std::string_view token,
|
||||
std::string_view replacement);
|
||||
|
||||
/// \brief Get boolean value from string
|
||||
///
|
||||
/// If "1", "true" (case-insensitive), returns true
|
||||
/// If "0", "false" (case-insensitive), returns false
|
||||
/// Otherwise, returns Status::Invalid
|
||||
ARROW_EXPORT
|
||||
arrow::Result<bool> ParseBoolean(std::string_view value);
|
||||
|
||||
#if __has_include(<charconv>)
|
||||
|
||||
namespace detail {
|
||||
template <typename T, typename = void>
|
||||
struct can_to_chars : public std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct can_to_chars<
|
||||
T, std::void_t<decltype(std::to_chars(std::declval<char*>(), std::declval<char*>(),
|
||||
std::declval<std::remove_reference_t<T>>()))>>
|
||||
: public std::true_type {};
|
||||
} // namespace detail
|
||||
|
||||
/// \brief Whether std::to_chars exists for the current value type.
|
||||
///
|
||||
/// This is useful as some C++ libraries do not implement all specified overloads
|
||||
/// for std::to_chars.
|
||||
template <typename T>
|
||||
inline constexpr bool have_to_chars = detail::can_to_chars<T>::value;
|
||||
|
||||
/// \brief An ergonomic wrapper around std::to_chars, returning a std::string
|
||||
///
|
||||
/// For most inputs, the std::string result will not incur any heap allocation
|
||||
/// thanks to small string optimization.
|
||||
///
|
||||
/// Compared to std::to_string, this function gives locale-agnostic results
|
||||
/// and might also be faster.
|
||||
template <typename T, typename... Args>
|
||||
std::string ToChars(T value, Args&&... args) {
|
||||
if constexpr (!have_to_chars<T>) {
|
||||
// Some C++ standard libraries do not yet implement std::to_chars for all types,
|
||||
// in which case we have to fallback to std::string.
|
||||
return std::to_string(value);
|
||||
} else {
|
||||
// According to various sources, the GNU libstdc++ and Microsoft's C++ STL
|
||||
// allow up to 15 bytes of small string optimization, while clang's libc++
|
||||
// goes up to 22 bytes. Choose the pessimistic value.
|
||||
std::string out(15, 0);
|
||||
auto res = std::to_chars(&out.front(), &out.back(), value, args...);
|
||||
while (res.ec != std::errc{}) {
|
||||
assert(res.ec == std::errc::value_too_large);
|
||||
out.resize(out.capacity() * 2);
|
||||
res = std::to_chars(&out.front(), &out.back(), value, args...);
|
||||
}
|
||||
const auto length = res.ptr - out.data();
|
||||
assert(length <= static_cast<int64_t>(out.length()));
|
||||
out.resize(length);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
#else // !__has_include(<charconv>)
|
||||
|
||||
template <typename T>
|
||||
inline constexpr bool have_to_chars = false;
|
||||
|
||||
template <typename T, typename... Args>
|
||||
std::string ToChars(T value, Args&&... args) {
|
||||
return std::to_string(value);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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. template <typename T>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
namespace detail {
|
||||
|
||||
class ARROW_EXPORT StringStreamWrapper {
|
||||
public:
|
||||
StringStreamWrapper();
|
||||
~StringStreamWrapper();
|
||||
|
||||
std::ostream& stream() { return ostream_; }
|
||||
std::string str();
|
||||
|
||||
protected:
|
||||
std::unique_ptr<std::ostringstream> sstream_;
|
||||
std::ostream& ostream_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename Head>
|
||||
void StringBuilderRecursive(std::ostream& stream, Head&& head) {
|
||||
stream << head;
|
||||
}
|
||||
|
||||
template <typename Head, typename... Tail>
|
||||
void StringBuilderRecursive(std::ostream& stream, Head&& head, Tail&&... tail) {
|
||||
StringBuilderRecursive(stream, std::forward<Head>(head));
|
||||
StringBuilderRecursive(stream, std::forward<Tail>(tail)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
std::string StringBuilder(Args&&... args) {
|
||||
detail::StringStreamWrapper ss;
|
||||
StringBuilderRecursive(ss.stream(), std::forward<Args>(args)...);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/// CRTP helper for declaring string representation. Defines operator<<
|
||||
template <typename T>
|
||||
class ToStringOstreamable {
|
||||
public:
|
||||
~ToStringOstreamable() {
|
||||
static_assert(
|
||||
std::is_same<decltype(std::declval<const T>().ToString()), std::string>::value,
|
||||
"ToStringOstreamable depends on the method T::ToString() const");
|
||||
}
|
||||
|
||||
private:
|
||||
const T& cast() const { return static_cast<const T&>(*this); }
|
||||
|
||||
friend inline std::ostream& operator<<(std::ostream& os, const ToStringOstreamable& t) {
|
||||
return os << t.cast().ToString();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/type_fwd.h"
|
||||
#include "arrow/util/cancel.h"
|
||||
#include "arrow/util/functional.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/type_fwd.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
/// \brief A group of related tasks
|
||||
///
|
||||
/// A TaskGroup executes tasks with the signature `Status()`.
|
||||
/// Execution can be serial or parallel, depending on the TaskGroup
|
||||
/// implementation. When Finish() returns, it is guaranteed that all
|
||||
/// tasks have finished, or at least one has errored.
|
||||
///
|
||||
/// Once an error has occurred any tasks that are submitted to the task group
|
||||
/// will not run. The call to Append will simply return without scheduling the
|
||||
/// task.
|
||||
///
|
||||
/// If the task group is parallel it is possible that multiple tasks could be
|
||||
/// running at the same time and one of those tasks fails. This will put the
|
||||
/// task group in a failure state (so additional tasks cannot be run) however
|
||||
/// it will not interrupt running tasks. Finish will not complete
|
||||
/// until all running tasks have finished, even if one task fails.
|
||||
///
|
||||
/// Once a task group has finished new tasks may not be added to it. If you need to start
|
||||
/// a new batch of work then you should create a new task group.
|
||||
class ARROW_EXPORT TaskGroup : public std::enable_shared_from_this<TaskGroup> {
|
||||
public:
|
||||
/// Add a Status-returning function to execute. Execution order is
|
||||
/// undefined. The function may be executed immediately or later.
|
||||
template <typename Function>
|
||||
void Append(Function&& func) {
|
||||
return AppendReal(std::forward<Function>(func));
|
||||
}
|
||||
|
||||
/// Wait for execution of all tasks (and subgroups) to be finished,
|
||||
/// or for at least one task (or subgroup) to error out.
|
||||
/// The returned Status propagates the error status of the first failing
|
||||
/// task (or subgroup).
|
||||
virtual Status Finish() = 0;
|
||||
|
||||
/// Returns a future that will complete the first time all tasks are finished.
|
||||
/// This should be called only after all top level tasks
|
||||
/// have been added to the task group.
|
||||
///
|
||||
/// If you are using a TaskGroup asynchronously there are a few considerations to keep
|
||||
/// in mind. The tasks should not block on I/O, etc (defeats the purpose of using
|
||||
/// futures) and should not be doing any nested locking or you run the risk of the tasks
|
||||
/// getting stuck in the thread pool waiting for tasks which cannot get scheduled.
|
||||
///
|
||||
/// Primarily this call is intended to help migrate existing work written with TaskGroup
|
||||
/// in mind to using futures without having to do a complete conversion on the first
|
||||
/// pass.
|
||||
virtual Future<> FinishAsync() = 0;
|
||||
|
||||
/// The current aggregate error Status. Non-blocking, useful for stopping early.
|
||||
virtual Status current_status() = 0;
|
||||
|
||||
/// Whether some tasks have already failed. Non-blocking, useful for stopping early.
|
||||
virtual bool ok() const = 0;
|
||||
|
||||
/// How many tasks can typically be executed in parallel.
|
||||
/// This is only a hint, useful for testing or debugging.
|
||||
virtual int parallelism() = 0;
|
||||
|
||||
static std::shared_ptr<TaskGroup> MakeSerial(StopToken = StopToken::Unstoppable());
|
||||
static std::shared_ptr<TaskGroup> MakeThreaded(internal::Executor*,
|
||||
StopToken = StopToken::Unstoppable());
|
||||
|
||||
virtual ~TaskGroup() = default;
|
||||
|
||||
protected:
|
||||
TaskGroup() = default;
|
||||
ARROW_DISALLOW_COPY_AND_ASSIGN(TaskGroup);
|
||||
|
||||
virtual void AppendReal(FnOnce<Status()> task) = 0;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,104 @@
|
||||
// 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.
|
||||
|
||||
// approximate quantiles from arbitrary length dataset with O(1) space
|
||||
// based on 'Computing Extremely Accurate Quantiles Using t-Digests' from Dunning & Ertl
|
||||
// - https://arxiv.org/abs/1902.04023
|
||||
// - https://github.com/tdunning/t-digest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/util/logging.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
class Status;
|
||||
|
||||
namespace internal {
|
||||
|
||||
class ARROW_EXPORT TDigest {
|
||||
public:
|
||||
explicit TDigest(uint32_t delta = 100, uint32_t buffer_size = 500);
|
||||
~TDigest();
|
||||
TDigest(TDigest&&);
|
||||
TDigest& operator=(TDigest&&);
|
||||
|
||||
// reset and re-use this tdigest
|
||||
void Reset();
|
||||
|
||||
// validate data integrity
|
||||
Status Validate() const;
|
||||
|
||||
// dump internal data, only for debug
|
||||
void Dump() const;
|
||||
|
||||
// buffer a single data point, consume internal buffer if full
|
||||
// this function is intensively called and performance critical
|
||||
// call it only if you are sure no NAN exists in input data
|
||||
void Add(double value) {
|
||||
DCHECK(!std::isnan(value)) << "cannot add NAN";
|
||||
if (ARROW_PREDICT_FALSE(input_.size() == input_.capacity())) {
|
||||
MergeInput();
|
||||
}
|
||||
input_.push_back(value);
|
||||
}
|
||||
|
||||
// skip NAN on adding
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_floating_point<T>::value>::type NanAdd(T value) {
|
||||
if (!std::isnan(value)) Add(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value>::type NanAdd(T value) {
|
||||
Add(static_cast<double>(value));
|
||||
}
|
||||
|
||||
// merge with other t-digests, called infrequently
|
||||
void Merge(const std::vector<TDigest>& others);
|
||||
void Merge(const TDigest& other);
|
||||
|
||||
// calculate quantile
|
||||
double Quantile(double q) const;
|
||||
|
||||
double Min() const { return Quantile(0); }
|
||||
double Max() const { return Quantile(1); }
|
||||
double Mean() const;
|
||||
|
||||
// check if this tdigest contains no valid data points
|
||||
bool is_empty() const;
|
||||
|
||||
private:
|
||||
// merge input data with current tdigest
|
||||
void MergeInput() const;
|
||||
|
||||
// input buffer, size = buffer_size * sizeof(double)
|
||||
mutable std::vector<double> input_;
|
||||
|
||||
// hide other members with pimpl
|
||||
class TDigestImpl;
|
||||
std::unique_ptr<TDigestImpl> impl_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,90 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
#include "arrow/testing/gtest_util.h"
|
||||
#include "arrow/util/iterator.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
struct TestInt {
|
||||
TestInt();
|
||||
TestInt(int i); // NOLINT runtime/explicit
|
||||
int value;
|
||||
|
||||
bool operator==(const TestInt& other) const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const TestInt& v);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct IterationTraits<TestInt> {
|
||||
static TestInt End() { return TestInt(); }
|
||||
static bool IsEnd(const TestInt& val) { return val == IterationTraits<TestInt>::End(); }
|
||||
};
|
||||
|
||||
struct TestStr {
|
||||
TestStr();
|
||||
TestStr(const std::string& s); // NOLINT runtime/explicit
|
||||
TestStr(const char* s); // NOLINT runtime/explicit
|
||||
explicit TestStr(const TestInt& test_int);
|
||||
std::string value;
|
||||
|
||||
bool operator==(const TestStr& other) const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const TestStr& v);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct IterationTraits<TestStr> {
|
||||
static TestStr End() { return TestStr(); }
|
||||
static bool IsEnd(const TestStr& val) { return val == IterationTraits<TestStr>::End(); }
|
||||
};
|
||||
|
||||
std::vector<TestInt> RangeVector(unsigned int max, unsigned int step = 1);
|
||||
|
||||
template <typename T>
|
||||
inline Iterator<T> VectorIt(std::vector<T> v) {
|
||||
return MakeVectorIterator<T>(std::move(v));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Iterator<T> PossiblySlowVectorIt(std::vector<T> v, bool slow = false) {
|
||||
auto iterator = MakeVectorIterator<T>(std::move(v));
|
||||
if (slow) {
|
||||
return MakeTransformedIterator<T, T>(std::move(iterator),
|
||||
[](T item) -> Result<TransformFlow<T>> {
|
||||
SleepABit();
|
||||
return TransformYield(item);
|
||||
});
|
||||
} else {
|
||||
return iterator;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void AssertIteratorExhausted(Iterator<T>& it) {
|
||||
ASSERT_OK_AND_ASSIGN(T next, it.Next());
|
||||
ASSERT_TRUE(IsIterationEnd(next));
|
||||
}
|
||||
|
||||
Transformer<TestInt, TestStr> MakeFilter(std::function<bool(TestInt&)> filter);
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,527 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/util/cancel.h"
|
||||
#include "arrow/util/functional.h"
|
||||
#include "arrow/util/future.h"
|
||||
#include "arrow/util/iterator.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
// Disable harmless warning for decorated name length limit
|
||||
#pragma warning(disable : 4503)
|
||||
#endif
|
||||
|
||||
namespace arrow {
|
||||
|
||||
/// \brief Get the capacity of the global thread pool
|
||||
///
|
||||
/// Return the number of worker threads in the thread pool to which
|
||||
/// Arrow dispatches various CPU-bound tasks. This is an ideal number,
|
||||
/// not necessarily the exact number of threads at a given point in time.
|
||||
///
|
||||
/// You can change this number using SetCpuThreadPoolCapacity().
|
||||
ARROW_EXPORT int GetCpuThreadPoolCapacity();
|
||||
|
||||
/// \brief Set the capacity of the global thread pool
|
||||
///
|
||||
/// Set the number of worker threads int the thread pool to which
|
||||
/// Arrow dispatches various CPU-bound tasks.
|
||||
///
|
||||
/// The current number is returned by GetCpuThreadPoolCapacity().
|
||||
ARROW_EXPORT Status SetCpuThreadPoolCapacity(int threads);
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Hints about a task that may be used by an Executor.
|
||||
// They are ignored by the provided ThreadPool implementation.
|
||||
struct TaskHints {
|
||||
// The lower, the more urgent
|
||||
int32_t priority = 0;
|
||||
// The IO transfer size in bytes
|
||||
int64_t io_size = -1;
|
||||
// The approximate CPU cost in number of instructions
|
||||
int64_t cpu_cost = -1;
|
||||
// An application-specific ID
|
||||
int64_t external_id = -1;
|
||||
};
|
||||
|
||||
class ARROW_EXPORT Executor {
|
||||
public:
|
||||
using StopCallback = internal::FnOnce<void(const Status&)>;
|
||||
|
||||
virtual ~Executor();
|
||||
|
||||
// Spawn a fire-and-forget task.
|
||||
template <typename Function>
|
||||
Status Spawn(Function&& func) {
|
||||
return SpawnReal(TaskHints{}, std::forward<Function>(func), StopToken::Unstoppable(),
|
||||
StopCallback{});
|
||||
}
|
||||
template <typename Function>
|
||||
Status Spawn(Function&& func, StopToken stop_token) {
|
||||
return SpawnReal(TaskHints{}, std::forward<Function>(func), std::move(stop_token),
|
||||
StopCallback{});
|
||||
}
|
||||
template <typename Function>
|
||||
Status Spawn(TaskHints hints, Function&& func) {
|
||||
return SpawnReal(hints, std::forward<Function>(func), StopToken::Unstoppable(),
|
||||
StopCallback{});
|
||||
}
|
||||
template <typename Function>
|
||||
Status Spawn(TaskHints hints, Function&& func, StopToken stop_token) {
|
||||
return SpawnReal(hints, std::forward<Function>(func), std::move(stop_token),
|
||||
StopCallback{});
|
||||
}
|
||||
template <typename Function>
|
||||
Status Spawn(TaskHints hints, Function&& func, StopToken stop_token,
|
||||
StopCallback stop_callback) {
|
||||
return SpawnReal(hints, std::forward<Function>(func), std::move(stop_token),
|
||||
std::move(stop_callback));
|
||||
}
|
||||
|
||||
// Transfers a future to this executor. Any continuations added to the
|
||||
// returned future will run in this executor. Otherwise they would run
|
||||
// on the same thread that called MarkFinished.
|
||||
//
|
||||
// This is necessary when (for example) an I/O task is completing a future.
|
||||
// The continuations of that future should run on the CPU thread pool keeping
|
||||
// CPU heavy work off the I/O thread pool. So the I/O task should transfer
|
||||
// the future to the CPU executor before returning.
|
||||
//
|
||||
// By default this method will only transfer if the future is not already completed. If
|
||||
// the future is already completed then any callback would be run synchronously and so
|
||||
// no transfer is typically necessary. However, in cases where you want to force a
|
||||
// transfer (e.g. to help the scheduler break up units of work across multiple cores)
|
||||
// then you can override this behavior with `always_transfer`.
|
||||
template <typename T>
|
||||
Future<T> Transfer(Future<T> future) {
|
||||
return DoTransfer(std::move(future), false);
|
||||
}
|
||||
|
||||
// Overload of Transfer which will always schedule callbacks on new threads even if the
|
||||
// future is finished when the callback is added.
|
||||
//
|
||||
// This can be useful in cases where you want to ensure parallelism
|
||||
template <typename T>
|
||||
Future<T> TransferAlways(Future<T> future) {
|
||||
return DoTransfer(std::move(future), true);
|
||||
}
|
||||
|
||||
// Submit a callable and arguments for execution. Return a future that
|
||||
// will return the callable's result value once.
|
||||
// The callable's arguments are copied before execution.
|
||||
template <typename Function, typename... Args,
|
||||
typename FutureType = typename ::arrow::detail::ContinueFuture::ForSignature<
|
||||
Function && (Args && ...)>>
|
||||
Result<FutureType> Submit(TaskHints hints, StopToken stop_token, Function&& func,
|
||||
Args&&... args) {
|
||||
using ValueType = typename FutureType::ValueType;
|
||||
|
||||
auto future = FutureType::Make();
|
||||
auto task = std::bind(::arrow::detail::ContinueFuture{}, future,
|
||||
std::forward<Function>(func), std::forward<Args>(args)...);
|
||||
struct {
|
||||
WeakFuture<ValueType> weak_fut;
|
||||
|
||||
void operator()(const Status& st) {
|
||||
auto fut = weak_fut.get();
|
||||
if (fut.is_valid()) {
|
||||
fut.MarkFinished(st);
|
||||
}
|
||||
}
|
||||
} stop_callback{WeakFuture<ValueType>(future)};
|
||||
ARROW_RETURN_NOT_OK(SpawnReal(hints, std::move(task), std::move(stop_token),
|
||||
std::move(stop_callback)));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
template <typename Function, typename... Args,
|
||||
typename FutureType = typename ::arrow::detail::ContinueFuture::ForSignature<
|
||||
Function && (Args && ...)>>
|
||||
Result<FutureType> Submit(StopToken stop_token, Function&& func, Args&&... args) {
|
||||
return Submit(TaskHints{}, stop_token, std::forward<Function>(func),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Function, typename... Args,
|
||||
typename FutureType = typename ::arrow::detail::ContinueFuture::ForSignature<
|
||||
Function && (Args && ...)>>
|
||||
Result<FutureType> Submit(TaskHints hints, Function&& func, Args&&... args) {
|
||||
return Submit(std::move(hints), StopToken::Unstoppable(),
|
||||
std::forward<Function>(func), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Function, typename... Args,
|
||||
typename FutureType = typename ::arrow::detail::ContinueFuture::ForSignature<
|
||||
Function && (Args && ...)>>
|
||||
Result<FutureType> Submit(Function&& func, Args&&... args) {
|
||||
return Submit(TaskHints{}, StopToken::Unstoppable(), std::forward<Function>(func),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Return the level of parallelism (the number of tasks that may be executed
|
||||
// concurrently). This may be an approximate number.
|
||||
virtual int GetCapacity() = 0;
|
||||
|
||||
// Return true if the thread from which this function is called is owned by this
|
||||
// Executor. Returns false if this Executor does not support this property.
|
||||
virtual bool OwnsThisThread() { return false; }
|
||||
|
||||
/// \brief An interface to represent something with a custom destructor
|
||||
///
|
||||
/// \see KeepAlive
|
||||
class ARROW_EXPORT Resource {
|
||||
public:
|
||||
virtual ~Resource() = default;
|
||||
};
|
||||
|
||||
/// \brief Keep a resource alive until all executor threads have terminated
|
||||
///
|
||||
/// Executors may have static storage duration. In particular, the CPU and I/O
|
||||
/// executors are currently implemented this way. These threads may access other
|
||||
/// objects with static storage duration such as the OpenTelemetry runtime context
|
||||
/// the default memory pool, or other static executors.
|
||||
///
|
||||
/// The order in which these objects are destroyed is difficult to control. In order
|
||||
/// to ensure those objects remain alive until all threads have finished those objects
|
||||
/// should be wrapped in a Resource object and passed into this method. The given
|
||||
/// shared_ptr will be kept alive until all threads have finished their worker loops.
|
||||
virtual void KeepAlive(std::shared_ptr<Resource> resource);
|
||||
|
||||
protected:
|
||||
ARROW_DISALLOW_COPY_AND_ASSIGN(Executor);
|
||||
|
||||
Executor() = default;
|
||||
|
||||
template <typename T, typename FT = Future<T>, typename FTSync = typename FT::SyncType>
|
||||
Future<T> DoTransfer(Future<T> future, bool always_transfer = false) {
|
||||
auto transferred = Future<T>::Make();
|
||||
if (always_transfer) {
|
||||
CallbackOptions callback_options = CallbackOptions::Defaults();
|
||||
callback_options.should_schedule = ShouldSchedule::Always;
|
||||
callback_options.executor = this;
|
||||
auto sync_callback = [transferred](const FTSync& result) mutable {
|
||||
transferred.MarkFinished(result);
|
||||
};
|
||||
future.AddCallback(sync_callback, callback_options);
|
||||
return transferred;
|
||||
}
|
||||
|
||||
// We could use AddCallback's ShouldSchedule::IfUnfinished but we can save a bit of
|
||||
// work by doing the test here.
|
||||
auto callback = [this, transferred](const FTSync& result) mutable {
|
||||
auto spawn_status =
|
||||
Spawn([transferred, result]() mutable { transferred.MarkFinished(result); });
|
||||
if (!spawn_status.ok()) {
|
||||
transferred.MarkFinished(spawn_status);
|
||||
}
|
||||
};
|
||||
auto callback_factory = [&callback]() { return callback; };
|
||||
if (future.TryAddCallback(callback_factory)) {
|
||||
return transferred;
|
||||
}
|
||||
// If the future is already finished and we aren't going to force spawn a thread
|
||||
// then we don't need to add another layer of callback and can return the original
|
||||
// future
|
||||
return future;
|
||||
}
|
||||
|
||||
// Subclassing API
|
||||
virtual Status SpawnReal(TaskHints hints, FnOnce<void()> task, StopToken,
|
||||
StopCallback&&) = 0;
|
||||
};
|
||||
|
||||
/// \brief An executor implementation that runs all tasks on a single thread using an
|
||||
/// event loop.
|
||||
///
|
||||
/// Note: Any sort of nested parallelism will deadlock this executor. Blocking waits are
|
||||
/// fine but if one task needs to wait for another task it must be expressed as an
|
||||
/// asynchronous continuation.
|
||||
class ARROW_EXPORT SerialExecutor : public Executor {
|
||||
public:
|
||||
template <typename T = ::arrow::internal::Empty>
|
||||
using TopLevelTask = internal::FnOnce<Future<T>(Executor*)>;
|
||||
|
||||
~SerialExecutor() override;
|
||||
|
||||
int GetCapacity() override { return 1; };
|
||||
bool OwnsThisThread() override;
|
||||
Status SpawnReal(TaskHints hints, FnOnce<void()> task, StopToken,
|
||||
StopCallback&&) override;
|
||||
|
||||
/// \brief Runs the TopLevelTask and any scheduled tasks
|
||||
///
|
||||
/// The TopLevelTask (or one of the tasks it schedules) must either return an invalid
|
||||
/// status or call the finish signal. Failure to do this will result in a deadlock. For
|
||||
/// this reason it is preferable (if possible) to use the helper methods (below)
|
||||
/// RunSynchronously/RunSerially which delegates the responsiblity onto a Future
|
||||
/// producer's existing responsibility to always mark a future finished (which can
|
||||
/// someday be aided by ARROW-12207).
|
||||
template <typename T = internal::Empty, typename FT = Future<T>,
|
||||
typename FTSync = typename FT::SyncType>
|
||||
static FTSync RunInSerialExecutor(TopLevelTask<T> initial_task) {
|
||||
Future<T> fut = SerialExecutor().Run<T>(std::move(initial_task));
|
||||
return FutureToSync(fut);
|
||||
}
|
||||
|
||||
/// \brief Transform an AsyncGenerator into an Iterator
|
||||
///
|
||||
/// An event loop will be created and each call to Next will power the event loop with
|
||||
/// the calling thread until the next item is ready to be delivered.
|
||||
///
|
||||
/// Note: The iterator's destructor will run until the given generator is fully
|
||||
/// exhausted. If you wish to abandon iteration before completion then the correct
|
||||
/// approach is to use a stop token to cause the generator to exhaust early.
|
||||
template <typename T>
|
||||
static Iterator<T> IterateGenerator(
|
||||
internal::FnOnce<Result<std::function<Future<T>()>>(Executor*)> initial_task) {
|
||||
auto serial_executor = std::unique_ptr<SerialExecutor>(new SerialExecutor());
|
||||
auto maybe_generator = std::move(initial_task)(serial_executor.get());
|
||||
if (!maybe_generator.ok()) {
|
||||
return MakeErrorIterator<T>(maybe_generator.status());
|
||||
}
|
||||
auto generator = maybe_generator.MoveValueUnsafe();
|
||||
struct SerialIterator {
|
||||
SerialIterator(std::unique_ptr<SerialExecutor> executor,
|
||||
std::function<Future<T>()> generator)
|
||||
: executor(std::move(executor)), generator(std::move(generator)) {}
|
||||
ARROW_DISALLOW_COPY_AND_ASSIGN(SerialIterator);
|
||||
ARROW_DEFAULT_MOVE_AND_ASSIGN(SerialIterator);
|
||||
~SerialIterator() {
|
||||
// A serial iterator must be consumed before it can be destroyed. Allowing it to
|
||||
// do otherwise would lead to resource leakage. There will likely be deadlocks at
|
||||
// this spot in the future but these will be the result of other bugs and not the
|
||||
// fact that we are forcing consumption here.
|
||||
|
||||
// If a streaming API needs to support early abandonment then it should be done so
|
||||
// with a cancellation token and not simply discarding the iterator and expecting
|
||||
// the underlying work to clean up correctly.
|
||||
if (executor && !executor->IsFinished()) {
|
||||
while (true) {
|
||||
Result<T> maybe_next = Next();
|
||||
if (!maybe_next.ok() || IsIterationEnd(*maybe_next)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<T> Next() {
|
||||
executor->Unpause();
|
||||
// This call may lead to tasks being scheduled in the serial executor
|
||||
Future<T> next_fut = generator();
|
||||
next_fut.AddCallback([this](const Result<T>& res) {
|
||||
// If we're done iterating we should drain the rest of the tasks in the executor
|
||||
if (!res.ok() || IsIterationEnd(*res)) {
|
||||
executor->Finish();
|
||||
return;
|
||||
}
|
||||
// Otherwise we will break out immediately, leaving the remaining tasks for
|
||||
// the next call.
|
||||
executor->Pause();
|
||||
});
|
||||
// Borrow this thread and run tasks until the future is finished
|
||||
executor->RunLoop();
|
||||
if (!next_fut.is_finished()) {
|
||||
// Not clear this is possible since RunLoop wouldn't generally exit
|
||||
// unless we paused/finished which would imply next_fut has been
|
||||
// finished.
|
||||
return Status::Invalid(
|
||||
"Serial executor terminated before next result computed");
|
||||
}
|
||||
// At this point we may still have tasks in the executor, that is ok.
|
||||
// We will run those tasks the next time through.
|
||||
return next_fut.result();
|
||||
}
|
||||
|
||||
std::unique_ptr<SerialExecutor> executor;
|
||||
std::function<Future<T>()> generator;
|
||||
};
|
||||
return Iterator<T>(SerialIterator{std::move(serial_executor), std::move(generator)});
|
||||
}
|
||||
|
||||
private:
|
||||
SerialExecutor();
|
||||
|
||||
// State uses mutex
|
||||
struct State;
|
||||
std::shared_ptr<State> state_;
|
||||
|
||||
void RunLoop();
|
||||
// We mark the serial executor "finished" when there should be
|
||||
// no more tasks scheduled on it. It's not strictly needed but
|
||||
// can help catch bugs where we are trying to use the executor
|
||||
// after we are done with it.
|
||||
void Finish();
|
||||
bool IsFinished();
|
||||
// We pause the executor when we are running an async generator
|
||||
// and we have received an item that we can deliver.
|
||||
void Pause();
|
||||
void Unpause();
|
||||
|
||||
template <typename T, typename FTSync = typename Future<T>::SyncType>
|
||||
Future<T> Run(TopLevelTask<T> initial_task) {
|
||||
auto final_fut = std::move(initial_task)(this);
|
||||
final_fut.AddCallback([this](const FTSync&) { Finish(); });
|
||||
RunLoop();
|
||||
return final_fut;
|
||||
}
|
||||
};
|
||||
|
||||
/// An Executor implementation spawning tasks in FIFO manner on a fixed-size
|
||||
/// pool of worker threads.
|
||||
///
|
||||
/// Note: Any sort of nested parallelism will deadlock this executor. Blocking waits are
|
||||
/// fine but if one task needs to wait for another task it must be expressed as an
|
||||
/// asynchronous continuation.
|
||||
class ARROW_EXPORT ThreadPool : public Executor {
|
||||
public:
|
||||
// Construct a thread pool with the given number of worker threads
|
||||
static Result<std::shared_ptr<ThreadPool>> Make(int threads);
|
||||
|
||||
// Like Make(), but takes care that the returned ThreadPool is compatible
|
||||
// with destruction late at process exit.
|
||||
static Result<std::shared_ptr<ThreadPool>> MakeEternal(int threads);
|
||||
|
||||
// Destroy thread pool; the pool will first be shut down
|
||||
~ThreadPool() override;
|
||||
|
||||
// Return the desired number of worker threads.
|
||||
// The actual number of workers may lag a bit before being adjusted to
|
||||
// match this value.
|
||||
int GetCapacity() override;
|
||||
|
||||
bool OwnsThisThread() override;
|
||||
|
||||
// Return the number of tasks either running or in the queue.
|
||||
int GetNumTasks();
|
||||
|
||||
// Dynamically change the number of worker threads.
|
||||
//
|
||||
// This function always returns immediately.
|
||||
// If fewer threads are running than this number, new threads are spawned
|
||||
// on-demand when needed for task execution.
|
||||
// If more threads are running than this number, excess threads are reaped
|
||||
// as soon as possible.
|
||||
Status SetCapacity(int threads);
|
||||
|
||||
// Heuristic for the default capacity of a thread pool for CPU-bound tasks.
|
||||
// This is exposed as a static method to help with testing.
|
||||
static int DefaultCapacity();
|
||||
|
||||
// Shutdown the pool. Once the pool starts shutting down, new tasks
|
||||
// cannot be submitted anymore.
|
||||
// If "wait" is true, shutdown waits for all pending tasks to be finished.
|
||||
// If "wait" is false, workers are stopped as soon as currently executing
|
||||
// tasks are finished.
|
||||
Status Shutdown(bool wait = true);
|
||||
|
||||
// Wait for the thread pool to become idle
|
||||
//
|
||||
// This is useful for sequencing tests
|
||||
void WaitForIdle();
|
||||
|
||||
void KeepAlive(std::shared_ptr<Executor::Resource> resource) override;
|
||||
|
||||
struct State;
|
||||
|
||||
protected:
|
||||
FRIEND_TEST(TestThreadPool, SetCapacity);
|
||||
FRIEND_TEST(TestGlobalThreadPool, Capacity);
|
||||
ARROW_FRIEND_EXPORT friend ThreadPool* GetCpuThreadPool();
|
||||
|
||||
ThreadPool();
|
||||
|
||||
Status SpawnReal(TaskHints hints, FnOnce<void()> task, StopToken,
|
||||
StopCallback&&) override;
|
||||
|
||||
// Collect finished worker threads, making sure the OS threads have exited
|
||||
void CollectFinishedWorkersUnlocked();
|
||||
// Launch a given number of additional workers
|
||||
void LaunchWorkersUnlocked(int threads);
|
||||
// Get the current actual capacity
|
||||
int GetActualCapacity();
|
||||
|
||||
static std::shared_ptr<ThreadPool> MakeCpuThreadPool();
|
||||
|
||||
std::shared_ptr<State> sp_state_;
|
||||
State* state_;
|
||||
bool shutdown_on_destroy_;
|
||||
};
|
||||
|
||||
// Return the process-global thread pool for CPU-bound tasks.
|
||||
ARROW_EXPORT ThreadPool* GetCpuThreadPool();
|
||||
|
||||
/// \brief Potentially run an async operation serially (if use_threads is false)
|
||||
/// \see RunSerially
|
||||
///
|
||||
/// If `use_threads` is true, the global CPU executor is used.
|
||||
/// If `use_threads` is false, a temporary SerialExecutor is used.
|
||||
/// `get_future` is called (from this thread) with the chosen executor and must
|
||||
/// return a future that will eventually finish. This function returns once the
|
||||
/// future has finished.
|
||||
template <typename Fut, typename ValueType = typename Fut::ValueType>
|
||||
typename Fut::SyncType RunSynchronously(FnOnce<Fut(Executor*)> get_future,
|
||||
bool use_threads) {
|
||||
if (use_threads) {
|
||||
auto fut = std::move(get_future)(GetCpuThreadPool());
|
||||
return FutureToSync(fut);
|
||||
} else {
|
||||
return SerialExecutor::RunInSerialExecutor<ValueType>(std::move(get_future));
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Potentially iterate an async generator serially (if use_threads is false)
|
||||
/// \see IterateGenerator
|
||||
///
|
||||
/// If `use_threads` is true, the global CPU executor will be used. Each call to
|
||||
/// the iterator will simply wait until the next item is available. Tasks may run in
|
||||
/// the background between calls.
|
||||
///
|
||||
/// If `use_threads` is false, the calling thread only will be used. Each call to
|
||||
/// the iterator will use the calling thread to do enough work to generate one item.
|
||||
/// Tasks will be left in a queue until the next call and no work will be done between
|
||||
/// calls.
|
||||
template <typename T>
|
||||
Iterator<T> IterateSynchronously(
|
||||
FnOnce<Result<std::function<Future<T>()>>(Executor*)> get_gen, bool use_threads) {
|
||||
if (use_threads) {
|
||||
auto maybe_gen = std::move(get_gen)(GetCpuThreadPool());
|
||||
if (!maybe_gen.ok()) {
|
||||
return MakeErrorIterator<T>(maybe_gen.status());
|
||||
}
|
||||
return MakeGeneratorIterator(*maybe_gen);
|
||||
} else {
|
||||
return SerialExecutor::IterateGenerator(std::move(get_gen));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,83 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "arrow/type_fwd.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
enum DivideOrMultiply {
|
||||
MULTIPLY,
|
||||
DIVIDE,
|
||||
};
|
||||
|
||||
ARROW_EXPORT
|
||||
std::pair<DivideOrMultiply, int64_t> GetTimestampConversion(TimeUnit::type in_unit,
|
||||
TimeUnit::type out_unit);
|
||||
|
||||
// Converts a Timestamp value into another Timestamp value.
|
||||
//
|
||||
// This function takes care of properly transforming from one unit to another.
|
||||
//
|
||||
// \param[in] in the input type. Must be TimestampType.
|
||||
// \param[in] out the output type. Must be TimestampType.
|
||||
// \param[in] value the input value.
|
||||
//
|
||||
// \return The converted value, or an error.
|
||||
ARROW_EXPORT Result<int64_t> ConvertTimestampValue(const std::shared_ptr<DataType>& in,
|
||||
const std::shared_ptr<DataType>& out,
|
||||
int64_t value);
|
||||
|
||||
template <typename Visitor, typename... Args>
|
||||
decltype(std::declval<Visitor>()(std::chrono::seconds{}, std::declval<Args&&>()...))
|
||||
VisitDuration(TimeUnit::type unit, Visitor&& visitor, Args&&... args) {
|
||||
switch (unit) {
|
||||
default:
|
||||
case TimeUnit::SECOND:
|
||||
break;
|
||||
case TimeUnit::MILLI:
|
||||
return visitor(std::chrono::milliseconds{}, std::forward<Args>(args)...);
|
||||
case TimeUnit::MICRO:
|
||||
return visitor(std::chrono::microseconds{}, std::forward<Args>(args)...);
|
||||
case TimeUnit::NANO:
|
||||
return visitor(std::chrono::nanoseconds{}, std::forward<Args>(args)...);
|
||||
}
|
||||
return visitor(std::chrono::seconds{}, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// Convert a count of seconds to the corresponding count in a different TimeUnit
|
||||
struct CastSecondsToUnitImpl {
|
||||
template <typename Duration>
|
||||
int64_t operator()(Duration, int64_t seconds) {
|
||||
auto duration = std::chrono::duration_cast<Duration>(std::chrono::seconds{seconds});
|
||||
return static_cast<int64_t>(duration.count());
|
||||
}
|
||||
};
|
||||
|
||||
inline int64_t CastSecondsToUnit(TimeUnit::type unit, int64_t seconds) {
|
||||
return VisitDuration(unit, CastSecondsToUnitImpl{}, seconds);
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
namespace tracing {
|
||||
|
||||
class ARROW_EXPORT SpanDetails {
|
||||
public:
|
||||
virtual ~SpanDetails() {}
|
||||
};
|
||||
|
||||
class ARROW_EXPORT Span {
|
||||
public:
|
||||
Span() noexcept;
|
||||
std::unique_ptr<SpanDetails> details;
|
||||
};
|
||||
|
||||
} // namespace tracing
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,243 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iosfwd>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/status.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
// A non-zero-terminated small string class.
|
||||
// std::string usually has a small string optimization
|
||||
// (see review at https://shaharmike.com/cpp/std-string/)
|
||||
// but this one allows tight control and optimization of memory layout.
|
||||
template <uint8_t N>
|
||||
class SmallString {
|
||||
public:
|
||||
SmallString() : length_(0) {}
|
||||
|
||||
template <typename T>
|
||||
SmallString(const T& v) { // NOLINT implicit constructor
|
||||
*this = std::string_view(v);
|
||||
}
|
||||
|
||||
SmallString& operator=(const std::string_view s) {
|
||||
#ifndef NDEBUG
|
||||
CheckSize(s.size());
|
||||
#endif
|
||||
length_ = static_cast<uint8_t>(s.size());
|
||||
std::memcpy(data_, s.data(), length_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmallString& operator=(const std::string& s) {
|
||||
*this = std::string_view(s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmallString& operator=(const char* s) {
|
||||
*this = std::string_view(s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator std::string_view() const { return std::string_view(data_, length_); }
|
||||
|
||||
const char* data() const { return data_; }
|
||||
size_t length() const { return length_; }
|
||||
bool empty() const { return length_ == 0; }
|
||||
char operator[](size_t pos) const {
|
||||
#ifdef NDEBUG
|
||||
assert(pos <= length_);
|
||||
#endif
|
||||
return data_[pos];
|
||||
}
|
||||
|
||||
SmallString substr(size_t pos) const {
|
||||
return SmallString(std::string_view(*this).substr(pos));
|
||||
}
|
||||
|
||||
SmallString substr(size_t pos, size_t count) const {
|
||||
return SmallString(std::string_view(*this).substr(pos, count));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator==(T&& other) const {
|
||||
return std::string_view(*this) == std::string_view(std::forward<T>(other));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator!=(T&& other) const {
|
||||
return std::string_view(*this) != std::string_view(std::forward<T>(other));
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t length_;
|
||||
char data_[N];
|
||||
|
||||
void CheckSize(size_t n) { assert(n <= N); }
|
||||
};
|
||||
|
||||
template <uint8_t N>
|
||||
std::ostream& operator<<(std::ostream& os, const SmallString<N>& str) {
|
||||
return os << std::string_view(str);
|
||||
}
|
||||
|
||||
// A trie class for byte strings, optimized for small sets of short strings.
|
||||
// This class is immutable by design, use a TrieBuilder to construct it.
|
||||
class ARROW_EXPORT Trie {
|
||||
using index_type = int16_t;
|
||||
using fast_index_type = int_fast16_t;
|
||||
static constexpr auto kMaxIndex = std::numeric_limits<index_type>::max();
|
||||
|
||||
public:
|
||||
Trie() : size_(0) {}
|
||||
Trie(Trie&&) = default;
|
||||
Trie& operator=(Trie&&) = default;
|
||||
|
||||
int32_t Find(std::string_view s) const {
|
||||
const Node* node = &nodes_[0];
|
||||
fast_index_type pos = 0;
|
||||
if (s.length() > static_cast<size_t>(kMaxIndex)) {
|
||||
return -1;
|
||||
}
|
||||
fast_index_type remaining = static_cast<fast_index_type>(s.length());
|
||||
|
||||
while (remaining > 0) {
|
||||
auto substring_length = node->substring_length();
|
||||
if (substring_length > 0) {
|
||||
auto substring_data = node->substring_data();
|
||||
if (remaining < substring_length) {
|
||||
// Input too short
|
||||
return -1;
|
||||
}
|
||||
for (fast_index_type i = 0; i < substring_length; ++i) {
|
||||
if (s[pos++] != substring_data[i]) {
|
||||
// Mismatching substring
|
||||
return -1;
|
||||
}
|
||||
--remaining;
|
||||
}
|
||||
if (remaining == 0) {
|
||||
// Matched node exactly
|
||||
return node->found_index_;
|
||||
}
|
||||
}
|
||||
// Lookup child using next input character
|
||||
if (node->child_lookup_ == -1) {
|
||||
// Input too long
|
||||
return -1;
|
||||
}
|
||||
auto c = static_cast<uint8_t>(s[pos++]);
|
||||
--remaining;
|
||||
auto child_index = lookup_table_[node->child_lookup_ * 256 + c];
|
||||
if (child_index == -1) {
|
||||
// Child not found
|
||||
return -1;
|
||||
}
|
||||
node = &nodes_[child_index];
|
||||
}
|
||||
|
||||
// Input exhausted
|
||||
if (node->substring_.empty()) {
|
||||
// Matched node exactly
|
||||
return node->found_index_;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Status Validate() const;
|
||||
|
||||
void Dump() const;
|
||||
|
||||
protected:
|
||||
static constexpr size_t kNodeSize = 16;
|
||||
static constexpr auto kMaxSubstringLength =
|
||||
kNodeSize - 2 * sizeof(index_type) - sizeof(int8_t);
|
||||
|
||||
struct Node {
|
||||
// If this node is a valid end of string, index of found string, otherwise -1
|
||||
index_type found_index_;
|
||||
// Base index for child lookup in lookup_table_ (-1 if no child nodes)
|
||||
index_type child_lookup_;
|
||||
// The substring for this node.
|
||||
SmallString<kMaxSubstringLength> substring_;
|
||||
|
||||
fast_index_type substring_length() const {
|
||||
return static_cast<fast_index_type>(substring_.length());
|
||||
}
|
||||
const char* substring_data() const { return substring_.data(); }
|
||||
};
|
||||
|
||||
static_assert(sizeof(Node) == kNodeSize, "Unexpected node size");
|
||||
|
||||
ARROW_DISALLOW_COPY_AND_ASSIGN(Trie);
|
||||
|
||||
void Dump(const Node* node, const std::string& indent) const;
|
||||
|
||||
// Node table: entry 0 is the root node
|
||||
std::vector<Node> nodes_;
|
||||
|
||||
// Indexed lookup structure: gives index in node table, or -1 if not found
|
||||
std::vector<index_type> lookup_table_;
|
||||
|
||||
// Number of entries
|
||||
index_type size_;
|
||||
|
||||
friend class TrieBuilder;
|
||||
};
|
||||
|
||||
class ARROW_EXPORT TrieBuilder {
|
||||
using index_type = Trie::index_type;
|
||||
using fast_index_type = Trie::fast_index_type;
|
||||
|
||||
public:
|
||||
TrieBuilder();
|
||||
Status Append(std::string_view s, bool allow_duplicate = false);
|
||||
Trie Finish();
|
||||
|
||||
protected:
|
||||
// Extend the lookup table by 256 entries, return the index of the new span
|
||||
Status ExtendLookupTable(index_type* out_lookup_index);
|
||||
// Split the node given by the index at the substring index `split_at`
|
||||
Status SplitNode(fast_index_type node_index, fast_index_type split_at);
|
||||
// Append an already constructed child node to the parent
|
||||
Status AppendChildNode(Trie::Node* parent, uint8_t ch, Trie::Node&& node);
|
||||
// Create a matching child node from this parent
|
||||
Status CreateChildNode(Trie::Node* parent, uint8_t ch, std::string_view substring);
|
||||
Status CreateChildNode(Trie::Node* parent, char ch, std::string_view substring);
|
||||
|
||||
Trie trie_;
|
||||
|
||||
static constexpr auto kMaxIndex = std::numeric_limits<index_type>::max();
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,64 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace arrow {
|
||||
|
||||
namespace internal {
|
||||
struct Empty;
|
||||
} // namespace internal
|
||||
|
||||
template <typename T = internal::Empty>
|
||||
class WeakFuture;
|
||||
class FutureWaiter;
|
||||
|
||||
class TimestampParser;
|
||||
|
||||
namespace internal {
|
||||
|
||||
class Executor;
|
||||
class TaskGroup;
|
||||
class ThreadPool;
|
||||
class CpuInfo;
|
||||
|
||||
} // namespace internal
|
||||
|
||||
struct Compression {
|
||||
/// \brief Compression algorithm
|
||||
enum type {
|
||||
UNCOMPRESSED,
|
||||
SNAPPY,
|
||||
GZIP,
|
||||
BROTLI,
|
||||
ZSTD,
|
||||
LZ4,
|
||||
LZ4_FRAME,
|
||||
LZO,
|
||||
BZ2,
|
||||
LZ4_HADOOP
|
||||
};
|
||||
};
|
||||
|
||||
namespace util {
|
||||
class AsyncTaskScheduler;
|
||||
class Compressor;
|
||||
class Decompressor;
|
||||
class Codec;
|
||||
} // namespace util
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
/// \brief Metafunction to allow checking if a type matches any of another set of types
|
||||
template <typename...>
|
||||
struct IsOneOf : std::false_type {}; /// Base case: nothing has matched
|
||||
|
||||
template <typename T, typename U, typename... Args>
|
||||
struct IsOneOf<T, U, Args...> {
|
||||
/// Recursive case: T == U or T matches any other types provided (not including U).
|
||||
static constexpr bool value = std::is_same<T, U>::value || IsOneOf<T, Args...>::value;
|
||||
};
|
||||
|
||||
/// \brief Shorthand for using IsOneOf + std::enable_if
|
||||
template <typename T, typename... Args>
|
||||
using EnableIfIsOneOf = typename std::enable_if<IsOneOf<T, Args...>::value, T>::type;
|
||||
|
||||
/// \brief is_null_pointer from C++17
|
||||
template <typename T>
|
||||
struct is_null_pointer : std::is_same<std::nullptr_t, typename std::remove_cv<T>::type> {
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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.
|
||||
|
||||
// Contains utilities for making UBSan happy.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include "arrow/util/macros.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
namespace internal {
|
||||
|
||||
constexpr uint8_t kNonNullFiller = 0;
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/// \brief Returns maybe_null if not null or a non-null pointer to an arbitrary memory
|
||||
/// that shouldn't be dereferenced.
|
||||
///
|
||||
/// Memset/Memcpy are undefined when a nullptr is passed as an argument use this utility
|
||||
/// method to wrap locations where this could happen.
|
||||
///
|
||||
/// Note: Flatbuffers has UBSan warnings if a zero length vector is passed.
|
||||
/// https://github.com/google/flatbuffers/pull/5355 is trying to resolve
|
||||
/// them.
|
||||
template <typename T>
|
||||
inline T* MakeNonNull(T* maybe_null = NULLPTR) {
|
||||
if (ARROW_PREDICT_TRUE(maybe_null != NULLPTR)) {
|
||||
return maybe_null;
|
||||
}
|
||||
|
||||
return const_cast<T*>(reinterpret_cast<const T*>(&internal::kNonNullFiller));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline typename std::enable_if<std::is_trivial<T>::value, T>::type SafeLoadAs(
|
||||
const uint8_t* unaligned) {
|
||||
typename std::remove_const<T>::type ret;
|
||||
std::memcpy(&ret, unaligned, sizeof(T));
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline typename std::enable_if<std::is_trivial<T>::value, T>::type SafeLoad(
|
||||
const T* unaligned) {
|
||||
typename std::remove_const<T>::type ret;
|
||||
std::memcpy(&ret, unaligned, sizeof(T));
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename U, typename T>
|
||||
inline typename std::enable_if<std::is_trivial<T>::value && std::is_trivial<U>::value &&
|
||||
sizeof(T) == sizeof(U),
|
||||
U>::type
|
||||
SafeCopy(T value) {
|
||||
typename std::remove_const<U>::type ret;
|
||||
std::memcpy(&ret, &value, sizeof(T));
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline typename std::enable_if<std::is_trivial<T>::value, void>::type SafeStore(
|
||||
void* unaligned, T value) {
|
||||
std::memcpy(unaligned, &value, sizeof(T));
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace arrow {
|
||||
|
||||
[[noreturn]] ARROW_EXPORT void Unreachable(const char* message = "Unreachable");
|
||||
|
||||
[[noreturn]] ARROW_EXPORT void Unreachable(std::string_view message);
|
||||
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,118 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/type_fwd.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
/// \brief A parsed URI
|
||||
class ARROW_EXPORT Uri {
|
||||
public:
|
||||
Uri();
|
||||
~Uri();
|
||||
Uri(Uri&&);
|
||||
Uri& operator=(Uri&&);
|
||||
|
||||
// XXX Should we use std::string_view instead? These functions are
|
||||
// not performance-critical.
|
||||
|
||||
/// The URI scheme, such as "http", or the empty string if the URI has no
|
||||
/// explicit scheme.
|
||||
std::string scheme() const;
|
||||
|
||||
/// Convenience function that returns true if the scheme() is "file"
|
||||
bool is_file_scheme() const;
|
||||
|
||||
/// Whether the URI has an explicit host name. This may return true if
|
||||
/// the URI has an empty host (e.g. "file:///tmp/foo"), while it returns
|
||||
/// false is the URI has not host component at all (e.g. "file:/tmp/foo").
|
||||
bool has_host() const;
|
||||
/// The URI host name, such as "localhost", "127.0.0.1" or "::1", or the empty
|
||||
/// string is the URI does not have a host component.
|
||||
std::string host() const;
|
||||
|
||||
/// The URI port number, as a string such as "80", or the empty string is the URI
|
||||
/// does not have a port number component.
|
||||
std::string port_text() const;
|
||||
/// The URI port parsed as an integer, or -1 if the URI does not have a port
|
||||
/// number component.
|
||||
int32_t port() const;
|
||||
|
||||
/// The username specified in the URI.
|
||||
std::string username() const;
|
||||
/// The password specified in the URI.
|
||||
std::string password() const;
|
||||
|
||||
/// The URI path component.
|
||||
std::string path() const;
|
||||
|
||||
/// The URI query string
|
||||
std::string query_string() const;
|
||||
|
||||
/// The URI query items
|
||||
///
|
||||
/// Note this API doesn't allow differentiating between an empty value
|
||||
/// and a missing value, such in "a&b=1" vs. "a=&b=1".
|
||||
Result<std::vector<std::pair<std::string, std::string>>> query_items() const;
|
||||
|
||||
/// Get the string representation of this URI.
|
||||
const std::string& ToString() const;
|
||||
|
||||
/// Factory function to parse a URI from its string representation.
|
||||
Status Parse(const std::string& uri_string);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
/// Percent-encode the input string, for use e.g. as a URI query parameter.
|
||||
///
|
||||
/// This will escape directory separators, making this function unsuitable
|
||||
/// for encoding URI paths directly. See UriFromAbsolutePath() instead.
|
||||
ARROW_EXPORT
|
||||
std::string UriEscape(std::string_view s);
|
||||
|
||||
ARROW_EXPORT
|
||||
std::string UriUnescape(std::string_view s);
|
||||
|
||||
/// Encode a host for use within a URI, such as "localhost",
|
||||
/// "127.0.0.1", or "[::1]".
|
||||
ARROW_EXPORT
|
||||
std::string UriEncodeHost(std::string_view host);
|
||||
|
||||
/// Whether the string is a syntactically valid URI scheme according to RFC 3986.
|
||||
ARROW_EXPORT
|
||||
bool IsValidUriScheme(std::string_view s);
|
||||
|
||||
/// Create a file uri from a given absolute path
|
||||
ARROW_EXPORT
|
||||
Result<std::string> UriFromAbsolutePath(std::string_view path);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "arrow/type_fwd.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace util {
|
||||
|
||||
// Convert a UTF8 string to a wstring (either UTF16 or UTF32, depending
|
||||
// on the wchar_t width).
|
||||
ARROW_EXPORT Result<std::wstring> UTF8ToWideString(const std::string& source);
|
||||
|
||||
// Similarly, convert a wstring to a UTF8 string.
|
||||
ARROW_EXPORT Result<std::string> WideStringToUTF8(const std::wstring& source);
|
||||
|
||||
// This function needs to be called before doing UTF8 validation.
|
||||
ARROW_EXPORT void InitializeUTF8();
|
||||
|
||||
ARROW_EXPORT bool ValidateUTF8(const uint8_t* data, int64_t size);
|
||||
|
||||
ARROW_EXPORT bool ValidateUTF8(const std::string_view& str);
|
||||
|
||||
// Skip UTF8 byte order mark, if any.
|
||||
ARROW_EXPORT
|
||||
Result<const uint8_t*> SkipUTF8BOM(const uint8_t* data, int64_t size);
|
||||
|
||||
static constexpr uint32_t kMaxUnicodeCodepoint = 0x110000;
|
||||
|
||||
} // namespace util
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,928 @@
|
||||
// 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.
|
||||
|
||||
// This is a private header for string-to-number parsing utilities
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "arrow/type.h"
|
||||
#include "arrow/type_traits.h"
|
||||
#include "arrow/util/checked_cast.h"
|
||||
#include "arrow/util/config.h"
|
||||
#include "arrow/util/macros.h"
|
||||
#include "arrow/util/time.h"
|
||||
#include "arrow/util/visibility.h"
|
||||
#include "arrow/vendored/datetime.h"
|
||||
#include "arrow/vendored/strptime.h"
|
||||
|
||||
namespace arrow {
|
||||
|
||||
/// \brief A virtual string to timestamp parser
|
||||
class ARROW_EXPORT TimestampParser {
|
||||
public:
|
||||
virtual ~TimestampParser() = default;
|
||||
|
||||
virtual bool operator()(const char* s, size_t length, TimeUnit::type out_unit,
|
||||
int64_t* out,
|
||||
bool* out_zone_offset_present = NULLPTR) const = 0;
|
||||
|
||||
virtual const char* kind() const = 0;
|
||||
|
||||
virtual const char* format() const;
|
||||
|
||||
/// \brief Create a TimestampParser that recognizes strptime-like format strings
|
||||
static std::shared_ptr<TimestampParser> MakeStrptime(std::string format);
|
||||
|
||||
/// \brief Create a TimestampParser that recognizes (locale-agnostic) ISO8601
|
||||
/// timestamps
|
||||
static std::shared_ptr<TimestampParser> MakeISO8601();
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
/// \brief The entry point for conversion from strings.
|
||||
///
|
||||
/// Specializations of StringConverter for `ARROW_TYPE` must define:
|
||||
/// - A default constructible member type `value_type` which will be yielded on a
|
||||
/// successful parse.
|
||||
/// - The static member function `Convert`, callable with signature
|
||||
/// `(const ARROW_TYPE& t, const char* s, size_t length, value_type* out)`.
|
||||
/// `Convert` returns truthy for successful parses and assigns the parsed values to
|
||||
/// `*out`. Parameters required for parsing (for example a timestamp's TimeUnit)
|
||||
/// are acquired from the type parameter `t`.
|
||||
template <typename ARROW_TYPE, typename Enable = void>
|
||||
struct StringConverter;
|
||||
|
||||
template <typename T>
|
||||
struct is_parseable {
|
||||
template <typename U, typename = typename StringConverter<U>::value_type>
|
||||
static std::true_type Test(U*);
|
||||
|
||||
template <typename U>
|
||||
static std::false_type Test(...);
|
||||
|
||||
static constexpr bool value = decltype(Test<T>(NULLPTR))::value;
|
||||
};
|
||||
|
||||
template <typename T, typename R = void>
|
||||
using enable_if_parseable = enable_if_t<is_parseable<T>::value, R>;
|
||||
|
||||
template <>
|
||||
struct StringConverter<BooleanType> {
|
||||
using value_type = bool;
|
||||
|
||||
bool Convert(const BooleanType&, const char* s, size_t length, value_type* out) {
|
||||
if (length == 1) {
|
||||
// "0" or "1"?
|
||||
if (s[0] == '0') {
|
||||
*out = false;
|
||||
return true;
|
||||
}
|
||||
if (s[0] == '1') {
|
||||
*out = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (length == 4) {
|
||||
// "true"?
|
||||
*out = true;
|
||||
return ((s[0] == 't' || s[0] == 'T') && (s[1] == 'r' || s[1] == 'R') &&
|
||||
(s[2] == 'u' || s[2] == 'U') && (s[3] == 'e' || s[3] == 'E'));
|
||||
}
|
||||
if (length == 5) {
|
||||
// "false"?
|
||||
*out = false;
|
||||
return ((s[0] == 'f' || s[0] == 'F') && (s[1] == 'a' || s[1] == 'A') &&
|
||||
(s[2] == 'l' || s[2] == 'L') && (s[3] == 's' || s[3] == 'S') &&
|
||||
(s[4] == 'e' || s[4] == 'E'));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Ideas for faster float parsing:
|
||||
// - http://rapidjson.org/md_doc_internals.html#ParsingDouble
|
||||
// - https://github.com/google/double-conversion [used here]
|
||||
// - https://github.com/achan001/dtoa-fast
|
||||
|
||||
ARROW_EXPORT
|
||||
bool StringToFloat(const char* s, size_t length, char decimal_point, float* out);
|
||||
|
||||
ARROW_EXPORT
|
||||
bool StringToFloat(const char* s, size_t length, char decimal_point, double* out);
|
||||
|
||||
template <>
|
||||
struct StringConverter<FloatType> {
|
||||
using value_type = float;
|
||||
|
||||
explicit StringConverter(char decimal_point = '.') : decimal_point(decimal_point) {}
|
||||
|
||||
bool Convert(const FloatType&, const char* s, size_t length, value_type* out) {
|
||||
return ARROW_PREDICT_TRUE(StringToFloat(s, length, decimal_point, out));
|
||||
}
|
||||
|
||||
private:
|
||||
const char decimal_point;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct StringConverter<DoubleType> {
|
||||
using value_type = double;
|
||||
|
||||
explicit StringConverter(char decimal_point = '.') : decimal_point(decimal_point) {}
|
||||
|
||||
bool Convert(const DoubleType&, const char* s, size_t length, value_type* out) {
|
||||
return ARROW_PREDICT_TRUE(StringToFloat(s, length, decimal_point, out));
|
||||
}
|
||||
|
||||
private:
|
||||
const char decimal_point;
|
||||
};
|
||||
|
||||
// NOTE: HalfFloatType would require a half<->float conversion library
|
||||
|
||||
inline uint8_t ParseDecimalDigit(char c) { return static_cast<uint8_t>(c - '0'); }
|
||||
|
||||
#define PARSE_UNSIGNED_ITERATION(C_TYPE) \
|
||||
if (length > 0) { \
|
||||
uint8_t digit = ParseDecimalDigit(*s++); \
|
||||
result = static_cast<C_TYPE>(result * 10U); \
|
||||
length--; \
|
||||
if (ARROW_PREDICT_FALSE(digit > 9U)) { \
|
||||
/* Non-digit */ \
|
||||
return false; \
|
||||
} \
|
||||
result = static_cast<C_TYPE>(result + digit); \
|
||||
} else { \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define PARSE_UNSIGNED_ITERATION_LAST(C_TYPE) \
|
||||
if (length > 0) { \
|
||||
if (ARROW_PREDICT_FALSE(result > std::numeric_limits<C_TYPE>::max() / 10U)) { \
|
||||
/* Overflow */ \
|
||||
return false; \
|
||||
} \
|
||||
uint8_t digit = ParseDecimalDigit(*s++); \
|
||||
result = static_cast<C_TYPE>(result * 10U); \
|
||||
C_TYPE new_result = static_cast<C_TYPE>(result + digit); \
|
||||
if (ARROW_PREDICT_FALSE(--length > 0)) { \
|
||||
/* Too many digits */ \
|
||||
return false; \
|
||||
} \
|
||||
if (ARROW_PREDICT_FALSE(digit > 9U)) { \
|
||||
/* Non-digit */ \
|
||||
return false; \
|
||||
} \
|
||||
if (ARROW_PREDICT_FALSE(new_result < result)) { \
|
||||
/* Overflow */ \
|
||||
return false; \
|
||||
} \
|
||||
result = new_result; \
|
||||
}
|
||||
|
||||
inline bool ParseUnsigned(const char* s, size_t length, uint8_t* out) {
|
||||
uint8_t result = 0;
|
||||
|
||||
do {
|
||||
PARSE_UNSIGNED_ITERATION(uint8_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint8_t);
|
||||
PARSE_UNSIGNED_ITERATION_LAST(uint8_t);
|
||||
} while (false);
|
||||
*out = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ParseUnsigned(const char* s, size_t length, uint16_t* out) {
|
||||
uint16_t result = 0;
|
||||
do {
|
||||
PARSE_UNSIGNED_ITERATION(uint16_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint16_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint16_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint16_t);
|
||||
PARSE_UNSIGNED_ITERATION_LAST(uint16_t);
|
||||
} while (false);
|
||||
*out = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ParseUnsigned(const char* s, size_t length, uint32_t* out) {
|
||||
uint32_t result = 0;
|
||||
do {
|
||||
PARSE_UNSIGNED_ITERATION(uint32_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint32_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint32_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint32_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint32_t);
|
||||
|
||||
PARSE_UNSIGNED_ITERATION(uint32_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint32_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint32_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint32_t);
|
||||
|
||||
PARSE_UNSIGNED_ITERATION_LAST(uint32_t);
|
||||
} while (false);
|
||||
*out = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ParseUnsigned(const char* s, size_t length, uint64_t* out) {
|
||||
uint64_t result = 0;
|
||||
do {
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
PARSE_UNSIGNED_ITERATION(uint64_t);
|
||||
|
||||
PARSE_UNSIGNED_ITERATION_LAST(uint64_t);
|
||||
} while (false);
|
||||
*out = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef PARSE_UNSIGNED_ITERATION
|
||||
#undef PARSE_UNSIGNED_ITERATION_LAST
|
||||
|
||||
template <typename T>
|
||||
bool ParseHex(const char* s, size_t length, T* out) {
|
||||
// lets make sure that the length of the string is not too big
|
||||
if (!ARROW_PREDICT_TRUE(sizeof(T) * 2 >= length && length > 0)) {
|
||||
return false;
|
||||
}
|
||||
T result = 0;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
result = static_cast<T>(result << 4);
|
||||
if (s[i] >= '0' && s[i] <= '9') {
|
||||
result = static_cast<T>(result | (s[i] - '0'));
|
||||
} else if (s[i] >= 'A' && s[i] <= 'F') {
|
||||
result = static_cast<T>(result | (s[i] - 'A' + 10));
|
||||
} else if (s[i] >= 'a' && s[i] <= 'f') {
|
||||
result = static_cast<T>(result | (s[i] - 'a' + 10));
|
||||
} else {
|
||||
/* Non-digit */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*out = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class ARROW_TYPE>
|
||||
struct StringToUnsignedIntConverterMixin {
|
||||
using value_type = typename ARROW_TYPE::c_type;
|
||||
|
||||
bool Convert(const ARROW_TYPE&, const char* s, size_t length, value_type* out) {
|
||||
if (ARROW_PREDICT_FALSE(length == 0)) {
|
||||
return false;
|
||||
}
|
||||
// If it starts with 0x then its hex
|
||||
if (length > 2 && s[0] == '0' && ((s[1] == 'x') || (s[1] == 'X'))) {
|
||||
length -= 2;
|
||||
s += 2;
|
||||
|
||||
return ARROW_PREDICT_TRUE(ParseHex(s, length, out));
|
||||
}
|
||||
// Skip leading zeros
|
||||
while (length > 0 && *s == '0') {
|
||||
length--;
|
||||
s++;
|
||||
}
|
||||
return ParseUnsigned(s, length, out);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct StringConverter<UInt8Type> : public StringToUnsignedIntConverterMixin<UInt8Type> {
|
||||
using StringToUnsignedIntConverterMixin<UInt8Type>::StringToUnsignedIntConverterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct StringConverter<UInt16Type>
|
||||
: public StringToUnsignedIntConverterMixin<UInt16Type> {
|
||||
using StringToUnsignedIntConverterMixin<UInt16Type>::StringToUnsignedIntConverterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct StringConverter<UInt32Type>
|
||||
: public StringToUnsignedIntConverterMixin<UInt32Type> {
|
||||
using StringToUnsignedIntConverterMixin<UInt32Type>::StringToUnsignedIntConverterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct StringConverter<UInt64Type>
|
||||
: public StringToUnsignedIntConverterMixin<UInt64Type> {
|
||||
using StringToUnsignedIntConverterMixin<UInt64Type>::StringToUnsignedIntConverterMixin;
|
||||
};
|
||||
|
||||
template <class ARROW_TYPE>
|
||||
struct StringToSignedIntConverterMixin {
|
||||
using value_type = typename ARROW_TYPE::c_type;
|
||||
using unsigned_type = typename std::make_unsigned<value_type>::type;
|
||||
|
||||
bool Convert(const ARROW_TYPE&, const char* s, size_t length, value_type* out) {
|
||||
static constexpr auto max_positive =
|
||||
static_cast<unsigned_type>(std::numeric_limits<value_type>::max());
|
||||
// Assuming two's complement
|
||||
static constexpr unsigned_type max_negative = max_positive + 1;
|
||||
bool negative = false;
|
||||
unsigned_type unsigned_value = 0;
|
||||
|
||||
if (ARROW_PREDICT_FALSE(length == 0)) {
|
||||
return false;
|
||||
}
|
||||
// If it starts with 0x then its hex
|
||||
if (length > 2 && s[0] == '0' && ((s[1] == 'x') || (s[1] == 'X'))) {
|
||||
length -= 2;
|
||||
s += 2;
|
||||
|
||||
if (!ARROW_PREDICT_TRUE(ParseHex(s, length, &unsigned_value))) {
|
||||
return false;
|
||||
}
|
||||
*out = static_cast<value_type>(unsigned_value);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (*s == '-') {
|
||||
negative = true;
|
||||
s++;
|
||||
if (--length == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Skip leading zeros
|
||||
while (length > 0 && *s == '0') {
|
||||
length--;
|
||||
s++;
|
||||
}
|
||||
if (!ARROW_PREDICT_TRUE(ParseUnsigned(s, length, &unsigned_value))) {
|
||||
return false;
|
||||
}
|
||||
if (negative) {
|
||||
if (ARROW_PREDICT_FALSE(unsigned_value > max_negative)) {
|
||||
return false;
|
||||
}
|
||||
// To avoid both compiler warnings (with unsigned negation)
|
||||
// and undefined behaviour (with signed negation overflow),
|
||||
// use the expanded formula for 2's complement negation.
|
||||
*out = static_cast<value_type>(~unsigned_value + 1);
|
||||
} else {
|
||||
if (ARROW_PREDICT_FALSE(unsigned_value > max_positive)) {
|
||||
return false;
|
||||
}
|
||||
*out = static_cast<value_type>(unsigned_value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct StringConverter<Int8Type> : public StringToSignedIntConverterMixin<Int8Type> {
|
||||
using StringToSignedIntConverterMixin<Int8Type>::StringToSignedIntConverterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct StringConverter<Int16Type> : public StringToSignedIntConverterMixin<Int16Type> {
|
||||
using StringToSignedIntConverterMixin<Int16Type>::StringToSignedIntConverterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct StringConverter<Int32Type> : public StringToSignedIntConverterMixin<Int32Type> {
|
||||
using StringToSignedIntConverterMixin<Int32Type>::StringToSignedIntConverterMixin;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct StringConverter<Int64Type> : public StringToSignedIntConverterMixin<Int64Type> {
|
||||
using StringToSignedIntConverterMixin<Int64Type>::StringToSignedIntConverterMixin;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Inline-able ISO-8601 parser
|
||||
|
||||
using ts_type = TimestampType::c_type;
|
||||
|
||||
template <typename Duration>
|
||||
static inline bool ParseYYYY_MM_DD(const char* s, Duration* since_epoch) {
|
||||
uint16_t year = 0;
|
||||
uint8_t month = 0;
|
||||
uint8_t day = 0;
|
||||
if (ARROW_PREDICT_FALSE(s[4] != '-') || ARROW_PREDICT_FALSE(s[7] != '-')) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(!ParseUnsigned(s + 0, 4, &year))) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(!ParseUnsigned(s + 5, 2, &month))) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(!ParseUnsigned(s + 8, 2, &day))) {
|
||||
return false;
|
||||
}
|
||||
arrow_vendored::date::year_month_day ymd{arrow_vendored::date::year{year},
|
||||
arrow_vendored::date::month{month},
|
||||
arrow_vendored::date::day{day}};
|
||||
if (ARROW_PREDICT_FALSE(!ymd.ok())) return false;
|
||||
|
||||
*since_epoch = std::chrono::duration_cast<Duration>(
|
||||
arrow_vendored::date::sys_days{ymd}.time_since_epoch());
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
static inline bool ParseHH(const char* s, Duration* out) {
|
||||
uint8_t hours = 0;
|
||||
if (ARROW_PREDICT_FALSE(!ParseUnsigned(s + 0, 2, &hours))) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(hours >= 24)) {
|
||||
return false;
|
||||
}
|
||||
*out = std::chrono::duration_cast<Duration>(std::chrono::hours(hours));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
static inline bool ParseHH_MM(const char* s, Duration* out) {
|
||||
uint8_t hours = 0;
|
||||
uint8_t minutes = 0;
|
||||
if (ARROW_PREDICT_FALSE(s[2] != ':')) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(!ParseUnsigned(s + 0, 2, &hours))) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(!ParseUnsigned(s + 3, 2, &minutes))) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(hours >= 24)) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(minutes >= 60)) {
|
||||
return false;
|
||||
}
|
||||
*out = std::chrono::duration_cast<Duration>(std::chrono::hours(hours) +
|
||||
std::chrono::minutes(minutes));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
static inline bool ParseHHMM(const char* s, Duration* out) {
|
||||
uint8_t hours = 0;
|
||||
uint8_t minutes = 0;
|
||||
if (ARROW_PREDICT_FALSE(!ParseUnsigned(s + 0, 2, &hours))) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(!ParseUnsigned(s + 2, 2, &minutes))) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(hours >= 24)) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(minutes >= 60)) {
|
||||
return false;
|
||||
}
|
||||
*out = std::chrono::duration_cast<Duration>(std::chrono::hours(hours) +
|
||||
std::chrono::minutes(minutes));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
static inline bool ParseHH_MM_SS(const char* s, Duration* out) {
|
||||
uint8_t hours = 0;
|
||||
uint8_t minutes = 0;
|
||||
uint8_t seconds = 0;
|
||||
if (ARROW_PREDICT_FALSE(s[2] != ':') || ARROW_PREDICT_FALSE(s[5] != ':')) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(!ParseUnsigned(s + 0, 2, &hours))) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(!ParseUnsigned(s + 3, 2, &minutes))) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(!ParseUnsigned(s + 6, 2, &seconds))) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(hours >= 24)) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(minutes >= 60)) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(seconds >= 60)) {
|
||||
return false;
|
||||
}
|
||||
*out = std::chrono::duration_cast<Duration>(std::chrono::hours(hours) +
|
||||
std::chrono::minutes(minutes) +
|
||||
std::chrono::seconds(seconds));
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool ParseSubSeconds(const char* s, size_t length, TimeUnit::type unit,
|
||||
uint32_t* out) {
|
||||
// The decimal point has been peeled off at this point
|
||||
|
||||
// Fail if number of decimal places provided exceeds what the unit can hold.
|
||||
// Calculate how many trailing decimal places are omitted for the unit
|
||||
// e.g. if 4 decimal places are provided and unit is MICRO, 2 are missing
|
||||
size_t omitted = 0;
|
||||
switch (unit) {
|
||||
case TimeUnit::MILLI:
|
||||
if (ARROW_PREDICT_FALSE(length > 3)) {
|
||||
return false;
|
||||
}
|
||||
if (length < 3) {
|
||||
omitted = 3 - length;
|
||||
}
|
||||
break;
|
||||
case TimeUnit::MICRO:
|
||||
if (ARROW_PREDICT_FALSE(length > 6)) {
|
||||
return false;
|
||||
}
|
||||
if (length < 6) {
|
||||
omitted = 6 - length;
|
||||
}
|
||||
break;
|
||||
case TimeUnit::NANO:
|
||||
if (ARROW_PREDICT_FALSE(length > 9)) {
|
||||
return false;
|
||||
}
|
||||
if (length < 9) {
|
||||
omitted = 9 - length;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ARROW_PREDICT_TRUE(omitted == 0)) {
|
||||
return ParseUnsigned(s, length, out);
|
||||
} else {
|
||||
uint32_t subseconds = 0;
|
||||
bool success = ParseUnsigned(s, length, &subseconds);
|
||||
if (ARROW_PREDICT_TRUE(success)) {
|
||||
switch (omitted) {
|
||||
case 1:
|
||||
*out = subseconds * 10;
|
||||
break;
|
||||
case 2:
|
||||
*out = subseconds * 100;
|
||||
break;
|
||||
case 3:
|
||||
*out = subseconds * 1000;
|
||||
break;
|
||||
case 4:
|
||||
*out = subseconds * 10000;
|
||||
break;
|
||||
case 5:
|
||||
*out = subseconds * 100000;
|
||||
break;
|
||||
case 6:
|
||||
*out = subseconds * 1000000;
|
||||
break;
|
||||
case 7:
|
||||
*out = subseconds * 10000000;
|
||||
break;
|
||||
case 8:
|
||||
*out = subseconds * 100000000;
|
||||
break;
|
||||
default:
|
||||
// Impossible case
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
static inline bool ParseTimestampISO8601(const char* s, size_t length,
|
||||
TimeUnit::type unit, TimestampType::c_type* out,
|
||||
bool* out_zone_offset_present = NULLPTR) {
|
||||
using seconds_type = std::chrono::duration<TimestampType::c_type>;
|
||||
|
||||
// We allow the following zone offset formats:
|
||||
// - (none)
|
||||
// - Z
|
||||
// - [+-]HH(:?MM)?
|
||||
//
|
||||
// We allow the following formats for all units:
|
||||
// - "YYYY-MM-DD"
|
||||
// - "YYYY-MM-DD[ T]hhZ?"
|
||||
// - "YYYY-MM-DD[ T]hh:mmZ?"
|
||||
// - "YYYY-MM-DD[ T]hh:mm:ssZ?"
|
||||
//
|
||||
// We allow the following formats for unit == MILLI, MICRO, or NANO:
|
||||
// - "YYYY-MM-DD[ T]hh:mm:ss.s{1,3}Z?"
|
||||
//
|
||||
// We allow the following formats for unit == MICRO, or NANO:
|
||||
// - "YYYY-MM-DD[ T]hh:mm:ss.s{4,6}Z?"
|
||||
//
|
||||
// We allow the following formats for unit == NANO:
|
||||
// - "YYYY-MM-DD[ T]hh:mm:ss.s{7,9}Z?"
|
||||
//
|
||||
// UTC is always assumed, and the DataType's timezone is ignored.
|
||||
//
|
||||
|
||||
if (ARROW_PREDICT_FALSE(length < 10)) return false;
|
||||
|
||||
seconds_type seconds_since_epoch;
|
||||
if (ARROW_PREDICT_FALSE(!detail::ParseYYYY_MM_DD(s, &seconds_since_epoch))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length == 10) {
|
||||
*out = util::CastSecondsToUnit(unit, seconds_since_epoch.count());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ARROW_PREDICT_FALSE(s[10] != ' ') && ARROW_PREDICT_FALSE(s[10] != 'T')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out_zone_offset_present) {
|
||||
*out_zone_offset_present = false;
|
||||
}
|
||||
|
||||
seconds_type zone_offset(0);
|
||||
if (s[length - 1] == 'Z') {
|
||||
--length;
|
||||
if (out_zone_offset_present) *out_zone_offset_present = true;
|
||||
} else if (s[length - 3] == '+' || s[length - 3] == '-') {
|
||||
// [+-]HH
|
||||
length -= 3;
|
||||
if (ARROW_PREDICT_FALSE(!detail::ParseHH(s + length + 1, &zone_offset))) {
|
||||
return false;
|
||||
}
|
||||
if (s[length] == '+') zone_offset *= -1;
|
||||
if (out_zone_offset_present) *out_zone_offset_present = true;
|
||||
} else if (s[length - 5] == '+' || s[length - 5] == '-') {
|
||||
// [+-]HHMM
|
||||
length -= 5;
|
||||
if (ARROW_PREDICT_FALSE(!detail::ParseHHMM(s + length + 1, &zone_offset))) {
|
||||
return false;
|
||||
}
|
||||
if (s[length] == '+') zone_offset *= -1;
|
||||
if (out_zone_offset_present) *out_zone_offset_present = true;
|
||||
} else if ((s[length - 6] == '+' || s[length - 6] == '-') && (s[length - 3] == ':')) {
|
||||
// [+-]HH:MM
|
||||
length -= 6;
|
||||
if (ARROW_PREDICT_FALSE(!detail::ParseHH_MM(s + length + 1, &zone_offset))) {
|
||||
return false;
|
||||
}
|
||||
if (s[length] == '+') zone_offset *= -1;
|
||||
if (out_zone_offset_present) *out_zone_offset_present = true;
|
||||
}
|
||||
|
||||
seconds_type seconds_since_midnight;
|
||||
switch (length) {
|
||||
case 13: // YYYY-MM-DD[ T]hh
|
||||
if (ARROW_PREDICT_FALSE(!detail::ParseHH(s + 11, &seconds_since_midnight))) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 16: // YYYY-MM-DD[ T]hh:mm
|
||||
if (ARROW_PREDICT_FALSE(!detail::ParseHH_MM(s + 11, &seconds_since_midnight))) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 19: // YYYY-MM-DD[ T]hh:mm:ss
|
||||
case 21: // YYYY-MM-DD[ T]hh:mm:ss.s
|
||||
case 22: // YYYY-MM-DD[ T]hh:mm:ss.ss
|
||||
case 23: // YYYY-MM-DD[ T]hh:mm:ss.sss
|
||||
case 24: // YYYY-MM-DD[ T]hh:mm:ss.ssss
|
||||
case 25: // YYYY-MM-DD[ T]hh:mm:ss.sssss
|
||||
case 26: // YYYY-MM-DD[ T]hh:mm:ss.ssssss
|
||||
case 27: // YYYY-MM-DD[ T]hh:mm:ss.sssssss
|
||||
case 28: // YYYY-MM-DD[ T]hh:mm:ss.ssssssss
|
||||
case 29: // YYYY-MM-DD[ T]hh:mm:ss.sssssssss
|
||||
if (ARROW_PREDICT_FALSE(!detail::ParseHH_MM_SS(s + 11, &seconds_since_midnight))) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
seconds_since_epoch += seconds_since_midnight;
|
||||
seconds_since_epoch += zone_offset;
|
||||
|
||||
if (length <= 19) {
|
||||
*out = util::CastSecondsToUnit(unit, seconds_since_epoch.count());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ARROW_PREDICT_FALSE(s[19] != '.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t subseconds = 0;
|
||||
if (ARROW_PREDICT_FALSE(
|
||||
!detail::ParseSubSeconds(s + 20, length - 20, unit, &subseconds))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = util::CastSecondsToUnit(unit, seconds_since_epoch.count()) + subseconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(ARROW_WITH_MUSL)
|
||||
static constexpr bool kStrptimeSupportsZone = false;
|
||||
#else
|
||||
static constexpr bool kStrptimeSupportsZone = true;
|
||||
#endif
|
||||
|
||||
/// \brief Returns time since the UNIX epoch in the requested unit
|
||||
static inline bool ParseTimestampStrptime(const char* buf, size_t length,
|
||||
const char* format, bool ignore_time_in_day,
|
||||
bool allow_trailing_chars, TimeUnit::type unit,
|
||||
int64_t* out) {
|
||||
// NOTE: strptime() is more than 10x faster than arrow_vendored::date::parse().
|
||||
// The buffer may not be nul-terminated
|
||||
std::string clean_copy(buf, length);
|
||||
struct tm result;
|
||||
memset(&result, 0, sizeof(struct tm));
|
||||
#ifdef _WIN32
|
||||
char* ret = arrow_strptime(clean_copy.c_str(), format, &result);
|
||||
#else
|
||||
char* ret = strptime(clean_copy.c_str(), format, &result);
|
||||
#endif
|
||||
if (ret == NULLPTR) {
|
||||
return false;
|
||||
}
|
||||
if (!allow_trailing_chars && static_cast<size_t>(ret - clean_copy.c_str()) != length) {
|
||||
return false;
|
||||
}
|
||||
// ignore the time part
|
||||
arrow_vendored::date::sys_seconds secs =
|
||||
arrow_vendored::date::sys_days(arrow_vendored::date::year(result.tm_year + 1900) /
|
||||
(result.tm_mon + 1) / result.tm_mday);
|
||||
if (!ignore_time_in_day) {
|
||||
secs += (std::chrono::hours(result.tm_hour) + std::chrono::minutes(result.tm_min) +
|
||||
std::chrono::seconds(result.tm_sec));
|
||||
#ifndef _WIN32
|
||||
secs -= std::chrono::seconds(result.tm_gmtoff);
|
||||
#endif
|
||||
}
|
||||
*out = util::CastSecondsToUnit(unit, secs.time_since_epoch().count());
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
struct StringConverter<TimestampType> {
|
||||
using value_type = int64_t;
|
||||
|
||||
bool Convert(const TimestampType& type, const char* s, size_t length, value_type* out) {
|
||||
return ParseTimestampISO8601(s, length, type.unit(), out);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct StringConverter<DurationType>
|
||||
: public StringToSignedIntConverterMixin<DurationType> {
|
||||
using StringToSignedIntConverterMixin<DurationType>::StringToSignedIntConverterMixin;
|
||||
};
|
||||
|
||||
template <typename DATE_TYPE>
|
||||
struct StringConverter<DATE_TYPE, enable_if_date<DATE_TYPE>> {
|
||||
using value_type = typename DATE_TYPE::c_type;
|
||||
|
||||
using duration_type =
|
||||
typename std::conditional<std::is_same<DATE_TYPE, Date32Type>::value,
|
||||
arrow_vendored::date::days,
|
||||
std::chrono::milliseconds>::type;
|
||||
|
||||
bool Convert(const DATE_TYPE& type, const char* s, size_t length, value_type* out) {
|
||||
if (ARROW_PREDICT_FALSE(length != 10)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
duration_type since_epoch;
|
||||
if (ARROW_PREDICT_FALSE(!detail::ParseYYYY_MM_DD(s, &since_epoch))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = static_cast<value_type>(since_epoch.count());
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TIME_TYPE>
|
||||
struct StringConverter<TIME_TYPE, enable_if_time<TIME_TYPE>> {
|
||||
using value_type = typename TIME_TYPE::c_type;
|
||||
|
||||
// We allow the following formats for all units:
|
||||
// - "hh:mm"
|
||||
// - "hh:mm:ss"
|
||||
//
|
||||
// We allow the following formats for unit == MILLI, MICRO, or NANO:
|
||||
// - "hh:mm:ss.s{1,3}"
|
||||
//
|
||||
// We allow the following formats for unit == MICRO, or NANO:
|
||||
// - "hh:mm:ss.s{4,6}"
|
||||
//
|
||||
// We allow the following formats for unit == NANO:
|
||||
// - "hh:mm:ss.s{7,9}"
|
||||
|
||||
bool Convert(const TIME_TYPE& type, const char* s, size_t length, value_type* out) {
|
||||
const auto unit = type.unit();
|
||||
std::chrono::seconds since_midnight;
|
||||
|
||||
if (length == 5) {
|
||||
if (ARROW_PREDICT_FALSE(!detail::ParseHH_MM(s, &since_midnight))) {
|
||||
return false;
|
||||
}
|
||||
*out =
|
||||
static_cast<value_type>(util::CastSecondsToUnit(unit, since_midnight.count()));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ARROW_PREDICT_FALSE(length < 8)) {
|
||||
return false;
|
||||
}
|
||||
if (ARROW_PREDICT_FALSE(!detail::ParseHH_MM_SS(s, &since_midnight))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = static_cast<value_type>(util::CastSecondsToUnit(unit, since_midnight.count()));
|
||||
|
||||
if (length == 8) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ARROW_PREDICT_FALSE(s[8] != '.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t subseconds_count = 0;
|
||||
if (ARROW_PREDICT_FALSE(
|
||||
!detail::ParseSubSeconds(s + 9, length - 9, unit, &subseconds_count))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out += subseconds_count;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Convenience wrappers around internal::StringConverter.
|
||||
template <typename T>
|
||||
bool ParseValue(const T& type, const char* s, size_t length,
|
||||
typename StringConverter<T>::value_type* out) {
|
||||
return StringConverter<T>{}.Convert(type, s, length, out);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
enable_if_parameter_free<T, bool> ParseValue(
|
||||
const char* s, size_t length, typename StringConverter<T>::value_type* out) {
|
||||
static T type;
|
||||
return StringConverter<T>{}.Convert(type, s, length, out);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,172 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "arrow/result.h"
|
||||
#include "arrow/util/algorithm.h"
|
||||
#include "arrow/util/functional.h"
|
||||
#include "arrow/util/logging.h"
|
||||
|
||||
namespace arrow {
|
||||
namespace internal {
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> DeleteVectorElement(const std::vector<T>& values, size_t index) {
|
||||
DCHECK(!values.empty());
|
||||
DCHECK_LT(index, values.size());
|
||||
std::vector<T> out;
|
||||
out.reserve(values.size() - 1);
|
||||
for (size_t i = 0; i < index; ++i) {
|
||||
out.push_back(values[i]);
|
||||
}
|
||||
for (size_t i = index + 1; i < values.size(); ++i) {
|
||||
out.push_back(values[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> AddVectorElement(const std::vector<T>& values, size_t index,
|
||||
T new_element) {
|
||||
DCHECK_LE(index, values.size());
|
||||
std::vector<T> out;
|
||||
out.reserve(values.size() + 1);
|
||||
for (size_t i = 0; i < index; ++i) {
|
||||
out.push_back(values[i]);
|
||||
}
|
||||
out.emplace_back(std::move(new_element));
|
||||
for (size_t i = index; i < values.size(); ++i) {
|
||||
out.push_back(values[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> ReplaceVectorElement(const std::vector<T>& values, size_t index,
|
||||
T new_element) {
|
||||
DCHECK_LE(index, values.size());
|
||||
std::vector<T> out;
|
||||
out.reserve(values.size());
|
||||
for (size_t i = 0; i < index; ++i) {
|
||||
out.push_back(values[i]);
|
||||
}
|
||||
out.emplace_back(std::move(new_element));
|
||||
for (size_t i = index + 1; i < values.size(); ++i) {
|
||||
out.push_back(values[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T, typename Predicate>
|
||||
std::vector<T> FilterVector(std::vector<T> values, Predicate&& predicate) {
|
||||
auto new_end = std::remove_if(values.begin(), values.end(),
|
||||
[&](const T& value) { return !predicate(value); });
|
||||
values.erase(new_end, values.end());
|
||||
return values;
|
||||
}
|
||||
|
||||
template <typename Fn, typename From,
|
||||
typename To = decltype(std::declval<Fn>()(std::declval<From>()))>
|
||||
std::vector<To> MapVector(Fn&& map, const std::vector<From>& source) {
|
||||
std::vector<To> out;
|
||||
out.reserve(source.size());
|
||||
std::transform(source.begin(), source.end(), std::back_inserter(out),
|
||||
std::forward<Fn>(map));
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Fn, typename From,
|
||||
typename To = decltype(std::declval<Fn>()(std::declval<From>()))>
|
||||
std::vector<To> MapVector(Fn&& map, std::vector<From>&& source) {
|
||||
std::vector<To> out;
|
||||
out.reserve(source.size());
|
||||
std::transform(std::make_move_iterator(source.begin()),
|
||||
std::make_move_iterator(source.end()), std::back_inserter(out),
|
||||
std::forward<Fn>(map));
|
||||
return out;
|
||||
}
|
||||
|
||||
/// \brief Like MapVector, but where the function can fail.
|
||||
template <typename Fn, typename From = internal::call_traits::argument_type<0, Fn>,
|
||||
typename To = typename internal::call_traits::return_type<Fn>::ValueType>
|
||||
Result<std::vector<To>> MaybeMapVector(Fn&& map, const std::vector<From>& source) {
|
||||
std::vector<To> out;
|
||||
out.reserve(source.size());
|
||||
ARROW_RETURN_NOT_OK(MaybeTransform(source.begin(), source.end(),
|
||||
std::back_inserter(out), std::forward<Fn>(map)));
|
||||
return std::move(out);
|
||||
}
|
||||
|
||||
template <typename Fn, typename From = internal::call_traits::argument_type<0, Fn>,
|
||||
typename To = typename internal::call_traits::return_type<Fn>::ValueType>
|
||||
Result<std::vector<To>> MaybeMapVector(Fn&& map, std::vector<From>&& source) {
|
||||
std::vector<To> out;
|
||||
out.reserve(source.size());
|
||||
ARROW_RETURN_NOT_OK(MaybeTransform(std::make_move_iterator(source.begin()),
|
||||
std::make_move_iterator(source.end()),
|
||||
std::back_inserter(out), std::forward<Fn>(map)));
|
||||
return std::move(out);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> FlattenVectors(const std::vector<std::vector<T>>& vecs) {
|
||||
std::size_t sum = 0;
|
||||
for (const auto& vec : vecs) {
|
||||
sum += vec.size();
|
||||
}
|
||||
std::vector<T> out;
|
||||
out.reserve(sum);
|
||||
for (const auto& vec : vecs) {
|
||||
out.insert(out.end(), vec.begin(), vec.end());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Result<std::vector<T>> UnwrapOrRaise(std::vector<Result<T>>&& results) {
|
||||
std::vector<T> out;
|
||||
out.reserve(results.size());
|
||||
auto end = std::make_move_iterator(results.end());
|
||||
for (auto it = std::make_move_iterator(results.begin()); it != end; it++) {
|
||||
if (!it->ok()) {
|
||||
return it->status();
|
||||
}
|
||||
out.push_back(it->MoveValueUnsafe());
|
||||
}
|
||||
return std::move(out);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Result<std::vector<T>> UnwrapOrRaise(const std::vector<Result<T>>& results) {
|
||||
std::vector<T> out;
|
||||
out.reserve(results.size());
|
||||
for (const auto& result : results) {
|
||||
if (!result.ok()) {
|
||||
return result.status();
|
||||
}
|
||||
out.push_back(result.ValueUnsafe());
|
||||
}
|
||||
return std::move(out);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace arrow
|
||||
@@ -0,0 +1,83 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||
// Windows
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(disable : 4251)
|
||||
#else
|
||||
#pragma GCC diagnostic ignored "-Wattributes"
|
||||
#endif
|
||||
|
||||
#if defined(__cplusplus) && defined(__GNUC__) && !defined(__clang__)
|
||||
// Use C++ attribute syntax where possible to avoid GCC parser bug
|
||||
// (https://stackoverflow.com/questions/57993818/gcc-how-to-combine-attribute-dllexport-and-nodiscard-in-a-struct-de)
|
||||
#define ARROW_DLLEXPORT [[gnu::dllexport]]
|
||||
#define ARROW_DLLIMPORT [[gnu::dllimport]]
|
||||
#else
|
||||
#define ARROW_DLLEXPORT __declspec(dllexport)
|
||||
#define ARROW_DLLIMPORT __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
#ifdef ARROW_STATIC
|
||||
#define ARROW_EXPORT
|
||||
#define ARROW_FRIEND_EXPORT
|
||||
#define ARROW_TEMPLATE_EXPORT
|
||||
#elif defined(ARROW_EXPORTING)
|
||||
#define ARROW_EXPORT ARROW_DLLEXPORT
|
||||
// For some reason [[gnu::dllexport]] doesn't work well with friend declarations
|
||||
#define ARROW_FRIEND_EXPORT __declspec(dllexport)
|
||||
#define ARROW_TEMPLATE_EXPORT ARROW_DLLEXPORT
|
||||
#else
|
||||
#define ARROW_EXPORT ARROW_DLLIMPORT
|
||||
#define ARROW_FRIEND_EXPORT __declspec(dllimport)
|
||||
#define ARROW_TEMPLATE_EXPORT ARROW_DLLIMPORT
|
||||
#endif
|
||||
|
||||
#define ARROW_NO_EXPORT
|
||||
#define ARROW_FORCE_INLINE __forceinline
|
||||
|
||||
#else
|
||||
|
||||
// Non-Windows
|
||||
|
||||
#define ARROW_FORCE_INLINE
|
||||
|
||||
#if defined(__cplusplus) && (defined(__GNUC__) || defined(__clang__))
|
||||
#ifndef ARROW_EXPORT
|
||||
#define ARROW_EXPORT [[gnu::visibility("default")]]
|
||||
#endif
|
||||
#ifndef ARROW_NO_EXPORT
|
||||
#define ARROW_NO_EXPORT [[gnu::visibility("hidden")]]
|
||||
#endif
|
||||
#else
|
||||
// Not C++, or not gcc/clang
|
||||
#ifndef ARROW_EXPORT
|
||||
#define ARROW_EXPORT
|
||||
#endif
|
||||
#ifndef ARROW_NO_EXPORT
|
||||
#define ARROW_NO_EXPORT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define ARROW_FRIEND_EXPORT
|
||||
#define ARROW_TEMPLATE_EXPORT
|
||||
|
||||
#endif // Non-Windows
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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.
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// Windows defines min and max macros that mess up std::min/max
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
// Set Windows 7 as a conservative minimum for Apache Arrow
|
||||
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x601
|
||||
#undef _WIN32_WINNT
|
||||
#endif
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x601
|
||||
#endif
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "arrow/util/windows_fixup.h"
|
||||
|
||||
#endif // _WIN32
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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.
|
||||
|
||||
// This header needs to be included multiple times.
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
// The Windows API defines macros from *File resolving to either
|
||||
// *FileA or *FileW. Need to undo them.
|
||||
#ifdef CopyFile
|
||||
#undef CopyFile
|
||||
#endif
|
||||
#ifdef CreateFile
|
||||
#undef CreateFile
|
||||
#endif
|
||||
#ifdef DeleteFile
|
||||
#undef DeleteFile
|
||||
#endif
|
||||
|
||||
// Other annoying Windows macro definitions...
|
||||
#ifdef IN
|
||||
#undef IN
|
||||
#endif
|
||||
#ifdef OUT
|
||||
#undef OUT
|
||||
#endif
|
||||
|
||||
// Note that we can't undefine OPTIONAL, because it can be used in other
|
||||
// Windows headers...
|
||||
|
||||
#endif // _WIN32
|
||||
Reference in New Issue
Block a user