Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 233 additions & 0 deletions be/benchmark/benchmark_binary_plain_page_v2.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
// 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 <benchmark/benchmark.h>

#include <cstdint>
#include <cstring>
#include <memory>
#include <random>
#include <string>
#include <vector>

#include "storage/cache/page_cache.h"
#include "storage/segment/binary_plain_page_v2.h"
#include "storage/segment/binary_plain_page_v2_pre_decoder.h"
#include "storage/segment/binary_plain_page_v3.h"
#include "storage/segment/binary_plain_page_v3_pre_decoder.h"
#include "storage/segment/options.h"
#include "storage/segment/page_builder.h"
#include "storage/types.h"
#include "util/slice.h"

namespace doris {
namespace segment_v2 {

// Build a fixed corpus of strings of `value_len` bytes each. We reuse the same
// corpus across V2 and V3 so the only variable left is the on-disk layout. A
// deterministic RNG keeps results comparable across runs.
inline std::vector<std::string> make_corpus(size_t num_elems, size_t value_len) {
std::mt19937 rng(0xC0FFEEu);
std::uniform_int_distribution<int> dist('a', 'z');
std::vector<std::string> corpus;
corpus.reserve(num_elems);
for (size_t i = 0; i < num_elems; ++i) {
std::string s(value_len, '\0');
for (size_t j = 0; j < value_len; ++j) {
s[j] = static_cast<char>(dist(rng));
}
corpus.emplace_back(std::move(s));
}
return corpus;
}

template <template <FieldType> class BuilderT>
inline OwnedSlice build_page(const std::vector<std::string>& corpus) {
std::vector<Slice> slices;
slices.reserve(corpus.size());
for (const auto& s : corpus) {
slices.emplace_back(s);
}

PageBuilderOptions opts;
// Disable the size-bound check so the whole corpus lands in one page.
opts.data_page_size = 0;
opts.dict_page_size = 0;

PageBuilder* raw = nullptr;
Status st = BuilderT<FieldType::OLAP_FIELD_TYPE_VARCHAR>::create(&raw, opts);
CHECK(st.ok()) << st;
std::unique_ptr<PageBuilder> builder(raw);

size_t count = slices.size();
st = builder->add(reinterpret_cast<const uint8_t*>(slices.data()), &count);
CHECK(st.ok()) << st;
CHECK_EQ(count, slices.size());

OwnedSlice out;
st = builder->finish(&out);
CHECK(st.ok()) << st;
return out;
}

// Per-fixture: build the input page once outside the timed loop, then in each
// iteration restore the input Slice (since decode() rewrites it to point at the
// freshly-allocated V1 page) and measure only the decode call. We also report
// per-element throughput so V2 vs V3 are easy to compare across (N, len).
template <template <FieldType> class BuilderT, class PreDecoderT>
inline void run_pre_decode_bm(benchmark::State& state) {
const size_t num_elems = static_cast<size_t>(state.range(0));
const size_t value_len = static_cast<size_t>(state.range(1));

auto corpus = make_corpus(num_elems, value_len);
OwnedSlice owned = build_page<BuilderT>(corpus);
const Slice original = owned.slice();

PreDecoderT pre_decoder;

for (auto _ : state) {
Slice page_slice = original;
std::unique_ptr<DataPage> decoded_page;
Status st = pre_decoder.decode(&decoded_page, &page_slice, /*size_of_tail=*/0,
/*use_cache=*/false, PageTypePB::DATA_PAGE,
/*file_path=*/std::string());
CHECK(st.ok()) << st;
benchmark::DoNotOptimize(page_slice);
benchmark::DoNotOptimize(decoded_page);
benchmark::ClobberMemory();
}

state.SetItemsProcessed(static_cast<int64_t>(state.iterations()) *
static_cast<int64_t>(num_elems));
state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) *
static_cast<int64_t>(original.size));
state.counters["page_bytes"] = static_cast<double>(original.size);
state.counters["ns_per_elem"] = benchmark::Counter(
static_cast<double>(num_elems),
benchmark::Counter::kIsIterationInvariantRate | benchmark::Counter::kInvert);
}

inline void BM_BinaryPlainPageV2_PreDecode(benchmark::State& state) {
run_pre_decode_bm<BinaryPlainPageV2Builder, BinaryPlainPageV2PreDecoder>(state);
}

inline void BM_BinaryPlainPageV3_PreDecode(benchmark::State& state) {
run_pre_decode_bm<BinaryPlainPageV3Builder, BinaryPlainPageV3PreDecoder>(state);
}

