dev/benchmarks/c/array_benchmark.cc (280 lines of code) (raw):

// 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 <nanoarrow/nanoarrow.hpp> // The length of most arrays used in these benchmarks. Just big enough so // that the benchmark takes a non-trivial amount of time to run. static const int64_t kNumItemsPrettyBig = 1000000; // Used to generate string/binary arrays static const std::string kAlphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; /// \defgroup nanoarrow-benchmark-array-view ArrowArrayView-related benchmarks /// /// Benchmarks for consuming ArrowArrays using the `ArrowArrayViewXXX()` functions. /// /// @{ // Helper to initialize an ArrowArrayView from buffers. The ArrowArray isn't used // by the benchmark but is needed to hold the memory. template <typename Buffer1T, typename Buffer2T = int8_t> ArrowErrorCode InitArrayViewFromBuffers(ArrowType type, ArrowArray* array, ArrowArrayView* array_view, std::vector<int8_t> validity, std::vector<Buffer1T> buffer1, std::vector<Buffer2T> buffer2 = {}) { // Initialize arrays nanoarrow::UniqueSchema schema; NANOARROW_RETURN_NOT_OK(ArrowSchemaInitFromType(schema.get(), type)); NANOARROW_RETURN_NOT_OK(ArrowArrayInitFromSchema(array, schema.get(), nullptr)); // Initialize buffers NANOARROW_RETURN_NOT_OK(ArrowBufferAppend(ArrowArrayBuffer(array, 1), buffer1.data(), buffer1.size() * sizeof(Buffer1T))); NANOARROW_RETURN_NOT_OK(ArrowBufferAppend(ArrowArrayBuffer(array, 2), buffer2.data(), buffer2.size() * sizeof(Buffer2T))); // Pack the validity bitmap if (validity.size() > 0) { ArrowBitmap* validity_bitmap = ArrowArrayValidityBitmap(array); NANOARROW_RETURN_NOT_OK(ArrowBitmapReserve(validity_bitmap, validity.size())); ArrowBitmapAppendInt8Unsafe(validity_bitmap, validity.data(), validity.size()); } // Set the length switch (type) { case NANOARROW_TYPE_STRING: case NANOARROW_TYPE_LARGE_STRING: case NANOARROW_TYPE_BINARY: case NANOARROW_TYPE_LARGE_BINARY: if (buffer1.size() > 0) { array->length = buffer1.size() - 1; } else { array->length = 0; } break; default: array->length = buffer1.size(); break; } // Set the null count if (validity.size() > 0) { array->null_count = array->length - ArrowBitCountSet(ArrowArrayBuffer(array, 0)->data, 0, array->length); } else { array->null_count = 0; } NANOARROW_RETURN_NOT_OK(ArrowArrayFinishBuildingDefault(array, nullptr)); NANOARROW_RETURN_NOT_OK( ArrowArrayViewInitFromSchema(array_view, schema.get(), nullptr)); NANOARROW_RETURN_NOT_OK(ArrowArrayViewSetArray(array_view, array, nullptr)); return NANOARROW_OK; } template <typename CType, ArrowType type> static void BaseArrayViewGetInt(benchmark::State& state) { nanoarrow::UniqueArray array; nanoarrow::UniqueArrayView array_view; int64_t n_values = kNumItemsPrettyBig; std::vector<CType> values(n_values); for (int64_t i = 0; i < n_values; i++) { values[i] = i % std::numeric_limits<CType>::max(); } NANOARROW_THROW_NOT_OK( InitArrayViewFromBuffers(type, array.get(), array_view.get(), {}, values)); std::vector<CType> values_out(n_values); for (auto _ : state) { for (int64_t i = 0; i < n_values; i++) { values_out[i] = ArrowArrayViewGetIntUnsafe(array_view.get(), i); } benchmark::DoNotOptimize(values_out); } state.SetItemsProcessed(n_values * state.iterations()); } /// \brief Use ArrowArrayViewGet() to consume an int8 array static void BenchmarkArrayViewGetInt8(benchmark::State& state) { BaseArrayViewGetInt<int8_t, NANOARROW_TYPE_INT8>(state); } /// \brief Use ArrowArrayViewGet() to consume an int16 array static void BenchmarkArrayViewGetInt16(benchmark::State& state) { BaseArrayViewGetInt<int16_t, NANOARROW_TYPE_INT16>(state); } /// \brief Use ArrowArrayViewGet() to consume an int32 array static void BenchmarkArrayViewGetInt32(benchmark::State& state) { BaseArrayViewGetInt<int32_t, NANOARROW_TYPE_INT32>(state); } /// \brief Use ArrowArrayViewGet() to consume an int64 array static void BenchmarkArrayViewGetInt64(benchmark::State& state) { BaseArrayViewGetInt<int64_t, NANOARROW_TYPE_INT64>(state); } /// \brief Use ArrowArrayViewIsNull() to check for nulls while consuming an int32 array /// that does not contain a validity buffer. static void BenchmarkArrayViewIsNullNonNullable(benchmark::State& state) { nanoarrow::UniqueArray array; nanoarrow::UniqueArrayView array_view; int64_t n_values = kNumItemsPrettyBig; // Create values std::vector<int32_t> values(n_values); for (int64_t i = 0; i < n_values; i++) { values[i] = i % 1000; } NANOARROW_THROW_NOT_OK(InitArrayViewFromBuffers(NANOARROW_TYPE_INT32, array.get(), array_view.get(), {}, values)); // Read the array std::vector<int32_t> values_out(n_values); for (auto _ : state) { for (int64_t i = 0; i < n_values; i++) { if (ArrowArrayViewIsNull(array_view.get(), i)) { values_out[i] = 0; } else { values_out[i] = ArrowArrayViewGetIntUnsafe(array_view.get(), i); } } benchmark::DoNotOptimize(values_out); } state.SetItemsProcessed(n_values * state.iterations()); } /// \brief Use ArrowArrayViewIsNull() to check for nulls while consuming an int32 array /// that contains 20% nulls. static void BenchmarkArrayViewIsNull(benchmark::State& state) { nanoarrow::UniqueArray array; nanoarrow::UniqueArrayView array_view; int64_t n_values = kNumItemsPrettyBig; // Create values std::vector<int32_t> values(n_values); for (int64_t i = 0; i < n_values; i++) { values[i] = i % 1000; } // Create validity buffer double prop_null = 0.2; int64_t num_nulls = n_values * prop_null; int64_t null_spacing = n_values / num_nulls; std::vector<int8_t> validity(n_values); for (int64_t i = 0; i < n_values; i++) { validity[i] = i % null_spacing != 0; } NANOARROW_THROW_NOT_OK(InitArrayViewFromBuffers(NANOARROW_TYPE_INT32, array.get(), array_view.get(), validity, values)); // Read the array std::vector<int32_t> values_out(n_values); for (auto _ : state) { for (int64_t i = 0; i < n_values; i++) { if (ArrowArrayViewIsNull(array_view.get(), i)) { values_out[i] = 0; } else { values_out[i] = ArrowArrayViewGetIntUnsafe(array_view.get(), i); } } benchmark::DoNotOptimize(values_out); } state.SetItemsProcessed(n_values * state.iterations()); } /// \brief Use ArrowArrayViewGetStringUnsafe() to consume a string array static void BenchmarkArrayViewGetString(benchmark::State& state) { nanoarrow::UniqueArray array; nanoarrow::UniqueArrayView array_view; // Create an array of relatively small strings int64_t n_values = kNumItemsPrettyBig; int64_t value_size = 7; std::string alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; std::vector<int32_t> offsets(n_values + 1); for (int64_t i = 0; i < n_values; i++) { offsets[i + 1] = i * value_size; } int64_t n_alphabets = n_values / alphabet.size() + 1; std::vector<char> data(alphabet.size() * n_alphabets); for (size_t data_pos = 0; data_pos < data.size(); data_pos += alphabet.size()) { memcpy(data.data() + data_pos, alphabet.data(), alphabet.size()); } // Read the array as non-copying views. Possibly less realistic than // what somebody might actually do, but also is a more direct benchmark // of the overhead associated with calling it. std::vector<ArrowStringView> values_out(n_values); for (auto _ : state) { for (int64_t i = 0; i < n_values; i++) { values_out[i] = ArrowArrayViewGetStringUnsafe(array_view.get(), i); } benchmark::DoNotOptimize(values_out); } state.SetItemsProcessed(n_values * state.iterations()); } /// @} /// \defgroup nanoarrow-benchmark-array ArrowArray-related benchmarks /// /// Benchmarks for producing ArrowArrays using the `ArrowArrayXXX()` functions. /// /// @{ template <typename CType, ArrowType type> static ArrowErrorCode CreateAndAppendToArrayInt(ArrowArray* array, const std::vector<CType>& values) { NANOARROW_RETURN_NOT_OK(ArrowArrayInitFromType(array, type)); NANOARROW_RETURN_NOT_OK(ArrowArrayStartAppending(array)); for (size_t i = 0; i < values.size(); i++) { NANOARROW_RETURN_NOT_OK(ArrowArrayAppendInt(array, values[i])); } NANOARROW_RETURN_NOT_OK(ArrowArrayFinishBuildingDefault(array, nullptr)); return NANOARROW_OK; } template <ArrowType type> static ArrowErrorCode CreateAndAppendToArrayString( ArrowArray* array, const std::vector<std::string>& values) { NANOARROW_RETURN_NOT_OK(ArrowArrayInitFromType(array, type)); NANOARROW_RETURN_NOT_OK(ArrowArrayStartAppending(array)); for (const std::string& s : values) { NANOARROW_RETURN_NOT_OK( ArrowArrayAppendString(array, {s.data(), static_cast<int64_t>(s.size())})); } NANOARROW_RETURN_NOT_OK(ArrowArrayFinishBuildingDefault(array, nullptr)); return NANOARROW_OK; } /// \brief Use ArrowArrayAppendString() to build a string array static void BenchmarkArrayAppendString(benchmark::State& state) { nanoarrow::UniqueArray array; int64_t n_values = kNumItemsPrettyBig; int64_t value_size = 7; std::vector<std::string> values(n_values); size_t alphabet_pos = 0; for (std::string& value : values) { if ((alphabet_pos + value_size) >= kAlphabet.size()) { alphabet_pos = 0; } value.assign(kAlphabet.data() + alphabet_pos, value_size); alphabet_pos += value_size; } for (auto _ : state) { array.reset(); NANOARROW_THROW_NOT_OK( CreateAndAppendToArrayString<NANOARROW_TYPE_STRING>(array.get(), values)); benchmark::DoNotOptimize(array); } state.SetItemsProcessed(n_values * state.iterations()); } template <typename CType, ArrowType type> static void BaseBenchmarkArrayAppendInt(benchmark::State& state) { nanoarrow::UniqueArray array; int64_t n_values = kNumItemsPrettyBig; std::vector<CType> values(n_values); for (int64_t i = 0; i < n_values; i++) { values[i] = i % std::numeric_limits<CType>::max(); } for (auto _ : state) { array.reset(); int code = CreateAndAppendToArrayInt<CType, type>(array.get(), values); NANOARROW_THROW_NOT_OK(code); benchmark::DoNotOptimize(array); } state.SetItemsProcessed(n_values * state.iterations()); } /// \brief Use ArrowArrayAppendInt() to build an int8 array static void BenchmarkArrayAppendInt8(benchmark::State& state) { BaseBenchmarkArrayAppendInt<int8_t, NANOARROW_TYPE_INT8>(state); } /// \brief Use ArrowArrayAppendInt() to build an int16 array static void BenchmarkArrayAppendInt16(benchmark::State& state) { BaseBenchmarkArrayAppendInt<int16_t, NANOARROW_TYPE_INT16>(state); } /// \brief Use ArrowArrayAppendInt() to build an int32 array static void BenchmarkArrayAppendInt32(benchmark::State& state) { BaseBenchmarkArrayAppendInt<int32_t, NANOARROW_TYPE_INT32>(state); } /// \brief Use ArrowArrayAppendInt() to build an int64 array static void BenchmarkArrayAppendInt64(benchmark::State& state) { BaseBenchmarkArrayAppendInt<int64_t, NANOARROW_TYPE_INT64>(state); } template <typename CType, ArrowType type> static ArrowErrorCode CreateAndAppendIntWithNulls(ArrowArray* array, const std::vector<int8_t>& validity) { NANOARROW_RETURN_NOT_OK(ArrowArrayInitFromType(array, type)); NANOARROW_RETURN_NOT_OK(ArrowArrayStartAppending(array)); CType non_null_value = std::numeric_limits<CType>::max() / 2; for (size_t i = 0; i < validity.size(); i++) { if (validity[i]) { NANOARROW_RETURN_NOT_OK(ArrowArrayAppendInt(array, non_null_value)); } else { NANOARROW_RETURN_NOT_OK(ArrowArrayAppendNull(array, 1)); } } NANOARROW_RETURN_NOT_OK(ArrowArrayFinishBuildingDefault(array, nullptr)); return NANOARROW_OK; } /// \brief Use ArrowArrayAppendNulls() to build an int32 array that contains 80% /// null values static void BenchmarkArrayAppendNulls(benchmark::State& state) { nanoarrow::UniqueArray array; int64_t n_values = kNumItemsPrettyBig; double prop_null = 0.8; int64_t num_nulls = n_values * prop_null; int64_t null_spacing = n_values / num_nulls; std::vector<int8_t> validity(n_values); for (int64_t i = 0; i < n_values; i++) { validity[i] = i % null_spacing != 0; } for (auto _ : state) { array.reset(); int code = CreateAndAppendIntWithNulls<int32_t, NANOARROW_TYPE_INT32>(array.get(), validity); NANOARROW_THROW_NOT_OK(code); benchmark::DoNotOptimize(array); } state.SetItemsProcessed(n_values * state.iterations()); } /// @} BENCHMARK(BenchmarkArrayViewGetInt8); BENCHMARK(BenchmarkArrayViewGetInt16); BENCHMARK(BenchmarkArrayViewGetInt32); BENCHMARK(BenchmarkArrayViewGetInt64); BENCHMARK(BenchmarkArrayViewGetString); BENCHMARK(BenchmarkArrayViewIsNullNonNullable); BENCHMARK(BenchmarkArrayViewIsNull); BENCHMARK(BenchmarkArrayAppendString); BENCHMARK(BenchmarkArrayAppendInt8); BENCHMARK(BenchmarkArrayAppendInt16); BENCHMARK(BenchmarkArrayAppendInt32); BENCHMARK(BenchmarkArrayAppendInt64); BENCHMARK(BenchmarkArrayAppendNulls); BENCHMARK_MAIN();