blob: d0edb4b2d08eded0b3ca5b19a998be20d6dcebd4 [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 <algorithm>
#include <cstdint>
#include <limits>
#include <numeric>
#include <random>
#include <string>
#include <vector>
#include "benchmark/benchmark.h"
#include "arrow/builder.h"
#include "arrow/memory_pool.h"
#include "arrow/testing/gtest_util.h"
#include "arrow/util/bit_util.h"
#include "arrow/util/decimal.h"
#include "arrow/util/string_view.h"
namespace arrow {
using ValueType = int64_t;
using VectorType = std::vector<ValueType>;
constexpr int64_t kNumberOfElements = 256 * 512;
static VectorType AlmostU8CompressibleVector() {
VectorType data(kNumberOfElements, 64);
// Insert an element late in the game that does not fit in the 8bit
// representation. This forces AdaptiveIntBuilder's to resize.
data[kNumberOfElements - 2] = 1L << 13;
return data;
}
constexpr int64_t kRounds = 256;
static VectorType kData = AlmostU8CompressibleVector();
constexpr int64_t kBytesProcessPerRound = kNumberOfElements * sizeof(ValueType);
constexpr int64_t kBytesProcessed = kRounds * kBytesProcessPerRound;
static const char* kBinaryString = "12345678";
static arrow::util::string_view kBinaryView(kBinaryString);
static void BuildIntArrayNoNulls(benchmark::State& state) { // NOLINT non-const reference
for (auto _ : state) {
Int64Builder builder;
for (int i = 0; i < kRounds; i++) {
ABORT_NOT_OK(builder.AppendValues(kData.data(), kData.size(), nullptr));
}
std::shared_ptr<Array> out;
ABORT_NOT_OK(builder.Finish(&out));
}
state.SetBytesProcessed(state.iterations() * kBytesProcessed);
}
static void BuildAdaptiveIntNoNulls(
benchmark::State& state) { // NOLINT non-const reference
for (auto _ : state) {
AdaptiveIntBuilder builder;
for (int i = 0; i < kRounds; i++) {
ABORT_NOT_OK(builder.AppendValues(kData.data(), kData.size(), nullptr));
}
std::shared_ptr<Array> out;
ABORT_NOT_OK(builder.Finish(&out));
}
state.SetBytesProcessed(state.iterations() * kBytesProcessed);
}
static void BuildAdaptiveIntNoNullsScalarAppend(
benchmark::State& state) { // NOLINT non-const reference
for (auto _ : state) {
AdaptiveIntBuilder builder;
for (int i = 0; i < kRounds; i++) {
for (size_t j = 0; j < kData.size(); j++) {
ABORT_NOT_OK(builder.Append(kData[i]))
}
}
std::shared_ptr<Array> out;
ABORT_NOT_OK(builder.Finish(&out));
}
state.SetBytesProcessed(state.iterations() * kBytesProcessed);
}
static void BuildBooleanArrayNoNulls(
benchmark::State& state) { // NOLINT non-const reference
size_t n_bytes = kBytesProcessPerRound;
const uint8_t* data = reinterpret_cast<const uint8_t*>(kData.data());
for (auto _ : state) {
BooleanBuilder builder;
for (int i = 0; i < kRounds; i++) {
ABORT_NOT_OK(builder.AppendValues(data, n_bytes));
}
std::shared_ptr<Array> out;
ABORT_NOT_OK(builder.Finish(&out));
}
state.SetBytesProcessed(state.iterations() * kBytesProcessed);
}
static void BuildBinaryArray(benchmark::State& state) { // NOLINT non-const reference
for (auto _ : state) {
BinaryBuilder builder;
for (int64_t i = 0; i < kRounds * kNumberOfElements; i++) {
ABORT_NOT_OK(builder.Append(kBinaryView));
}
std::shared_ptr<Array> out;
ABORT_NOT_OK(builder.Finish(&out));
}
state.SetBytesProcessed(state.iterations() * kBytesProcessed);
}
static void BuildChunkedBinaryArray(
benchmark::State& state) { // NOLINT non-const reference
// 1MB chunks
const int32_t kChunkSize = 1 << 20;
for (auto _ : state) {
internal::ChunkedBinaryBuilder builder(kChunkSize);
for (int64_t i = 0; i < kRounds * kNumberOfElements; i++) {
ABORT_NOT_OK(builder.Append(kBinaryView));
}
ArrayVector out;
ABORT_NOT_OK(builder.Finish(&out));
}
state.SetBytesProcessed(state.iterations() * kBytesProcessed);
}
static void BuildFixedSizeBinaryArray(
benchmark::State& state) { // NOLINT non-const reference
auto type = fixed_size_binary(static_cast<int32_t>(kBinaryView.size()));
for (auto _ : state) {
FixedSizeBinaryBuilder builder(type);
for (int64_t i = 0; i < kRounds * kNumberOfElements; i++) {
ABORT_NOT_OK(builder.Append(kBinaryView));
}
std::shared_ptr<Array> out;
ABORT_NOT_OK(builder.Finish(&out));
}
state.SetBytesProcessed(state.iterations() * kBytesProcessed);
}
static void BuildDecimalArray(benchmark::State& state) { // NOLINT non-const reference
auto type = decimal(10, 5);
Decimal128 value;
int32_t precision = 0;
int32_t scale = 0;
ABORT_NOT_OK(Decimal128::FromString("1234.1234", &value, &precision, &scale));
for (auto _ : state) {
Decimal128Builder builder(type);
for (int64_t i = 0; i < kRounds * kNumberOfElements; i++) {
ABORT_NOT_OK(builder.Append(value));
}
std::shared_ptr<Array> out;
ABORT_NOT_OK(builder.Finish(&out));
}
state.SetBytesProcessed(state.iterations() * kRounds * kNumberOfElements * 16);
}
// ----------------------------------------------------------------------
// DictionaryBuilder benchmarks
size_t kDistinctElements = kNumberOfElements / 100;
// Testing with different distributions of integer values helps stress
// the hash table's robustness.
// Make a vector out of `n_distinct` sequential int values
template <class Integer = ValueType>
static std::vector<Integer> MakeSequentialIntDictFodder() {
std::default_random_engine gen(42);
std::vector<Integer> values(kNumberOfElements);
{
std::uniform_int_distribution<Integer> values_dist(0, kDistinctElements - 1);
std::generate(values.begin(), values.end(), [&]() { return values_dist(gen); });
}
return values;
}
// Make a vector out of `n_distinct` int values with potentially colliding hash
// entries as only their highest bits differ.
template <class Integer = ValueType>
static std::vector<Integer> MakeSimilarIntDictFodder() {
std::default_random_engine gen(42);
std::vector<Integer> values(kNumberOfElements);
{
std::uniform_int_distribution<Integer> values_dist(0, kDistinctElements - 1);
auto max_int = std::numeric_limits<Integer>::max();
auto multiplier =
static_cast<Integer>(BitUtil::NextPower2(max_int / kDistinctElements / 2));
std::generate(values.begin(), values.end(),
[&]() { return multiplier * values_dist(gen); });
}
return values;
}
// Make a vector out of `n_distinct` random int values
template <class Integer = ValueType>
static std::vector<Integer> MakeRandomIntDictFodder() {
std::default_random_engine gen(42);
std::vector<Integer> values_dict(kDistinctElements);
std::vector<Integer> values(kNumberOfElements);
{
std::uniform_int_distribution<Integer> values_dist(
0, std::numeric_limits<Integer>::max());
std::generate(values_dict.begin(), values_dict.end(),
[&]() { return static_cast<Integer>(values_dist(gen)); });
}
{
std::uniform_int_distribution<int32_t> indices_dist(
0, static_cast<int32_t>(kDistinctElements - 1));
std::generate(values.begin(), values.end(),
[&]() { return values_dict[indices_dist(gen)]; });
}
return values;
}
// Make a vector out of `kDistinctElements` string values
static std::vector<std::string> MakeStringDictFodder() {
std::default_random_engine gen(42);
std::vector<std::string> values_dict(kDistinctElements);
std::vector<std::string> values(kNumberOfElements);
{
auto it = values_dict.begin();
// Add empty string
*it++ = "";
// Add a few similar strings
*it++ = "abc";
*it++ = "abcdef";
*it++ = "abcfgh";
// Add random strings
std::uniform_int_distribution<int32_t> length_dist(2, 20);
std::independent_bits_engine<std::default_random_engine, 8, uint16_t> bytes_gen(42);
std::generate(it, values_dict.end(), [&] {
auto length = length_dist(gen);
std::string s(length, 'X');
for (int32_t i = 0; i < length; ++i) {
s[i] = static_cast<char>(bytes_gen());
}
return s;
});
}
{
std::uniform_int_distribution<int32_t> indices_dist(
0, static_cast<int32_t>(kDistinctElements - 1));
std::generate(values.begin(), values.end(),
[&] { return values_dict[indices_dist(gen)]; });
}
return values;
}
template <class DictionaryBuilderType, class Scalar>
static void BenchmarkDictionaryArray(
benchmark::State& state, // NOLINT non-const reference
const std::vector<Scalar>& fodder, size_t fodder_nbytes = 0) {
for (auto _ : state) {
DictionaryBuilderType builder(default_memory_pool());
for (int64_t i = 0; i < kRounds; i++) {
for (const auto& value : fodder) {
ABORT_NOT_OK(builder.Append(value));
}
}
std::shared_ptr<Array> out;
ABORT_NOT_OK(builder.Finish(&out));
}
if (fodder_nbytes == 0) {
fodder_nbytes = fodder.size() * sizeof(Scalar);
}
state.SetBytesProcessed(state.iterations() * fodder_nbytes * kRounds);
}
static void BuildInt64DictionaryArrayRandom(
benchmark::State& state) { // NOLINT non-const reference
const auto fodder = MakeRandomIntDictFodder();
BenchmarkDictionaryArray<DictionaryBuilder<Int64Type>>(state, fodder);
}
static void BuildInt64DictionaryArraySequential(
benchmark::State& state) { // NOLINT non-const reference
const auto fodder = MakeSequentialIntDictFodder();
BenchmarkDictionaryArray<DictionaryBuilder<Int64Type>>(state, fodder);
}
static void BuildInt64DictionaryArraySimilar(
benchmark::State& state) { // NOLINT non-const reference
const auto fodder = MakeSimilarIntDictFodder();
BenchmarkDictionaryArray<DictionaryBuilder<Int64Type>>(state, fodder);
}
static void BuildStringDictionaryArray(
benchmark::State& state) { // NOLINT non-const reference
const auto fodder = MakeStringDictFodder();
auto fodder_nbytes =
std::accumulate(fodder.begin(), fodder.end(), 0ULL,
[&](size_t acc, const std::string& s) { return acc + s.size(); });
BenchmarkDictionaryArray<BinaryDictionaryBuilder>(state, fodder, fodder_nbytes);
}
static void ArrayDataConstructDestruct(
benchmark::State& state) { // NOLINT non-const reference
std::vector<std::shared_ptr<ArrayData>> arrays;
const int kNumArrays = 1000;
auto InitArrays = [&]() {
for (int i = 0; i < kNumArrays; ++i) {
arrays.emplace_back(new ArrayData);
}
};
for (auto _ : state) {
InitArrays();
arrays.clear();
}
}
// ----------------------------------------------------------------------
// BufferBuilder benchmarks
static void BenchmarkBufferBuilder(
const std::string& datum,
benchmark::State& state) { // NOLINT non-const reference
const void* raw_data = datum.data();
int64_t raw_nbytes = static_cast<int64_t>(datum.size());
// Write approx. 256 MB to BufferBuilder
int64_t num_raw_values = (1 << 28) / raw_nbytes;
for (auto _ : state) {
BufferBuilder builder;
std::shared_ptr<Buffer> buf;
for (int64_t i = 0; i < num_raw_values; ++i) {
ABORT_NOT_OK(builder.Append(raw_data, raw_nbytes));
}
ABORT_NOT_OK(builder.Finish(&buf));
}
state.SetBytesProcessed(int64_t(state.iterations()) * num_raw_values * raw_nbytes);
}
static void BufferBuilderTinyWrites(
benchmark::State& state) { // NOLINT non-const reference
// A 8-byte datum
return BenchmarkBufferBuilder("abdefghi", state);
}
static void BufferBuilderSmallWrites(
benchmark::State& state) { // NOLINT non-const reference
// A 700-byte datum
std::string datum;
for (int i = 0; i < 100; ++i) {
datum += "abcdefg";
}
return BenchmarkBufferBuilder(datum, state);
}
static void BufferBuilderLargeWrites(
benchmark::State& state) { // NOLINT non-const reference
// A 1.5MB datum
std::string datum(1500000, 'x');
return BenchmarkBufferBuilder(datum, state);
}
BENCHMARK(BufferBuilderTinyWrites)->UseRealTime();
BENCHMARK(BufferBuilderSmallWrites)->UseRealTime();
BENCHMARK(BufferBuilderLargeWrites)->UseRealTime();
// ----------------------------------------------------------------------
// Benchmark declarations
//
#ifdef ARROW_WITH_BENCHMARKS_REFERENCE
// This benchmarks acts as a reference to the native std::vector
// implementation. It appends kRounds chunks into a vector.
static void ReferenceBuildVectorNoNulls(
benchmark::State& state) { // NOLINT non-const reference
for (auto _ : state) {
std::vector<int64_t> builder;
for (int i = 0; i < kRounds; i++) {
builder.insert(builder.end(), kData.cbegin(), kData.cend());
}
}
state.SetBytesProcessed(state.iterations() * kBytesProcessed);
}
BENCHMARK(ReferenceBuildVectorNoNulls);
#endif
BENCHMARK(BuildBooleanArrayNoNulls);
BENCHMARK(BuildIntArrayNoNulls);
BENCHMARK(BuildAdaptiveIntNoNulls);
BENCHMARK(BuildAdaptiveIntNoNullsScalarAppend);
BENCHMARK(BuildBinaryArray);
BENCHMARK(BuildChunkedBinaryArray);
BENCHMARK(BuildFixedSizeBinaryArray);
BENCHMARK(BuildDecimalArray);
BENCHMARK(BuildInt64DictionaryArrayRandom);
BENCHMARK(BuildInt64DictionaryArraySequential);
BENCHMARK(BuildInt64DictionaryArraySimilar);
BENCHMARK(BuildStringDictionaryArray);
BENCHMARK(ArrayDataConstructDestruct);
} // namespace arrow