// (num_elems, value_len) grid. Picks representative sizes: a "many small"
// case (8 byte values like compact dictionary keys), a typical varchar case
// (32, 128 bytes), and a "large value" case (1024 bytes). num_elems range
// covers small / medium / page-sized pages.
static void V2V3PreDecodeArgs(benchmark::internal::Benchmark* b) {
for (int n : {256, 1024, 4096, 16384}) {
for (int len : {8, 32, 128, 1024}) {
b->Args({n, len});
}
}
}

BENCHMARK(BM_BinaryPlainPageV2_PreDecode)->Apply(V2V3PreDecodeArgs);
BENCHMARK(BM_BinaryPlainPageV3_PreDecode)->Apply(V2V3PreDecodeArgs);

// ---------------------------------------------------------------------------
// Fixed-page-size variants: pin to a production page size so we measure
// pre-decode cost at realistic byte counts. num_elems is derived from
// value_len so each input page lands at ~target_bytes.
// - 64 KiB matches STORAGE_PAGE_SIZE_DEFAULT_VALUE (default data page)
// - 256 KiB matches STORAGE_DICT_PAGE_SIZE_DEFAULT_VALUE (dict / large)
// ---------------------------------------------------------------------------

inline constexpr size_t kBenchPage64KiB = 64 * 1024;
inline constexpr size_t kBenchPage256KiB = 256 * 1024;

// Pick num_elems so that (varint_len + value_len) * num_elems ~= target_bytes.
// Varint cost: 1 byte for value_len < 128, 2 bytes for value_len < 16384.
inline size_t elems_for_target(size_t target_bytes, size_t value_len) {
const size_t varint_bytes = value_len < 128 ? 1 : 2;
const size_t per_entry = varint_bytes + value_len;
return target_bytes / per_entry;
}

template <template <FieldType> class BuilderT, class PreDecoderT, size_t TargetBytes>
inline void run_pre_decode_fixed_page_bm(benchmark::State& state) {
const size_t value_len = static_cast<size_t>(state.range(0));
const size_t num_elems = elems_for_target(TargetBytes, value_len);

auto corpus = make_corpus(num_elems, value_len);
OwnedSlice owned = build_page<BuilderT>(corpus);
const Slice original = owned.slice();

PreDecoderT pre_decoder;
for (auto _ : state) {
Slice page_slice = original;
std::unique_ptr<DataPage> decoded_page;
Status st = pre_decoder.decode(&decoded_page, &page_slice, /*size_of_tail=*/0,
/*use_cache=*/false, PageTypePB::DATA_PAGE,
/*file_path=*/std::string());
CHECK(st.ok()) << st;
benchmark::DoNotOptimize(page_slice);
benchmark::DoNotOptimize(decoded_page);
benchmark::ClobberMemory();
}

state.SetItemsProcessed(static_cast<int64_t>(state.iterations()) *
static_cast<int64_t>(num_elems));
state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) *
static_cast<int64_t>(original.size));
state.counters["num_elems"] = static_cast<double>(num_elems);
state.counters["page_bytes"] = static_cast<double>(original.size);
state.counters["ns_per_elem"] = benchmark::Counter(
static_cast<double>(num_elems),
benchmark::Counter::kIsIterationInvariantRate | benchmark::Counter::kInvert);
}

inline void BM_BinaryPlainPageV2_PreDecode_64KiB(benchmark::State& state) {
run_pre_decode_fixed_page_bm<BinaryPlainPageV2Builder, BinaryPlainPageV2PreDecoder,
kBenchPage64KiB>(state);
}
inline void BM_BinaryPlainPageV3_PreDecode_64KiB(benchmark::State& state) {
run_pre_decode_fixed_page_bm<BinaryPlainPageV3Builder, BinaryPlainPageV3PreDecoder,
kBenchPage64KiB>(state);
}
inline void BM_BinaryPlainPageV2_PreDecode_256KiB(benchmark::State& state) {
run_pre_decode_fixed_page_bm<BinaryPlainPageV2Builder, BinaryPlainPageV2PreDecoder,
kBenchPage256KiB>(state);
}
inline void BM_BinaryPlainPageV3_PreDecode_256KiB(benchmark::State& state) {
run_pre_decode_fixed_page_bm<BinaryPlainPageV3Builder, BinaryPlainPageV3PreDecoder,
kBenchPage256KiB>(state);
}

// value_len sweep. Each row produces a page near the target byte count;
// num_elems is reported via the `num_elems` counter so the (N, len)
// relationship is visible alongside the timing.
static void V2V3PreDecodeFixedPageArgs(benchmark::internal::Benchmark* b) {
for (int len : {8, 16, 32, 64, 128, 256, 512, 1024, 4096}) {
b->Args({len});
}
}

