blob: daf03754984a8a0e340d89ee03fd213d4e50ecae [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "benchmark/benchmark.h"
#include "arrow/array/array_base.h"
#include "arrow/compute/api.h"
#include "arrow/memory_pool.h"
#include "arrow/scalar.h"
#include "arrow/testing/gtest_util.h"
#include "arrow/testing/random.h"
#include "arrow/util/benchmark_util.h"
namespace arrow {
using internal::checked_cast;
using internal::checked_pointer_cast;
namespace compute {
constexpr int32_t kSeed = 0xfede4a7e;
constexpr int64_t kScalarCount = 1 << 10;
inline ScalarVector ToScalars(std::shared_ptr<Array> arr) {
ScalarVector scalars{static_cast<size_t>(arr->length())};
int64_t i = 0;
for (auto& scalar : scalars) {
scalar = arr->GetScalar(i++).ValueOrDie();
}
return scalars;
}
void BM_CastDispatch(benchmark::State& state) {
// Repeatedly invoke a trivial Cast: the main cost should be dispatch
random::RandomArrayGenerator rag(kSeed);
auto int_scalars = ToScalars(rag.Int64(kScalarCount, 0, 1 << 20));
auto double_type = float64();
for (auto _ : state) {
Datum timestamp_scalar;
for (Datum int_scalar : int_scalars) {
ASSERT_OK_AND_ASSIGN(timestamp_scalar, Cast(int_scalar, double_type));
}
benchmark::DoNotOptimize(timestamp_scalar);
}
state.SetItemsProcessed(state.iterations() * kScalarCount);
}
void BM_CastDispatchBaseline(benchmark::State& state) {
// Repeatedly invoke a trivial Cast with all dispatch outside the hot loop
random::RandomArrayGenerator rag(kSeed);
auto int_scalars = ToScalars(rag.Int64(kScalarCount, 0, 1 << 20));
auto double_type = float64();
CastOptions cast_options;
cast_options.to_type = double_type;
ASSERT_OK_AND_ASSIGN(auto cast_function, GetCastFunction(double_type));
ASSERT_OK_AND_ASSIGN(auto cast_kernel,
cast_function->DispatchExact({int_scalars[0]->type}));
const auto& exec = static_cast<const ScalarKernel*>(cast_kernel)->exec;
ExecContext exec_context;
KernelContext kernel_context(&exec_context);
auto cast_state = cast_kernel
->init(&kernel_context,
KernelInitArgs{cast_kernel, {double_type}, &cast_options})
.ValueOrDie();
kernel_context.SetState(cast_state.get());
for (auto _ : state) {
Datum timestamp_scalar = MakeNullScalar(double_type);
for (Datum int_scalar : int_scalars) {
ABORT_NOT_OK(
exec(&kernel_context, {{std::move(int_scalar)}, 1}, &timestamp_scalar));
}
benchmark::DoNotOptimize(timestamp_scalar);
}
state.SetItemsProcessed(state.iterations() * kScalarCount);
}
void BM_AddDispatch(benchmark::State& state) {
ExecContext exec_context;
KernelContext kernel_context(&exec_context);
for (auto _ : state) {
ASSERT_OK_AND_ASSIGN(auto add_function, GetFunctionRegistry()->GetFunction("add"));
ASSERT_OK_AND_ASSIGN(auto add_kernel,
checked_cast<const ScalarFunction&>(*add_function)
.DispatchExact({int64(), int64()}));
benchmark::DoNotOptimize(add_kernel);
}
state.SetItemsProcessed(state.iterations());
}
static ScalarVector MakeScalarsForIsValid(int64_t nitems) {
std::vector<std::shared_ptr<Scalar>> scalars;
scalars.reserve(nitems);
for (int64_t i = 0; i < nitems; ++i) {
if (i & 0x10) {
scalars.emplace_back(MakeNullScalar(int64()));
} else {
scalars.emplace_back(*MakeScalar(int64(), i));
}
}
return scalars;
}
void BM_ExecuteScalarFunctionOnScalar(benchmark::State& state) {
// Execute a trivial function, with argument dispatch in the hot path
const int64_t N = 10000;
auto function = checked_pointer_cast<ScalarFunction>(
*GetFunctionRegistry()->GetFunction("is_valid"));
const auto scalars = MakeScalarsForIsValid(N);
ExecContext exec_context;
KernelContext kernel_context(&exec_context);
for (auto _ : state) {
int64_t total = 0;
for (const auto& scalar : scalars) {
const Datum result =
*function->Execute({Datum(scalar)}, function->default_options(), &exec_context);
total += result.scalar()->is_valid;
}
benchmark::DoNotOptimize(total);
}
state.SetItemsProcessed(state.iterations() * N);
}
void BM_ExecuteScalarKernelOnScalar(benchmark::State& state) {
// Execute a trivial function, with argument dispatch outside the hot path
const int64_t N = 10000;
auto function = *GetFunctionRegistry()->GetFunction("is_valid");
auto kernel = *function->DispatchExact({ValueDescr::Scalar(int64())});
const auto& exec = static_cast<const ScalarKernel&>(*kernel).exec;
const auto scalars = MakeScalarsForIsValid(N);
ExecContext exec_context;
KernelContext kernel_context(&exec_context);
for (auto _ : state) {
int64_t total = 0;
for (const auto& scalar : scalars) {
Datum result{MakeNullScalar(int64())};
ABORT_NOT_OK(exec(&kernel_context, ExecBatch{{scalar}, /*length=*/1}, &result));
total += result.scalar()->is_valid;
}
benchmark::DoNotOptimize(total);
}
state.SetItemsProcessed(state.iterations() * N);
}
BENCHMARK(BM_CastDispatch);
BENCHMARK(BM_CastDispatchBaseline);
BENCHMARK(BM_AddDispatch);
BENCHMARK(BM_ExecuteScalarFunctionOnScalar);
BENCHMARK(BM_ExecuteScalarKernelOnScalar);
} // namespace compute
} // namespace arrow