blob: 25e30e65a352621ce0ec2b4db369acaab77ba4bd [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 <cstdint>
#include <sstream>
#include "arrow/compute/api_vector.h"
#include "arrow/compute/kernels/test_util.h"
#include "arrow/testing/gtest_util.h"
#include "arrow/testing/random.h"
#include "arrow/util/benchmark_util.h"
namespace arrow {
namespace compute {
constexpr auto kSeed = 0x0ff1ce;
struct FilterParams {
// proportion of nulls in the values array
const double values_null_proportion;
// proportion of true in filter
const double selected_proportion;
// proportion of nulls in the filter
const double filter_null_proportion;
};
std::vector<int64_t> g_data_sizes = {kL2Size};
// The benchmark state parameter references this vector of cases. Test high and
// low selectivity filters.
// clang-format off
std::vector<FilterParams> g_filter_params = {
{0., 0.999, 0.05},
{0., 0.50, 0.05},
{0., 0.01, 0.05},
{0.001, 0.999, 0.05},
{0.001, 0.50, 0.05},
{0.001, 0.01, 0.05},
{0.01, 0.999, 0.05},
{0.01, 0.50, 0.05},
{0.01, 0.01, 0.05},
{0.1, 0.999, 0.05},
{0.1, 0.50, 0.05},
{0.1, 0.01, 0.05},
{0.9, 0.999, 0.05},
{0.9, 0.50, 0.05},
{0.9, 0.01, 0.05}
};
// clang-format on
// RAII struct to handle some of the boilerplate in filter
struct FilterArgs {
// size of memory tested (per iteration) in bytes
int64_t size;
// What to call the "size" that's reported in the console output, for result
// interpretability.
std::string size_name = "size";
double values_null_proportion = 0.;
double selected_proportion = 0.;
double filter_null_proportion = 0.;
FilterArgs(benchmark::State& state, bool filter_has_nulls)
: size(state.range(0)), state_(state) {
auto params = g_filter_params[state.range(1)];
values_null_proportion = params.values_null_proportion;
selected_proportion = params.selected_proportion;
filter_null_proportion = filter_has_nulls ? params.filter_null_proportion : 0;
}
~FilterArgs() {
state_.counters[size_name] = static_cast<double>(size);
state_.counters["select%"] = selected_proportion * 100;
state_.counters["data null%"] = values_null_proportion * 100;
state_.counters["mask null%"] = filter_null_proportion * 100;
state_.SetBytesProcessed(state_.iterations() * size);
}
private:
benchmark::State& state_;
};
struct TakeBenchmark {
benchmark::State& state;
RegressionArgs args;
random::RandomArrayGenerator rand;
bool indices_have_nulls;
bool monotonic_indices = false;
TakeBenchmark(benchmark::State& state, bool indices_have_nulls,
bool monotonic_indices = false)
: state(state),
args(state, /*size_is_bytes=*/false),
rand(kSeed),
indices_have_nulls(indices_have_nulls),
monotonic_indices(monotonic_indices) {}
void Int64() {
auto values = rand.Int64(args.size, -100, 100, args.null_proportion);
Bench(values);
}
void FSLInt64() {
auto int_array = rand.Int64(args.size, -100, 100, args.null_proportion);
auto values = std::make_shared<FixedSizeListArray>(
fixed_size_list(int64(), 1), args.size, int_array, int_array->null_bitmap(),
int_array->null_count());
Bench(values);
}
void String() {
int32_t string_min_length = 0, string_max_length = 32;
auto values = std::static_pointer_cast<StringArray>(rand.String(
args.size, string_min_length, string_max_length, args.null_proportion));
Bench(values);
}
void Bench(const std::shared_ptr<Array>& values) {
double indices_null_proportion = indices_have_nulls ? args.null_proportion : 0;
auto indices =
rand.Int32(values->length(), 0, static_cast<int32_t>(values->length() - 1),
indices_null_proportion);
if (monotonic_indices) {
auto arg_sorter = *SortIndices(*indices);
indices = *Take(*indices, *arg_sorter);
}
for (auto _ : state) {
ABORT_NOT_OK(Take(values, indices).status());
}
}
};
struct FilterBenchmark {
benchmark::State& state;
FilterArgs args;
random::RandomArrayGenerator rand;
bool filter_has_nulls;
FilterBenchmark(benchmark::State& state, bool filter_has_nulls)
: state(state),
args(state, filter_has_nulls),
rand(kSeed),
filter_has_nulls(filter_has_nulls) {}
void Int64() {
const int64_t array_size = args.size / sizeof(int64_t);
auto values = std::static_pointer_cast<NumericArray<Int64Type>>(
rand.Int64(array_size, -100, 100, args.values_null_proportion));
Bench(values);
}
void FSLInt64() {
const int64_t array_size = args.size / sizeof(int64_t);
auto int_array = std::static_pointer_cast<NumericArray<Int64Type>>(
rand.Int64(array_size, -100, 100, args.values_null_proportion));
auto values = std::make_shared<FixedSizeListArray>(
fixed_size_list(int64(), 1), array_size, int_array, int_array->null_bitmap(),
int_array->null_count());
Bench(values);
}
void String() {
int32_t string_min_length = 0, string_max_length = 32;
int32_t string_mean_length = (string_max_length + string_min_length) / 2;
// for an array of 50% null strings, we need to generate twice as many strings
// to ensure that they have an average of args.size total characters
int64_t array_size = args.size;
if (args.values_null_proportion < 1) {
array_size = static_cast<int64_t>(args.size / string_mean_length /
(1 - args.values_null_proportion));
}
auto values = std::static_pointer_cast<StringArray>(rand.String(
array_size, string_min_length, string_max_length, args.values_null_proportion));
Bench(values);
}
void Bench(const std::shared_ptr<Array>& values) {
auto filter = rand.Boolean(values->length(), args.selected_proportion,
args.filter_null_proportion);
for (auto _ : state) {
ABORT_NOT_OK(Filter(values, filter).status());
}
}
void BenchRecordBatch() {
const int64_t total_data_cells = 10000000;
const int64_t num_columns = state.range(0);
const int64_t num_rows = total_data_cells / num_columns;
auto col_data = rand.Float64(num_rows, 0, 1);
auto filter =
rand.Boolean(num_rows, args.selected_proportion, args.filter_null_proportion);
int64_t output_length =
internal::GetFilterOutputSize(*filter->data(), FilterOptions::DROP);
// HACK: set FilterArgs.size to the number of selected data cells *
// sizeof(double) for accurate memory processing performance
args.size = output_length * num_columns * sizeof(double);
args.size_name = "extracted_size";
state.counters["num_cols"] = static_cast<double>(num_columns);
std::vector<std::shared_ptr<Array>> columns;
std::vector<std::shared_ptr<Field>> fields;
for (int64_t i = 0; i < num_columns; ++i) {
std::stringstream ss;
ss << "f" << i;
fields.push_back(::arrow::field(ss.str(), float64()));
columns.push_back(col_data);
}
auto batch = RecordBatch::Make(schema(fields), num_rows, columns);
for (auto _ : state) {
ABORT_NOT_OK(Filter(batch, filter).status());
}
}
};
static void FilterInt64FilterNoNulls(benchmark::State& state) {
FilterBenchmark(state, false).Int64();
}
static void FilterInt64FilterWithNulls(benchmark::State& state) {
FilterBenchmark(state, true).Int64();
}
static void FilterFSLInt64FilterNoNulls(benchmark::State& state) {
FilterBenchmark(state, false).FSLInt64();
}
static void FilterFSLInt64FilterWithNulls(benchmark::State& state) {
FilterBenchmark(state, true).FSLInt64();
}
static void FilterStringFilterNoNulls(benchmark::State& state) {
FilterBenchmark(state, false).String();
}
static void FilterStringFilterWithNulls(benchmark::State& state) {
FilterBenchmark(state, true).String();
}
static void FilterRecordBatchNoNulls(benchmark::State& state) {
FilterBenchmark(state, false).BenchRecordBatch();
}
static void FilterRecordBatchWithNulls(benchmark::State& state) {
FilterBenchmark(state, true).BenchRecordBatch();
}
static void TakeInt64RandomIndicesNoNulls(benchmark::State& state) {
TakeBenchmark(state, false).Int64();
}
static void TakeInt64RandomIndicesWithNulls(benchmark::State& state) {
TakeBenchmark(state, true).Int64();
}
static void TakeInt64MonotonicIndices(benchmark::State& state) {
TakeBenchmark(state, /*indices_with_nulls=*/false, /*monotonic=*/true).Int64();
}
static void TakeFSLInt64RandomIndicesNoNulls(benchmark::State& state) {
TakeBenchmark(state, false).FSLInt64();
}
static void TakeFSLInt64RandomIndicesWithNulls(benchmark::State& state) {
TakeBenchmark(state, true).FSLInt64();
}
static void TakeFSLInt64MonotonicIndices(benchmark::State& state) {
TakeBenchmark(state, /*indices_with_nulls=*/false, /*monotonic=*/true).FSLInt64();
}
static void TakeStringRandomIndicesNoNulls(benchmark::State& state) {
TakeBenchmark(state, false).String();
}
static void TakeStringRandomIndicesWithNulls(benchmark::State& state) {
TakeBenchmark(state, true).String();
}
static void TakeStringMonotonicIndices(benchmark::State& state) {
TakeBenchmark(state, /*indices_with_nulls=*/false, /*monotonic=*/true).FSLInt64();
}
void FilterSetArgs(benchmark::internal::Benchmark* bench) {
for (int64_t size : g_data_sizes) {
for (int i = 0; i < static_cast<int>(g_filter_params.size()); ++i) {
bench->Args({static_cast<ArgsType>(size), i});
}
}
}
BENCHMARK(FilterInt64FilterNoNulls)->Apply(FilterSetArgs);
BENCHMARK(FilterInt64FilterWithNulls)->Apply(FilterSetArgs);
BENCHMARK(FilterFSLInt64FilterNoNulls)->Apply(FilterSetArgs);
BENCHMARK(FilterFSLInt64FilterWithNulls)->Apply(FilterSetArgs);
BENCHMARK(FilterStringFilterNoNulls)->Apply(FilterSetArgs);
BENCHMARK(FilterStringFilterWithNulls)->Apply(FilterSetArgs);
void FilterRecordBatchSetArgs(benchmark::internal::Benchmark* bench) {
for (auto num_cols : std::vector<int>({10, 50, 100})) {
for (int i = 0; i < static_cast<int>(g_filter_params.size()); ++i) {
bench->Args({num_cols, i});
}
}
}
BENCHMARK(FilterRecordBatchNoNulls)->Apply(FilterRecordBatchSetArgs);
BENCHMARK(FilterRecordBatchWithNulls)->Apply(FilterRecordBatchSetArgs);
void TakeSetArgs(benchmark::internal::Benchmark* bench) {
for (int64_t size : g_data_sizes) {
for (auto nulls : std::vector<ArgsType>({1000, 10, 2, 1, 0})) {
bench->Args({static_cast<ArgsType>(size), nulls});
}
}
}
BENCHMARK(TakeInt64RandomIndicesNoNulls)->Apply(TakeSetArgs);
BENCHMARK(TakeInt64RandomIndicesWithNulls)->Apply(TakeSetArgs);
BENCHMARK(TakeInt64MonotonicIndices)->Apply(TakeSetArgs);
BENCHMARK(TakeFSLInt64RandomIndicesNoNulls)->Apply(TakeSetArgs);
BENCHMARK(TakeFSLInt64RandomIndicesWithNulls)->Apply(TakeSetArgs);
BENCHMARK(TakeFSLInt64MonotonicIndices)->Apply(TakeSetArgs);
BENCHMARK(TakeStringRandomIndicesNoNulls)->Apply(TakeSetArgs);
BENCHMARK(TakeStringRandomIndicesWithNulls)->Apply(TakeSetArgs);
BENCHMARK(TakeStringMonotonicIndices)->Apply(TakeSetArgs);
} // namespace compute
} // namespace arrow