BENCHMARK(BM_BinaryPlainPageV2_PreDecode_64KiB)->Apply(V2V3PreDecodeFixedPageArgs);
BENCHMARK(BM_BinaryPlainPageV3_PreDecode_64KiB)->Apply(V2V3PreDecodeFixedPageArgs);
BENCHMARK(BM_BinaryPlainPageV2_PreDecode_256KiB)->Apply(V2V3PreDecodeFixedPageArgs);
BENCHMARK(BM_BinaryPlainPageV3_PreDecode_256KiB)->Apply(V2V3PreDecodeFixedPageArgs);

} // namespace segment_v2
} // namespace doris
34 changes: 32 additions & 2 deletions be/benchmark/benchmark_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,22 @@
#include "benchmark_string.hpp"
#include "benchmark_string_replace.hpp"
#include "benchmark_zone_map_index.hpp"
#include "binary_cast_benchmark.hpp"
#include "runtime/exec_env.h"
#include "runtime/memory/mem_tracker_limiter.h"
#include "runtime/memory/thread_mem_tracker_mgr.h"
#include "runtime/thread_context.h"
// binary_cast_benchmark.hpp references TypeConverter / *Union types that no
// longer exist in the tree; including it breaks the benchmark target. Disable
// for now so the rest of the suite compiles.
// #include "binary_cast_benchmark.hpp"
#include "core/block/block.h"
#include "core/column/column_string.h"
#include "core/data_type/data_type.h"
#include "core/data_type/data_type_string.h"
// Keep our hpp last: it transitively pulls AWS SDK headers via
// storage/cache/page_cache.h that shadow `TypeConverter` / fragile types used
// by binary_cast_benchmark.hpp when placed before it.
#include "benchmark_binary_plain_page_v2.hpp"

namespace doris { // change if need

Expand All @@ -57,4 +68,23 @@ static void Example1(benchmark::State& state) {
BENCHMARK(Example1);
} // namespace doris

BENCHMARK_MAIN();
// Custom main: benchmarks that touch DataPage allocation require a Doris
// ThreadContext + mem tracker, otherwise the allocator throws E-7412. Mirrors
// the minimal subset of be/test/testutil/run_all_tests.cpp::main.
int main(int argc, char** argv) {
SCOPED_INIT_THREAD_CONTEXT();
doris::ExecEnv::GetInstance()->init_mem_tracker();
doris::thread_context()->thread_mem_tracker_mgr->init();
auto bench_tracker = doris::MemTrackerLimiter::create_shared(
doris::MemTrackerLimiter::Type::GLOBAL, "BE-BENCH");
doris::thread_context()->thread_mem_tracker_mgr->attach_limiter_tracker(bench_tracker);
doris::ExecEnv::set_tracking_memory(false);

::benchmark::Initialize(&argc, argv);
if (::benchmark::ReportUnrecognizedArguments(argc, argv)) {
return 1;
}
::benchmark::RunSpecifiedBenchmarks();
::benchmark::Shutdown();
return 0;
}
30 changes: 20 additions & 10 deletions be/src/storage/segment/binary_dict_page.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,32 @@ struct StringRef;

namespace segment_v2 {

namespace {
// Translate the schema-level BinaryPlainEncodingTypePB preference into the
// concrete on-disk EncodingTypePB used by both the dict word page and the
// fallback (non-dict) binary page builder.
EncodingTypePB binary_plain_encoding_from_pref(BinaryPlainEncodingTypePB pref) {
switch (pref) {
case BinaryPlainEncodingTypePB::BINARY_PLAIN_ENCODING_V3:
return PLAIN_ENCODING_V3;
case BinaryPlainEncodingTypePB::BINARY_PLAIN_ENCODING_V2:
return PLAIN_ENCODING_V2;
default:
return PLAIN_ENCODING;
}
}
} // namespace

BinaryDictPageBuilder::BinaryDictPageBuilder(const PageBuilderOptions& options)
: _options(options),
_finished(false),
_data_page_builder(nullptr),
_dict_builder(nullptr),
_encoding_type(DICT_ENCODING),
_dict_word_page_encoding_type(
options.encoding_preference.binary_plain_encoding_default_impl ==
BinaryPlainEncodingTypePB::BINARY_PLAIN_ENCODING_V2
? PLAIN_ENCODING_V2
: PLAIN_ENCODING),
_fallback_binary_encoding_type(
options.encoding_preference.binary_plain_encoding_default_impl ==
BinaryPlainEncodingTypePB::BINARY_PLAIN_ENCODING_V2
? PLAIN_ENCODING_V2
: PLAIN_ENCODING) {}
_dict_word_page_encoding_type(binary_plain_encoding_from_pref(
options.encoding_preference.binary_plain_encoding_default_impl)),
_fallback_binary_encoding_type(binary_plain_encoding_from_pref(
options.encoding_preference.binary_plain_encoding_default_impl)) {}

Status BinaryDictPageBuilder::init() {
// initially use DICT_ENCODING
Expand Down
Loading
Loading