From 7bf43e9a8835ef83e3bcfd5e416eb8b1609f2100 Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt <952130278@qq.com> Date: Fri, 20 Aug 2021 19:05:32 +0800 Subject: [PATCH 1/6] add benchmark_tool trans benchmark_tool to unit test fix eof remove unused code and file add SegmentWrite&& support multi benchmark test add PageEncodeBenchmark remove time count at first turn run && move some util fuction to test_util&& add benchmark name display add MultiBenchmark to manage benchmarks add SegmentWriteByFileBenchmark add SegmentScanByFileBenchmark add ignore case compare add benchmark include build promote usage --- be/CMakeLists.txt | 5 + be/test/olap/tablet_schema_helper.h | 24 ++ be/test/test_util/test_util.cpp | 39 +- be/test/test_util/test_util.h | 11 + be/test/tools/CMakeLists.txt | 23 ++ be/test/tools/benchmark_tool.cpp | 571 ++++++++++++++++++++++++++++ run-be-ut.sh | 2 +- thirdparty/build-thirdparty.sh | 20 + thirdparty/patched_mark | 0 thirdparty/vars.sh | 10 +- 10 files changed, 702 insertions(+), 3 deletions(-) create mode 100644 be/test/tools/CMakeLists.txt create mode 100644 be/test/tools/benchmark_tool.cpp create mode 100644 thirdparty/patched_mark diff --git a/be/CMakeLists.txt b/be/CMakeLists.txt index 49df7889bdb93f..29a6b176044f4a 100644 --- a/be/CMakeLists.txt +++ b/be/CMakeLists.txt @@ -170,6 +170,9 @@ set_target_properties(protoc PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib/ add_library(gtest STATIC IMPORTED) set_target_properties(gtest PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib/libgtest.a) +add_library(benchmark STATIC IMPORTED) +set_target_properties(benchmark PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib/libbenchmark.a) + add_library(gmock STATIC IMPORTED) set_target_properties(gmock PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib/libgmock.a) @@ -500,6 +503,7 @@ set(COMMON_THIRDPARTY gflags brpc protobuf + benchmark openssl crypto leveldb @@ -694,6 +698,7 @@ if (${MAKE_TEST} STREQUAL "ON") add_subdirectory(${TEST_DIR}/util) add_subdirectory(${TEST_DIR}/plugin) add_subdirectory(${TEST_DIR}/plugin/example) + add_subdirectory(${TEST_DIR}/tools) endif () # Install be diff --git a/be/test/olap/tablet_schema_helper.h b/be/test/olap/tablet_schema_helper.h index 66c83fcf9e73c0..4a069828c024ff 100644 --- a/be/test/olap/tablet_schema_helper.h +++ b/be/test/olap/tablet_schema_helper.h @@ -123,4 +123,28 @@ void set_column_value_by_type(FieldType fieldType, int src, char* target, MemPoo } } +void set_column_value_by_type(FieldType fieldType, std::string src, char* target, MemPool* pool, + size_t _length = 0) { + if (fieldType == OLAP_FIELD_TYPE_CHAR) { + char* src_value = &src[0]; + int src_len = src.size(); + + auto* dest_slice = (Slice*)target; + dest_slice->size = _length; + dest_slice->data = (char*)pool->allocate(dest_slice->size); + memcpy(dest_slice->data, src_value, src_len); + memset(dest_slice->data + src_len, 0, dest_slice->size - src_len); + } else if (fieldType == OLAP_FIELD_TYPE_VARCHAR) { + char* src_value = &src[0]; + int src_len = src.size(); + + auto* dest_slice = (Slice*)target; + dest_slice->size = src_len; + dest_slice->data = (char*)pool->allocate(src_len); + memcpy(dest_slice->data, src_value, src_len); + } else { + *(int*)target = std::stoi(src); + } +} + } // namespace doris diff --git a/be/test/test_util/test_util.cpp b/be/test/test_util/test_util.cpp index be118cd8cf1453..a275f32496b1cc 100644 --- a/be/test/test_util/test_util.cpp +++ b/be/test/test_util/test_util.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -74,4 +73,42 @@ void InitConfig() { } } +std::vector split_str(const std::string& str, char separation) { + std::vector tokens; + int pre = 0; + int now = 0; + for (char c : str) { + now++; + if (c == separation) { + tokens.emplace_back(str.substr(pre, now - pre - 1)); + pre = now; + } + } + if (pre < now) tokens.emplace_back(str.substr(pre, now - pre)); + return tokens; +} + +bool equal_ignore_case(std::string lhs, std::string rhs) { + std::transform(lhs.begin(), lhs.end(), lhs.begin(), ::tolower); + std::transform(rhs.begin(), rhs.end(), rhs.begin(), ::tolower); + return lhs == rhs; +} + +std::mt19937_64 rng_64(std::chrono::steady_clock::now().time_since_epoch().count()); + +int rand_rng_int(int l, int r) { + std::uniform_int_distribution u(l, r); + return u(rng_64); +} +char rand_rng_char() { + return (rand_rng_int(0, 1) ? 'a' : 'A') + rand_rng_int(0, 25); +} +std::string rand_rng_string(size_t length) { + string s; + while (length--) { + s += rand_rng_char(); + } + return s; +} + } // namespace doris diff --git a/be/test/test_util/test_util.h b/be/test/test_util/test_util.h index 738adb580ad0d3..74b94fce0f548a 100644 --- a/be/test/test_util/test_util.h +++ b/be/test/test_util/test_util.h @@ -17,6 +17,9 @@ #pragma once +#include +#include +#include #include namespace doris { @@ -36,4 +39,12 @@ std::string GetCurrentRunningDir(); // Initialize config file. void InitConfig(); +std::vector split_str(const std::string& str, char separation = ','); + +bool equal_ignore_case(std::string lhs, std::string rhs); + +int rand_rng_int(int l, int r); +char rand_rng_char(); +std::string rand_rng_string(size_t length = 8); + } // namespace doris diff --git a/be/test/tools/CMakeLists.txt b/be/test/tools/CMakeLists.txt new file mode 100644 index 00000000000000..2389387704663f --- /dev/null +++ b/be/test/tools/CMakeLists.txt @@ -0,0 +1,23 @@ +# 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. + +# where to put generated libraries +set(EXECUTABLE_OUTPUT_PATH "${BUILD_DIR}/test/env") +# where to put generated binaries +set(EXECUTABLE_OUTPUT_PATH "${BUILD_DIR}/test/tools") + +ADD_BE_TEST(benchmark_tool) diff --git a/be/test/tools/benchmark_tool.cpp b/be/test/tools/benchmark_tool.cpp new file mode 100644 index 00000000000000..2f717dd7c31362 --- /dev/null +++ b/be/test/tools/benchmark_tool.cpp @@ -0,0 +1,571 @@ +// 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/compiler_util.h" +#include "common/logging.h" +#include "gutil/strings/substitute.h" +#include "olap/comparison_predicate.h" +#include "olap/fs/block_manager.h" +#include "olap/fs/fs_util.h" +#include "olap/in_list_predicate.h" +#include "olap/olap_common.h" +#include "olap/row_block2.h" +#include "olap/row_cursor.h" +#include "olap/rowset/segment_v2/binary_dict_page.h" +#include "olap/rowset/segment_v2/binary_plain_page.h" +#include "olap/rowset/segment_v2/page_builder.h" +#include "olap/rowset/segment_v2/page_decoder.h" +#include "olap/rowset/segment_v2/segment_iterator.h" +#include "olap/rowset/segment_v2/segment_writer.h" +#include "olap/tablet_schema.h" +#include "olap/tablet_schema_helper.h" +#include "olap/types.h" +#include "runtime/mem_pool.h" +#include "runtime/mem_tracker.h" +#include "test_util/test_util.h" +#include "util/debug_util.h" +#include "util/file_utils.h" + +DEFINE_string(operation, "Custom", + "valid operation: Custom, BinaryDictPageEncode, SegmentScan, SegmentWrite, " + "SegmentScanByFile, SegmentWriteByFile"); +DEFINE_string(input_file, "./sample.dat", "input file directory"); +DEFINE_string(column_type, "int,varchar", "valid type: int, char, varchar"); +DEFINE_string(rows_number, "10000", "rows number"); +DEFINE_string(iterations, "10", + "run times, this is set to 0 means the number of iterations is automatically set "); + +const std::string kSegmentDir = "./segment_benchmark"; + +std::string get_usage(const std::string& progname) { + std::stringstream ss; + ss << progname << " is the Doris BE benchmark tool.\n"; + ss << "Stop BE first before use this tool.\n"; + + ss << "Usage:\n"; + ss << "./benchmark_tool --operation=Custom\n"; + ss << "./benchmark_tool --operation=BinaryDictPageEncode " + "--rows_number=10000 --iterations=40\n"; + ss << "./benchmark_tool --operation=SegmentScan --column_type=int,varchar " + "--rows_number=10000 --iterations=0\n"; + ss << "./benchmark_tool --operation=SegmentWrite --column_type=int " + "--rows_number=10000 --iterations=10\n"; + ss << "./benchmark_tool --operation=SegmentScanByFile --input_file=./sample.dat " + "--iterations=10\n"; + ss << "./benchmark_tool --operation=SegmentWriteByFile --input_file=./sample.dat " + "--iterations=10\n"; + + ss << "Sampe data file format: \n" + << "The first line defines Shcema\n" + << "The rest of the content is DataSet\n" + << "For example: \n" + << "int,char,varchar\n" + << "123,hello,world\n" + << "321,good,bye\n"; + return ss.str(); +} + +static int seg_id = 0; + +namespace doris { +class BaseBenchmark { +public: + BaseBenchmark(std::string name, int iterations) : _name(name), _iterations(iterations) {} + virtual ~BaseBenchmark() {}; + + void add_name(std::string str) { _name += str; } + + virtual void init() {}; + virtual void run() {}; + + void register_bm() { + auto bm = benchmark::RegisterBenchmark(_name.c_str(), [&](benchmark::State& state) { + //first turn will use more time + this->init(); + this->run(); + for (auto _ : state) { + state.PauseTiming(); + this->init(); + state.ResumeTiming(); + this->run(); + } + }); + if (_iterations != 0) { + bm->Iterations(_iterations); + } + bm->Unit(benchmark::kMillisecond); + } + +private: + std::string _name; + int _iterations; +}; + +class BinaryDictPageBenchmark : public BaseBenchmark { +public: + BinaryDictPageBenchmark(std::string name, int iterations) : BaseBenchmark(name, iterations) {} + virtual ~BinaryDictPageBenchmark() override {}; + + virtual void init() override {}; + virtual void run() override {}; + + std::vector encode_pages(const std::vector& contents) { + PageBuilderOptions options; + BinaryDictPageBuilder page_builder(options); + + std::vector results; + for (size_t i = 0; i < contents.size(); i++) { + const Slice* ptr = &contents[i]; + size_t add_num = 1; + Status ret = page_builder.add(reinterpret_cast(ptr), &add_num); + if (page_builder.is_page_full()) { + OwnedSlice s = page_builder.finish(); + results.emplace_back(std::move(s)); + page_builder.reset(); + } + } + OwnedSlice s = page_builder.finish(); + results.emplace_back(std::move(s)); + + return results; + } +}; + +class BinaryDictPageEncodeBenchmark : public BinaryDictPageBenchmark { +public: + BinaryDictPageEncodeBenchmark(std::string name, int iterations, int rows_number) + : BinaryDictPageBenchmark(name + "/rows_number:" + std::to_string(rows_number), + iterations), + _rows_number(rows_number) {} + virtual ~BinaryDictPageEncodeBenchmark() override {}; + + virtual void init() override { + src_strings.clear(); + for (int i = 0; i < _rows_number; i++) { + src_strings.emplace_back(rand_rng_string(rand_rng_int(1, 8))); + } + + slices.clear(); + for (auto s : src_strings) { + slices.emplace_back(s.c_str()); + } + }; + virtual void run() override { auto p = encode_pages(slices); }; + +private: + std::vector slices; + std::vector src_strings; + int _rows_number; +}; + +class SegmentBenchmark : public BaseBenchmark { +public: + SegmentBenchmark(std::string name, int iterations, std::string column_type) + : BaseBenchmark(name, iterations), + _tracker(std::make_shared()), + _pool(_tracker.get()) { + if (FileUtils::check_exist(kSegmentDir)) { + FileUtils::remove_all(kSegmentDir); + } + FileUtils::create_dir(kSegmentDir); + + init_schema(column_type); + } + SegmentBenchmark(std::string name, int iterations) + : BaseBenchmark(name, iterations), + _tracker(std::make_shared()), + _pool(_tracker.get()) { + if (FileUtils::check_exist(kSegmentDir)) { + FileUtils::remove_all(kSegmentDir); + } + FileUtils::create_dir(kSegmentDir); + } + virtual ~SegmentBenchmark() override { + if (FileUtils::check_exist(kSegmentDir)) { + FileUtils::remove_all(kSegmentDir); + } + } + + const Schema& get_schema() { return *_schema.get(); } + + virtual void init() override {}; + virtual void run() override {}; + + void init_schema(std::string column_type) { + std::string column_valid = "/column_type:"; + + auto tokens = split_str(column_type); + std::vector columns; + + bool first_column = true; + for (auto token : tokens) { + bool valid = true; + + if (equal_ignore_case(token, "int")) { + columns.emplace_back(create_int_key(columns.size() + 1)); + } else if (equal_ignore_case(token, "varchar")) { + columns.emplace_back(create_varchar_key(columns.size() + 1)); + } else if (equal_ignore_case(token, "char")) { + columns.emplace_back(create_char_key(columns.size() + 1)); + } else { + valid = false; + } + + if (valid) { + if (first_column) { + first_column = false; + } else { + column_valid += ','; + } + column_valid += token; + } + } + + _tablet_schema = _create_schema(columns); + _schema = std::make_shared(_tablet_schema); + + add_name(column_valid); + } + + void build_segment(size_t nrows, std::shared_ptr* res) { + // must use unique filename for each segment, otherwise page cache kicks in and produces + // the wrong answer (it use (filename,offset) as cache key) + std::string filename = strings::Substitute("$0/seg_$1.dat", kSegmentDir, ++seg_id); + std::unique_ptr wblock; + fs::CreateBlockOptions block_opts({filename}); + fs::fs_util::block_manager()->create_block(block_opts, &wblock); + SegmentWriterOptions opts; + SegmentWriter writer(wblock.get(), 0, &_tablet_schema, opts); + writer.init(1024); + + RowCursor row; + row.init(_tablet_schema); + + for (size_t rid = 0; rid < nrows; ++rid) { + for (int cid = 0; cid < _tablet_schema.num_columns(); ++cid) { + RowCursorCell cell = row.cell(cid); + set_column_value_by_type(_tablet_schema._cols[cid]._type, rid * 10 + cid, + (char*)cell.mutable_cell_ptr(), &_pool, + _tablet_schema._cols[cid]._length); + } + writer.append_row(row); + } + + uint64_t file_size, index_size; + writer.finalize(&file_size, &index_size); + wblock->close(); + + Segment::open(filename, seg_id, &_tablet_schema, res); + } + + void build_segment(std::vector> dataset, + std::shared_ptr* res) { + // must use unique filename for each segment, otherwise page cache kicks in and produces + // the wrong answer (it use (filename,offset) as cache key) + std::string filename = strings::Substitute("$0/seg_$1.dat", kSegmentDir, ++seg_id); + std::unique_ptr wblock; + fs::CreateBlockOptions block_opts({filename}); + fs::fs_util::block_manager()->create_block(block_opts, &wblock); + SegmentWriterOptions opts; + SegmentWriter writer(wblock.get(), 0, &_tablet_schema, opts); + writer.init(1024); + + RowCursor row; + row.init(_tablet_schema); + + for (auto tokens : dataset) { + for (int cid = 0; cid < _tablet_schema.num_columns(); ++cid) { + RowCursorCell cell = row.cell(cid); + set_column_value_by_type(_tablet_schema._cols[cid]._type, tokens[cid], + (char*)cell.mutable_cell_ptr(), &_pool, + _tablet_schema._cols[cid]._length); + } + writer.append_row(row); + } + + uint64_t file_size, index_size; + writer.finalize(&file_size, &index_size); + wblock->close(); + + Segment::open(filename, seg_id, &_tablet_schema, res); + } + +private: + TabletSchema _create_schema(const std::vector& columns, + int num_short_key_columns = -1) { + TabletSchema res; + int num_key_columns = 0; + for (auto& col : columns) { + if (col.is_key()) { + num_key_columns++; + } + res._cols.push_back(col); + } + res._num_columns = columns.size(); + res._num_key_columns = num_key_columns; + res._num_short_key_columns = + num_short_key_columns != -1 ? num_short_key_columns : num_key_columns; + res.init_field_index_for_test(); + return res; + } + + std::shared_ptr _tracker; + MemPool _pool; + TabletSchema _tablet_schema; + std::shared_ptr _schema; +}; // namespace doris + +class SegmentWriteBenchmark : public SegmentBenchmark { + SegmentWriteBenchmark(std::string name, int iterations, std::string column_type, + int rows_number) + : SegmentBenchmark(name + "/rows_number:" + std::to_string(rows_number), iterations, + column_type), + _rows_number(rows_number) {} + virtual ~SegmentWriteBenchmark() override {} + + virtual void init() override {} + virtual void run() override { build_segment(_rows_number, &_segment); }; + +private: + int _rows_number; + std::shared_ptr _segment; +}; + +class SegmentWriteByFileBenchmark : public SegmentBenchmark { + SegmentWriteByFileBenchmark(std::string name, int iterations, std::string file_str) + : SegmentBenchmark(name + "/file_path:" + file_str, iterations) { + std::ifstream file(file_str); + assert(file.is_open()); + + std::string column_type; + std::getline(file, column_type); + init_schema(column_type); + while (file.peek() != EOF) { + std::string row_str; + std::getline(file, row_str); + auto tokens = split_str(row_str); + assert(tokens.size() == _tablet_schema.num_columns()); + _dataset.push_back(tokens); + } + + add_name("/rows_number:" + std::to_string(_dataset.size())); + } + virtual ~SegmentWriteByFileBenchmark() override {} + + virtual void init() override {} + virtual void run() override { build_segment(_dataset, &_segment); }; + +private: + std::vector> _dataset; + std::shared_ptr _segment; +}; + +class SegmentScanBenchmark : public SegmentBenchmark { +public: + SegmentScanBenchmark(std::string name, int iterations, std::string column_type, int rows_number) + : SegmentBenchmark(name + "/rows_number:" + std::to_string(rows_number), iterations, + column_type), + _rows_number(rows_number) {} + virtual ~SegmentScanBenchmark() override {} + + virtual void init() override { build_segment(_rows_number, &_segment); } + virtual void run() override { + StorageReadOptions read_opts; + read_opts.stats = &stats; + std::unique_ptr iter; + _segment->new_iterator(get_schema(), read_opts, nullptr, &iter); + RowBlockV2 block(get_schema(), 1024); + + int left = _rows_number; + int rowid = 0; + while (left > 0) { + int rows_read = std::min(left, 1024); + block.clear(); + iter->next_batch(&block); + left -= rows_read; + rowid += rows_read; + } + }; + +private: + int _rows_number; + std::shared_ptr _segment; + OlapReaderStatistics stats; +}; + +class SegmentScanByFileBenchmark : public SegmentBenchmark { +public: + SegmentScanByFileBenchmark(std::string name, int iterations, std::string file_str) + : SegmentBenchmark(name, iterations) { + std::ifstream file(file_str); + assert(file.is_open()); + + std::string column_type; + std::getline(file, column_type); + init_schema(column_type); + while (file.peek() != EOF) { + std::string row_str; + std::getline(file, row_str); + auto tokens = split_str(row_str); + assert(tokens.size() == _tablet_schema.num_columns()); + _dataset.push_back(tokens); + } + + add_name("/rows_number:" + std::to_string(_dataset.size())); + } + virtual ~SegmentScanByFileBenchmark() override {} + + virtual void init() override { build_segment(_dataset, &_segment); } + virtual void run() override { + StorageReadOptions read_opts; + read_opts.stats = &stats; + std::unique_ptr iter; + _segment->new_iterator(get_schema(), read_opts, nullptr, &iter); + RowBlockV2 block(get_schema(), 1024); + + int left = _dataset.size(); + int rowid = 0; + while (left > 0) { + int rows_read = std::min(left, 1024); + block.clear(); + iter->next_batch(&block); + left -= rows_read; + rowid += rows_read; + } + }; + +private: + std::vector> _dataset; + std::shared_ptr _segment; + OlapReaderStatistics stats; +}; + +// This is sample custom test. User can write custom test code at custom_init()&custom_run(). +// Call method: ./benchmark_tool --operation=Custom +class CustomBenchmark : public BaseBenchmark { +public: + CustomBenchmark(std::string name, int iterations, std::function init_func, + std::function run_func) + : BaseBenchmark(name, iterations), _init_func(init_func), _run_func(run_func) {} + virtual ~CustomBenchmark() override {} + + virtual void init() override { _init_func(); } + virtual void run() override { _run_func(); } + +private: + std::function _init_func; + std::function _run_func; +}; +void custom_init() {} +void custom_run_plus() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (UNLIKELY(q == 1024)) q = 0; + } +} +void custom_run_mod() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (q %= 1024) q = 0; + } +} + +class MultiBenchmark { +public: + MultiBenchmark() {}; + ~MultiBenchmark() { + for (auto bm : benchmarks) { + delete bm; + } + } + + void add_bm() { + if (equal_ignore_case(FLAGS_operation, "Custom")) { + benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_plus", std::stoi(FLAGS_iterations), + doris::custom_init, doris::custom_run_plus)); + benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_mod", std::stoi(FLAGS_iterations), + doris::custom_init, doris::custom_run_mod)); + } else if (equal_ignore_case(FLAGS_operation, "BinaryDictPageEncode")) { + benchmarks.emplace_back(new doris::BinaryDictPageEncodeBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), std::stoi(FLAGS_rows_number))); + } else if (equal_ignore_case(FLAGS_operation, "SegmentScan")) { + benchmarks.emplace_back(new doris::SegmentScanBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), FLAGS_column_type, + std::stoi(FLAGS_rows_number))); + } else if (equal_ignore_case(FLAGS_operation, "SegmentWrite")) { + benchmarks.emplace_back(new doris::SegmentWriteBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), FLAGS_column_type, + std::stoi(FLAGS_rows_number))); + } else if (equal_ignore_case(FLAGS_operation, "SegmentScanByFile")) { + benchmarks.emplace_back(new doris::SegmentScanByFileBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), FLAGS_input_file)); + } else if (equal_ignore_case(FLAGS_operation, "SegmentWriteByFile")) { + benchmarks.emplace_back(new doris::SegmentWriteByFileBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), FLAGS_input_file)); + } else { + std::cout << "operation invalid!" << std::endl; + } + } + void register_bm() { + for (auto bm : benchmarks) { + bm->register_bm(); + } + } + +private: + std::vector benchmarks; +}; + +} //namespace doris + +int main(int argc, char** argv) { + std::string usage = get_usage(argv[0]); + gflags::SetUsageMessage(usage); + google::ParseCommandLineFlags(&argc, &argv, true); + + doris::StoragePageCache::create_global_cache(1 << 30, 0.1); + + doris::MultiBenchmark multi_bm; + multi_bm.add_bm(); + multi_bm.register_bm(); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + + return 0; +} diff --git a/run-be-ut.sh b/run-be-ut.sh index 4ca5c274e1a600..07d97d1a8c8ed3 100755 --- a/run-be-ut.sh +++ b/run-be-ut.sh @@ -128,7 +128,7 @@ if [ ${RUN} -ne 1 ]; then fi echo "******************************" -echo " Running Backend Unit Test " +echo " Running Backend Unit Test " echo "******************************" cd ${DORIS_HOME} diff --git a/thirdparty/build-thirdparty.sh b/thirdparty/build-thirdparty.sh index 0163a52040b8ba..7452bc7ecbb401 100755 --- a/thirdparty/build-thirdparty.sh +++ b/thirdparty/build-thirdparty.sh @@ -914,6 +914,25 @@ build_hdfs3() { make -j $PARALLEL && make install } +# benchmark +build_benchmark() { + check_if_source_exist $BENCHMARK_SOURCE + + cd $TP_SOURCE_DIR/$BENCHMARK_SOURCE + + sed -i '160 i \ \ add_cxx_compiler_flag(-lresolv)' ./CMakeLists.txt + sed -i '160 i \ \ add_cxx_compiler_flag(-pthread)' ./CMakeLists.txt + sed -i '160 i \ \ add_cxx_compiler_flag(-lrt)' ./CMakeLists.txt + + cmake -E make_directory "build" + cmake -E chdir "build" cmake -DBENCHMARK_DOWNLOAD_DEPENDENCIES=on -DCMAKE_BUILD_TYPE=Release ../ + cmake --build "build" --config Release + + mkdir $TP_INCLUDE_DIR/benchmark + cp $TP_SOURCE_DIR/$BENCHMARK_SOURCE/include/benchmark/benchmark.h $TP_INCLUDE_DIR/benchmark/ + cp $TP_SOURCE_DIR/$BENCHMARK_SOURCE/build/src/libbenchmark.a $TP_LIB_DIR/ +} + # See https://github.com/apache/incubator-doris/issues/2910 # LLVM related codes have already be removed in master, so there is # no need to build llvm tool here. @@ -968,5 +987,6 @@ build_lzma build_xml2 build_gsasl build_hdfs3 +build_benchmark echo "Finished to build all thirdparties" diff --git a/thirdparty/patched_mark b/thirdparty/patched_mark new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/thirdparty/vars.sh b/thirdparty/vars.sh index 6088e7e0756874..12261cd09de4a0 100644 --- a/thirdparty/vars.sh +++ b/thirdparty/vars.sh @@ -390,6 +390,13 @@ PDQSORT_DOWNLOAD="http://ftp.cise.ufl.edu/ubuntu/pool/universe/p/pdqsort/pdqsort PDQSORT_NAME="pdqsort.tar.gz" PDQSORT_SOURCE="pdqsort-0.0.0+git20180419" PDQSORT_MD5SUM="39261c3e7b40aa7505662fac29f22d20" + +# benchmark +BENCHMARK_DOWNLOAD="https://github.com/google/benchmark/archive/v1.5.6.tar.gz" +BENCHMARK_NAME=benchmark-1.5.6.tar.gz +BENCHMARK_SOURCE=benchmark-1.5.6 +BENCHMARK_MD5SUM="668b9e10d8b0795e5d461894db18db3c" + # all thirdparties which need to be downloaded is set in array TP_ARCHIVES export TP_ARCHIVES="LIBEVENT OPENSSL @@ -447,5 +454,6 @@ XML2 GSASL HDFS3 LIBDIVIDE -PDQSORT" +PDQSORT +BENCHMARK" From a918c5ba5f53b6389ba61a91cf22f5a7e03da9e5 Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt <952130278@qq.com> Date: Wed, 25 Aug 2021 20:35:04 +0800 Subject: [PATCH 2/6] remove useless file --- thirdparty/patched_mark | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 thirdparty/patched_mark diff --git a/thirdparty/patched_mark b/thirdparty/patched_mark deleted file mode 100644 index e69de29bb2d1d6..00000000000000 From 65bb2d5eba3f8b0264542d7d1968c8de07d55fbb Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt <952130278@qq.com> Date: Thu, 26 Aug 2021 11:43:54 +0800 Subject: [PATCH 3/6] support string type && remove split && promote build function --- be/test/olap/tablet_schema_helper.h | 45 ++++++++++++++++++++++++----- be/test/test_util/test_util.cpp | 15 ---------- be/test/test_util/test_util.h | 2 -- be/test/tools/benchmark_tool.cpp | 15 ++++++---- thirdparty/build-thirdparty.sh | 6 +--- 5 files changed, 47 insertions(+), 36 deletions(-) diff --git a/be/test/olap/tablet_schema_helper.h b/be/test/olap/tablet_schema_helper.h index 4a069828c024ff..b2183c3a7387ff 100644 --- a/be/test/olap/tablet_schema_helper.h +++ b/be/test/olap/tablet_schema_helper.h @@ -80,7 +80,19 @@ TabletColumn create_varchar_key(int32_t id, bool is_nullable = true) { column._type = OLAP_FIELD_TYPE_VARCHAR; column._is_key = true; column._is_nullable = is_nullable; - column._length = 8; + column._length = 65533; + column._index_length = 4; + return column; +} + +TabletColumn create_string_key(int32_t id, bool is_nullable = true) { + TabletColumn column; + column._unique_id = id; + column._col_name = std::to_string(id); + column._type = OLAP_FIELD_TYPE_STRING; + column._is_key = true; + column._is_nullable = is_nullable; + column._length = 2147483643; column._index_length = 4; return column; } @@ -98,10 +110,10 @@ TabletColumn create_with_default_value(std::string default_value) { } void set_column_value_by_type(FieldType fieldType, int src, char* target, MemPool* pool, - size_t _length = 0) { + size_t _length = 8) { if (fieldType == OLAP_FIELD_TYPE_CHAR) { std::string s = std::to_string(src); - char* src_value = &s[0]; + const char* src_value = s.c_str(); int src_len = s.size(); auto* dest_slice = (Slice*)target; @@ -111,7 +123,16 @@ void set_column_value_by_type(FieldType fieldType, int src, char* target, MemPoo memset(dest_slice->data + src_len, 0, dest_slice->size - src_len); } else if (fieldType == OLAP_FIELD_TYPE_VARCHAR) { std::string s = std::to_string(src); - char* src_value = &s[0]; + const char* src_value = s.c_str(); + int src_len = s.size(); + + auto* dest_slice = (Slice*)target; + dest_slice->size = src_len; + dest_slice->data = (char*)pool->allocate(src_len); + memcpy(dest_slice->data, src_value, src_len); + } else if (fieldType == OLAP_FIELD_TYPE_STRING) { + std::string s = std::to_string(src); + const char* src_value = s.c_str(); int src_len = s.size(); auto* dest_slice = (Slice*)target; @@ -123,10 +144,10 @@ void set_column_value_by_type(FieldType fieldType, int src, char* target, MemPoo } } -void set_column_value_by_type(FieldType fieldType, std::string src, char* target, MemPool* pool, - size_t _length = 0) { +void set_column_value_by_type(FieldType fieldType, const std::string& src, char* target, + MemPool* pool, size_t _length = 8) { if (fieldType == OLAP_FIELD_TYPE_CHAR) { - char* src_value = &src[0]; + const char* src_value = src.c_str(); int src_len = src.size(); auto* dest_slice = (Slice*)target; @@ -135,7 +156,15 @@ void set_column_value_by_type(FieldType fieldType, std::string src, char* target memcpy(dest_slice->data, src_value, src_len); memset(dest_slice->data + src_len, 0, dest_slice->size - src_len); } else if (fieldType == OLAP_FIELD_TYPE_VARCHAR) { - char* src_value = &src[0]; + const char* src_value = src.c_str(); + int src_len = src.size(); + + auto* dest_slice = (Slice*)target; + dest_slice->size = src_len; + dest_slice->data = (char*)pool->allocate(src_len); + memcpy(dest_slice->data, src_value, src_len); + } else if (fieldType == OLAP_FIELD_TYPE_STRING) { + const char* src_value = src.c_str(); int src_len = src.size(); auto* dest_slice = (Slice*)target; diff --git a/be/test/test_util/test_util.cpp b/be/test/test_util/test_util.cpp index a275f32496b1cc..705003a73fb19a 100644 --- a/be/test/test_util/test_util.cpp +++ b/be/test/test_util/test_util.cpp @@ -73,21 +73,6 @@ void InitConfig() { } } -std::vector split_str(const std::string& str, char separation) { - std::vector tokens; - int pre = 0; - int now = 0; - for (char c : str) { - now++; - if (c == separation) { - tokens.emplace_back(str.substr(pre, now - pre - 1)); - pre = now; - } - } - if (pre < now) tokens.emplace_back(str.substr(pre, now - pre)); - return tokens; -} - bool equal_ignore_case(std::string lhs, std::string rhs) { std::transform(lhs.begin(), lhs.end(), lhs.begin(), ::tolower); std::transform(rhs.begin(), rhs.end(), rhs.begin(), ::tolower); diff --git a/be/test/test_util/test_util.h b/be/test/test_util/test_util.h index 74b94fce0f548a..7cb2d796c2a017 100644 --- a/be/test/test_util/test_util.h +++ b/be/test/test_util/test_util.h @@ -39,8 +39,6 @@ std::string GetCurrentRunningDir(); // Initialize config file. void InitConfig(); -std::vector split_str(const std::string& str, char separation = ','); - bool equal_ignore_case(std::string lhs, std::string rhs); int rand_rng_int(int l, int r); diff --git a/be/test/tools/benchmark_tool.cpp b/be/test/tools/benchmark_tool.cpp index 2f717dd7c31362..a9eb8480e16e3d 100644 --- a/be/test/tools/benchmark_tool.cpp +++ b/be/test/tools/benchmark_tool.cpp @@ -31,6 +31,7 @@ #include "common/compiler_util.h" #include "common/logging.h" +#include "gutil/strings/split.h" #include "gutil/strings/substitute.h" #include "olap/comparison_predicate.h" #include "olap/fs/block_manager.h" @@ -58,7 +59,7 @@ DEFINE_string(operation, "Custom", "valid operation: Custom, BinaryDictPageEncode, SegmentScan, SegmentWrite, " "SegmentScanByFile, SegmentWriteByFile"); DEFINE_string(input_file, "./sample.dat", "input file directory"); -DEFINE_string(column_type, "int,varchar", "valid type: int, char, varchar"); +DEFINE_string(column_type, "int,varchar", "valid type: int, char, varchar, string"); DEFINE_string(rows_number, "10000", "rows number"); DEFINE_string(iterations, "10", "run times, this is set to 0 means the number of iterations is automatically set "); @@ -222,7 +223,7 @@ class SegmentBenchmark : public BaseBenchmark { void init_schema(std::string column_type) { std::string column_valid = "/column_type:"; - auto tokens = split_str(column_type); + std::vector tokens = strings::Split(column_type, ","); std::vector columns; bool first_column = true; @@ -231,10 +232,12 @@ class SegmentBenchmark : public BaseBenchmark { if (equal_ignore_case(token, "int")) { columns.emplace_back(create_int_key(columns.size() + 1)); - } else if (equal_ignore_case(token, "varchar")) { - columns.emplace_back(create_varchar_key(columns.size() + 1)); } else if (equal_ignore_case(token, "char")) { columns.emplace_back(create_char_key(columns.size() + 1)); + } else if (equal_ignore_case(token, "varchar")) { + columns.emplace_back(create_varchar_key(columns.size() + 1)); + } else if (equal_ignore_case(token, "string")) { + columns.emplace_back(create_string_key(columns.size() + 1)); } else { valid = false; } @@ -371,7 +374,7 @@ class SegmentWriteByFileBenchmark : public SegmentBenchmark { while (file.peek() != EOF) { std::string row_str; std::getline(file, row_str); - auto tokens = split_str(row_str); + std::vector tokens = strings::Split(row_str, ","); assert(tokens.size() == _tablet_schema.num_columns()); _dataset.push_back(tokens); } @@ -434,7 +437,7 @@ class SegmentScanByFileBenchmark : public SegmentBenchmark { while (file.peek() != EOF) { std::string row_str; std::getline(file, row_str); - auto tokens = split_str(row_str); + std::vector tokens = strings::Split(row_str, ","); assert(tokens.size() == _tablet_schema.num_columns()); _dataset.push_back(tokens); } diff --git a/thirdparty/build-thirdparty.sh b/thirdparty/build-thirdparty.sh index 7452bc7ecbb401..2a1299d42ba050 100755 --- a/thirdparty/build-thirdparty.sh +++ b/thirdparty/build-thirdparty.sh @@ -920,12 +920,8 @@ build_benchmark() { cd $TP_SOURCE_DIR/$BENCHMARK_SOURCE - sed -i '160 i \ \ add_cxx_compiler_flag(-lresolv)' ./CMakeLists.txt - sed -i '160 i \ \ add_cxx_compiler_flag(-pthread)' ./CMakeLists.txt - sed -i '160 i \ \ add_cxx_compiler_flag(-lrt)' ./CMakeLists.txt - cmake -E make_directory "build" - cmake -E chdir "build" cmake -DBENCHMARK_DOWNLOAD_DEPENDENCIES=on -DCMAKE_BUILD_TYPE=Release ../ + CXXFLAGS="-lresolv -pthread -lrt" cmake -E chdir "build" cmake -DBENCHMARK_ENABLE_GTEST_TESTS=OFF -DCMAKE_BUILD_TYPE=Release ../ cmake --build "build" --config Release mkdir $TP_INCLUDE_DIR/benchmark From e9967949cb392263006a39c23b39697f3d606f5d Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt <952130278@qq.com> Date: Thu, 26 Aug 2021 16:22:32 +0800 Subject: [PATCH 4/6] replace static generator to random generator --- be/test/test_util/test_util.cpp | 11 ++++++ be/test/test_util/test_util.h | 3 ++ be/test/tools/benchmark_tool.cpp | 57 +++++++++++--------------------- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/be/test/test_util/test_util.cpp b/be/test/test_util/test_util.cpp index 705003a73fb19a..f9b034c825148b 100644 --- a/be/test/test_util/test_util.cpp +++ b/be/test/test_util/test_util.cpp @@ -95,5 +95,16 @@ std::string rand_rng_string(size_t length) { } return s; } +std::string rand_rng_by_type(FieldType fieldType) { + if (fieldType == OLAP_FIELD_TYPE_CHAR) { + return rand_rng_string(rand_rng_int(1, 8)); + } else if (fieldType == OLAP_FIELD_TYPE_VARCHAR) { + return rand_rng_string(rand_rng_int(1, 128)); + } else if (fieldType == OLAP_FIELD_TYPE_STRING) { + return rand_rng_string(rand_rng_int(1, 100000)); + } else { + return std::to_string(rand_rng_int(1, 1000000)); + } +} } // namespace doris diff --git a/be/test/test_util/test_util.h b/be/test/test_util/test_util.h index 7cb2d796c2a017..fe06aa62c5ac1f 100644 --- a/be/test/test_util/test_util.h +++ b/be/test/test_util/test_util.h @@ -22,6 +22,8 @@ #include #include +#include "olap/tablet_schema.h" + namespace doris { #define LOOP_LESS_OR_MORE(less, more) (AllowSlowTests() ? more : less) @@ -44,5 +46,6 @@ bool equal_ignore_case(std::string lhs, std::string rhs); int rand_rng_int(int l, int r); char rand_rng_char(); std::string rand_rng_string(size_t length = 8); +std::string rand_rng_by_type(FieldType fieldType); } // namespace doris diff --git a/be/test/tools/benchmark_tool.cpp b/be/test/tools/benchmark_tool.cpp index a9eb8480e16e3d..74fb609eae9c42 100644 --- a/be/test/tools/benchmark_tool.cpp +++ b/be/test/tools/benchmark_tool.cpp @@ -258,7 +258,8 @@ class SegmentBenchmark : public BaseBenchmark { add_name(column_valid); } - void build_segment(size_t nrows, std::shared_ptr* res) { + void build_segment(std::vector> dataset, + std::shared_ptr* res) { // must use unique filename for each segment, otherwise page cache kicks in and produces // the wrong answer (it use (filename,offset) as cache key) std::string filename = strings::Substitute("$0/seg_$1.dat", kSegmentDir, ++seg_id); @@ -272,10 +273,10 @@ class SegmentBenchmark : public BaseBenchmark { RowCursor row; row.init(_tablet_schema); - for (size_t rid = 0; rid < nrows; ++rid) { + for (auto tokens : dataset) { for (int cid = 0; cid < _tablet_schema.num_columns(); ++cid) { RowCursorCell cell = row.cell(cid); - set_column_value_by_type(_tablet_schema._cols[cid]._type, rid * 10 + cid, + set_column_value_by_type(_tablet_schema._cols[cid]._type, tokens[cid], (char*)cell.mutable_cell_ptr(), &_pool, _tablet_schema._cols[cid]._length); } @@ -289,36 +290,16 @@ class SegmentBenchmark : public BaseBenchmark { Segment::open(filename, seg_id, &_tablet_schema, res); } - void build_segment(std::vector> dataset, - std::shared_ptr* res) { - // must use unique filename for each segment, otherwise page cache kicks in and produces - // the wrong answer (it use (filename,offset) as cache key) - std::string filename = strings::Substitute("$0/seg_$1.dat", kSegmentDir, ++seg_id); - std::unique_ptr wblock; - fs::CreateBlockOptions block_opts({filename}); - fs::fs_util::block_manager()->create_block(block_opts, &wblock); - SegmentWriterOptions opts; - SegmentWriter writer(wblock.get(), 0, &_tablet_schema, opts); - writer.init(1024); - - RowCursor row; - row.init(_tablet_schema); - - for (auto tokens : dataset) { + std::vector> generate_dataset(int rows_number) { + std::vector> dataset; + while (rows_number--) { + std::vector row_data; for (int cid = 0; cid < _tablet_schema.num_columns(); ++cid) { - RowCursorCell cell = row.cell(cid); - set_column_value_by_type(_tablet_schema._cols[cid]._type, tokens[cid], - (char*)cell.mutable_cell_ptr(), &_pool, - _tablet_schema._cols[cid]._length); + row_data.emplace_back(rand_rng_by_type(_tablet_schema._cols[cid]._type)); } - writer.append_row(row); + dataset.emplace_back(row_data); } - - uint64_t file_size, index_size; - writer.finalize(&file_size, &index_size); - wblock->close(); - - Segment::open(filename, seg_id, &_tablet_schema, res); + return dataset; } private: @@ -347,22 +328,24 @@ class SegmentBenchmark : public BaseBenchmark { }; // namespace doris class SegmentWriteBenchmark : public SegmentBenchmark { +public: SegmentWriteBenchmark(std::string name, int iterations, std::string column_type, int rows_number) : SegmentBenchmark(name + "/rows_number:" + std::to_string(rows_number), iterations, column_type), - _rows_number(rows_number) {} + _dataset(generate_dataset(rows_number)) {} virtual ~SegmentWriteBenchmark() override {} virtual void init() override {} - virtual void run() override { build_segment(_rows_number, &_segment); }; + virtual void run() override { build_segment(_dataset, &_segment); }; private: - int _rows_number; + std::vector> _dataset; std::shared_ptr _segment; }; class SegmentWriteByFileBenchmark : public SegmentBenchmark { +public: SegmentWriteByFileBenchmark(std::string name, int iterations, std::string file_str) : SegmentBenchmark(name + "/file_path:" + file_str, iterations) { std::ifstream file(file_str); @@ -396,10 +379,10 @@ class SegmentScanBenchmark : public SegmentBenchmark { SegmentScanBenchmark(std::string name, int iterations, std::string column_type, int rows_number) : SegmentBenchmark(name + "/rows_number:" + std::to_string(rows_number), iterations, column_type), - _rows_number(rows_number) {} + _dataset(generate_dataset(rows_number)) {} virtual ~SegmentScanBenchmark() override {} - virtual void init() override { build_segment(_rows_number, &_segment); } + virtual void init() override { build_segment(_dataset, &_segment); } virtual void run() override { StorageReadOptions read_opts; read_opts.stats = &stats; @@ -407,7 +390,7 @@ class SegmentScanBenchmark : public SegmentBenchmark { _segment->new_iterator(get_schema(), read_opts, nullptr, &iter); RowBlockV2 block(get_schema(), 1024); - int left = _rows_number; + int left = _dataset.size(); int rowid = 0; while (left > 0) { int rows_read = std::min(left, 1024); @@ -419,7 +402,7 @@ class SegmentScanBenchmark : public SegmentBenchmark { }; private: - int _rows_number; + std::vector> _dataset; std::shared_ptr _segment; OlapReaderStatistics stats; }; From d439a14e3ed37dc5babad1e68e7c3b7cf8572439 Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt <952130278@qq.com> Date: Mon, 30 Aug 2021 11:24:40 +0800 Subject: [PATCH 5/6] add document for benchmark tool --- docs/.vuepress/sidebar/en.js | 1 + docs/.vuepress/sidebar/zh-CN.js | 1 + docs/en/developer-guide/benchmark-tool.md | 139 +++++++++++++++++++ docs/zh-CN/developer-guide/benchmark-tool.md | 138 ++++++++++++++++++ 4 files changed, 279 insertions(+) create mode 100644 docs/en/developer-guide/benchmark-tool.md create mode 100644 docs/zh-CN/developer-guide/benchmark-tool.md diff --git a/docs/.vuepress/sidebar/en.js b/docs/.vuepress/sidebar/en.js index f075724425f966..8791ab30b75106 100644 --- a/docs/.vuepress/sidebar/en.js +++ b/docs/.vuepress/sidebar/en.js @@ -601,6 +601,7 @@ module.exports = [ directoryPath: "developer-guide/", children: [ "debug-tool", + "benchmark-tool", "fe-eclipse-dev", "fe-idea-dev", "be-vscode-dev", diff --git a/docs/.vuepress/sidebar/zh-CN.js b/docs/.vuepress/sidebar/zh-CN.js index efe1ee6bc45667..50363555feec81 100644 --- a/docs/.vuepress/sidebar/zh-CN.js +++ b/docs/.vuepress/sidebar/zh-CN.js @@ -605,6 +605,7 @@ module.exports = [ directoryPath: "developer-guide/", children: [ "debug-tool", + "benchmark-tool", "fe-eclipse-dev", "fe-idea-dev", "be-vscode-dev", diff --git a/docs/en/developer-guide/benchmark-tool.md b/docs/en/developer-guide/benchmark-tool.md new file mode 100644 index 00000000000000..0936c9b15c62fb --- /dev/null +++ b/docs/en/developer-guide/benchmark-tool.md @@ -0,0 +1,139 @@ +--- +{ + "title": "Doris BE Storage Layer Benchmark Tool", + "language": "zh-CN" +} + +--- + + + +# Doris BE Storage Layer Benchmark Tool + +## usage + +It can be used to test the performance of some parts of the BE storage layer (for example, segment, page). According to the input data, the designated object is constructed, and the google benchmark is used for performance testing. + +## Compilation + +1. To ensure that the environment has been able to successfully compile the Doris ontology, you can refer to [Installation and deployment] (https://doris.apache.org/master/en/installing/compilation.html)。 + +2. Execute`run-be-ut.sh` + +3. The compiled executable file is located in `./be/ut_build_ASAN/test/tools/benchmark_tool` + +## operator + +#### Use randomly generated data set for segment read test + +The data set will be used to write a `segment` file first, and then the time-consuming scan of the entire `segment` will be counted. + +> ./benchmark_tool --operation=SegmentScan --column_type=int,varchar --rows_number=10000 --iterations=0 + +The `column_type` here can set the schema, the column type of the `segment` layer currently supports `int, char, varchar, string`, the length of the `char` type is `8`, and both `varchar` and `string` types have length restrictions Is the maximum value. The default value is `int,varchar`. + +The data set is generated according to the following rules. +>int: Random in [1,1000000]. + +The data character set of string type is uppercase and lowercase English letters, and the length varies according to the type. +> char: Length random in [1,8]。 +> varchar: Length random in [1,128]。 +> string: Length random in [1,100000]。 + +`rows_number` indicates the number of rows of data, the default value is `10000`. + +`iterations` indicates the number of iterations, the benchmark will repeat the test, and then calculate the average time. If `iterations` is `0`, it means that the number of iterations is automatically selected by the benchmark. The default value is `10`. + +#### Use randomly generated data set for segment write test + +Perform time-consuming statistics on the process of adding data sets to segments and writing them to disk. + +> ./benchmark_tool --operation=SegmentWrite + +#### Use the data set imported from the file for segment read test + +> ./benchmark_tool --operation=SegmentScanByFile --input_file=./sample.dat + +The `input_file` here is the imported data set file. +The first row of the data set file defines the schema, and each row corresponds to a row of data, and each data is separated by `,`. + +Example: +``` +int,char,varchar +123,hello,world +321,good,bye +``` + +The type support is also `int`, `char`, `varchar`, `string`. Note that the data length of the `char` type cannot exceed 8. + +#### Use the data set imported from the file for segment write test + +> ./benchmark_tool --operation=SegmentWriteByFile --input_file=./sample.dat + +## Custom test + +Here, users are supported to use their own functions for performance testing, which can be implemented in `/be/test/tools/benchmark_tool.cpp`. + +For example: +```cpp +void custom_run_plus() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (UNLIKELY(q == 1024)) q = 0; + } +} +void custom_run_mod() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (q %= 1024) q = 0; + } +} +``` +You can join the test by registering `CustomBenchmark`. +```cpp +benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_plus", 0, + custom_init, custom_run_plus)); +benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_mod", 0, + custom_init, custom_run_mod)); +``` +The `custom_init` here is the initialization step of each round of testing (not counted as time-consuming). If the user has an object that needs to be initialized, it can be implemented by a derived class of `CustomBenchmark`. +After running, the results are as follows: +``` +2021-08-30T10:29:35+08:00 +Running ./benchmark_tool +Run on (96 X 3100.75 MHz CPU s) +CPU Caches: + L1 Data 32 KiB (x48) + L1 Instruction 32 KiB (x48) + L2 Unified 1024 KiB (x48) + L3 Unified 33792 KiB (x2) +Load Average: 0.55, 0.53, 0.39 +---------------------------------------------------------- +Benchmark Time CPU Iterations +---------------------------------------------------------- +custom_run_plus 0.812 ms 0.812 ms 861 +custom_run_mod 1.30 ms 1.30 ms 539 +``` diff --git a/docs/zh-CN/developer-guide/benchmark-tool.md b/docs/zh-CN/developer-guide/benchmark-tool.md new file mode 100644 index 00000000000000..c37265a0f799b0 --- /dev/null +++ b/docs/zh-CN/developer-guide/benchmark-tool.md @@ -0,0 +1,138 @@ +--- +{ + "title": "Doris BE存储层Benchmark工具", + "language": "zh-CN" +} + +--- + + + +# Doris BE存储层Benchmark工具 + +## 用途 + + 可以用来测试BE存储层的一些部分(例如segment、page)的性能。根据输入数据构造出指定对象,利用google benchmark进行性能测试。 + +## 编译 + +1. 确保环境已经能顺利编译Doris本体,可以参考[编译与部署](https://doris.apache.org/master/zh-CN/installing/compilation.html)。 + +2. 运行目录下的`run-be-ut.sh` + +3. 编译出的可执行文件位于`./be/ut_build_ASAN/test/tools/benchmark_tool` + +## 使用 + +#### 使用随机生成的数据集进行Segment读取测试 + +会先利用数据集写入一个`segment`文件,然后对scan整个`segment`的耗时进行统计。 + +> ./benchmark_tool --operation=SegmentScan --column_type=int,varchar --rows_number=10000 --iterations=0 + +这里的`column_type`可以设置表结构,`segment`层的表结构类型目前支持`int、char、varchar、string`,`char`类型的长度为`8`,`varchar`和`string`类型长度限制都为最大值。默认值为`int,varchar`。 + +数据集按以下规则生成。 +>int: 在[1,1000000]内随机。 + +字符串类型的数据字符集为大小写英文字母,长度根据类型不同。 +> char: 长度在[1,8]内随机。 +> varchar: 长度在[1,128]内随机。 +> string: 长度在[1,100000]内随机。 + +`rows_number`表示数据的行数,默认值为`10000`。 + +`iterations`表示迭代次数,benchmark会重复进行测试,然后计算平均耗时。如果`iterations`为`0`则表示由benchmark自动选择迭代次数。默认值为`10`。 + +#### 使用随机生成的数据集进行Segment写入测试 + +对将数据集添加进segment并写入磁盘的流程进行耗时统计。 + +> ./benchmark_tool --operation=SegmentWrite + +#### 使用从文件导入的数据集进行Segment读取测试 + +> ./benchmark_tool --operation=SegmentScanByFile --input_file=./sample.dat + +这里的`input_file`为导入的数据集文件。 +数据集文件第一行为表结构定义,之后每行分别对应一行数据,每个数据用`,`隔开。 + +举例: +`` +int,char,varchar +123,hello,world +321,good,bye +``` + +类型支持同样为`int`、`char`、`varchar`、`string`,注意`char`类型数据长度不能超过8。 + +#### 使用从文件导入的数据集进行Segment写入测试 + +> ./benchmark_tool --operation=SegmentWriteByFile --input_file=./sample.dat + +## Custom测试 + +这里支持用户使用自己编写的函数进行性能测试,具体可以实现在`/be/test/tools/benchmark_tool.cpp`。 +例如实现有: +```cpp +void custom_run_plus() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (UNLIKELY(q == 1024)) q = 0; + } +} +void custom_run_mod() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (q %= 1024) q = 0; + } +} +``` +则可以通过注册`CustomBenchmark`来加入测试。 +```cpp +benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_plus", 0, + custom_init, custom_run_plus)); +benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_mod", 0, + custom_init, custom_run_mod)); +``` +这里的`init`为每轮测试的初始化步骤(不会计入耗时),如果用户有需要初始化的对象则可以通过`CustomBenchmark`的派生类来实现。 +运行后有如下结果: +``` +2021-08-30T10:29:35+08:00 +Running ./benchmark_tool +Run on (96 X 3100.75 MHz CPU s) +CPU Caches: + L1 Data 32 KiB (x48) + L1 Instruction 32 KiB (x48) + L2 Unified 1024 KiB (x48) + L3 Unified 33792 KiB (x2) +Load Average: 0.55, 0.53, 0.39 +---------------------------------------------------------- +Benchmark Time CPU Iterations +---------------------------------------------------------- +custom_run_plus 0.812 ms 0.812 ms 861 +custom_run_mod 1.30 ms 1.30 ms 539 +``` From 6be11e6620595dcf95530b6176225278005acd68 Mon Sep 17 00:00:00 2001 From: BiteTheDDDDt <952130278@qq.com> Date: Mon, 30 Aug 2021 20:18:43 +0800 Subject: [PATCH 6/6] add BinaryDictPageDecodeBenchmark --- be/test/tools/benchmark_tool.cpp | 115 +++++++++++++++---- docs/en/developer-guide/benchmark-tool.md | 12 ++ docs/zh-CN/developer-guide/benchmark-tool.md | 14 ++- 3 files changed, 120 insertions(+), 21 deletions(-) diff --git a/be/test/tools/benchmark_tool.cpp b/be/test/tools/benchmark_tool.cpp index 74fb609eae9c42..1e8d4e23e7aaa9 100644 --- a/be/test/tools/benchmark_tool.cpp +++ b/be/test/tools/benchmark_tool.cpp @@ -56,7 +56,8 @@ #include "util/file_utils.h" DEFINE_string(operation, "Custom", - "valid operation: Custom, BinaryDictPageEncode, SegmentScan, SegmentWrite, " + "valid operation: Custom, BinaryDictPageEncode, BinaryDictPageDecode, SegmentScan, " + "SegmentWrite, " "SegmentScanByFile, SegmentWriteByFile"); DEFINE_string(input_file, "./sample.dat", "input file directory"); DEFINE_string(column_type, "int,varchar", "valid type: int, char, varchar, string"); @@ -75,6 +76,8 @@ std::string get_usage(const std::string& progname) { ss << "./benchmark_tool --operation=Custom\n"; ss << "./benchmark_tool --operation=BinaryDictPageEncode " "--rows_number=10000 --iterations=40\n"; + ss << "./benchmark_tool --operation=BinaryDictPageDecode " + "--rows_number=10000 --iterations=40\n"; ss << "./benchmark_tool --operation=SegmentScan --column_type=int,varchar " "--rows_number=10000 --iterations=0\n"; ss << "./benchmark_tool --operation=SegmentWrite --column_type=int " @@ -100,12 +103,12 @@ namespace doris { class BaseBenchmark { public: BaseBenchmark(std::string name, int iterations) : _name(name), _iterations(iterations) {} - virtual ~BaseBenchmark() {}; + virtual ~BaseBenchmark() {} void add_name(std::string str) { _name += str; } - virtual void init() {}; - virtual void run() {}; + virtual void init() {} + virtual void run() {} void register_bm() { auto bm = benchmark::RegisterBenchmark(_name.c_str(), [&](benchmark::State& state) { @@ -133,16 +136,18 @@ class BaseBenchmark { class BinaryDictPageBenchmark : public BaseBenchmark { public: BinaryDictPageBenchmark(std::string name, int iterations) : BaseBenchmark(name, iterations) {} - virtual ~BinaryDictPageBenchmark() override {}; + virtual ~BinaryDictPageBenchmark() override {} - virtual void init() override {}; - virtual void run() override {}; + virtual void init() override {} + virtual void run() override {} - std::vector encode_pages(const std::vector& contents) { + void encode_pages(const std::vector& contents) { PageBuilderOptions options; BinaryDictPageBuilder page_builder(options); - std::vector results; + results.clear(); + page_start_ids.clear(); + page_start_ids.push_back(0); for (size_t i = 0; i < contents.size(); i++) { const Slice* ptr = &contents[i]; size_t add_num = 1; @@ -150,14 +155,52 @@ class BinaryDictPageBenchmark : public BaseBenchmark { if (page_builder.is_page_full()) { OwnedSlice s = page_builder.finish(); results.emplace_back(std::move(s)); + page_start_ids.push_back(i + 1); page_builder.reset(); } } OwnedSlice s = page_builder.finish(); results.emplace_back(std::move(s)); + page_start_ids.push_back(contents.size()); - return results; + Status status = page_builder.get_dictionary_page(&dict_slice); } + void decode_pages() { + int slice_index = 0; + for (auto& src : results) { + PageDecoderOptions dict_decoder_options; + std::unique_ptr dict_page_decoder( + new BinaryPlainPageDecoder(dict_slice.slice(), dict_decoder_options)); + dict_page_decoder->init(); + + // decode + PageDecoderOptions decoder_options; + BinaryDictPageDecoder page_decoder(src.slice(), decoder_options); + page_decoder.init(); + page_decoder.set_dict_decoder(dict_page_decoder.get()); + + //check values + + size_t num = page_start_ids[slice_index + 1] - page_start_ids[slice_index]; + + auto tracker = std::make_shared(); + MemPool pool(tracker.get()); + TypeInfo* type_info = get_scalar_type_info(OLAP_FIELD_TYPE_VARCHAR); + std::unique_ptr cvb; + ColumnVectorBatch::create(num, false, type_info, nullptr, &cvb); + ColumnBlock column_block(cvb.get(), &pool); + ColumnBlockView block_view(&column_block); + + page_decoder.next_batch(&num, &block_view); + + slice_index++; + } + } + +private: + std::vector results; + OwnedSlice dict_slice; + std::vector page_start_ids; }; class BinaryDictPageEncodeBenchmark : public BinaryDictPageBenchmark { @@ -166,7 +209,7 @@ class BinaryDictPageEncodeBenchmark : public BinaryDictPageBenchmark { : BinaryDictPageBenchmark(name + "/rows_number:" + std::to_string(rows_number), iterations), _rows_number(rows_number) {} - virtual ~BinaryDictPageEncodeBenchmark() override {}; + virtual ~BinaryDictPageEncodeBenchmark() override {} virtual void init() override { src_strings.clear(); @@ -178,8 +221,8 @@ class BinaryDictPageEncodeBenchmark : public BinaryDictPageBenchmark { for (auto s : src_strings) { slices.emplace_back(s.c_str()); } - }; - virtual void run() override { auto p = encode_pages(slices); }; + } + virtual void run() override { encode_pages(slices); } private: std::vector slices; @@ -187,6 +230,35 @@ class BinaryDictPageEncodeBenchmark : public BinaryDictPageBenchmark { int _rows_number; }; +class BinaryDictPageDecodeBenchmark : public BinaryDictPageBenchmark { +public: + BinaryDictPageDecodeBenchmark(std::string name, int iterations, int rows_number) + : BinaryDictPageBenchmark(name + "/rows_number:" + std::to_string(rows_number), + iterations), + _rows_number(rows_number) {} + virtual ~BinaryDictPageDecodeBenchmark() override {} + + virtual void init() override { + src_strings.clear(); + for (int i = 0; i < _rows_number; i++) { + src_strings.emplace_back(rand_rng_string(rand_rng_int(1, 8))); + } + + slices.clear(); + for (auto s : src_strings) { + slices.emplace_back(s.c_str()); + } + + encode_pages(slices); + } + virtual void run() override { decode_pages(); } + +private: + std::vector slices; + std::vector src_strings; + int _rows_number; +}; // namespace doris + class SegmentBenchmark : public BaseBenchmark { public: SegmentBenchmark(std::string name, int iterations, std::string column_type) @@ -217,8 +289,8 @@ class SegmentBenchmark : public BaseBenchmark { const Schema& get_schema() { return *_schema.get(); } - virtual void init() override {}; - virtual void run() override {}; + virtual void init() override {} + virtual void run() override {} void init_schema(std::string column_type) { std::string column_valid = "/column_type:"; @@ -337,7 +409,7 @@ class SegmentWriteBenchmark : public SegmentBenchmark { virtual ~SegmentWriteBenchmark() override {} virtual void init() override {} - virtual void run() override { build_segment(_dataset, &_segment); }; + virtual void run() override { build_segment(_dataset, &_segment); } private: std::vector> _dataset; @@ -367,7 +439,7 @@ class SegmentWriteByFileBenchmark : public SegmentBenchmark { virtual ~SegmentWriteByFileBenchmark() override {} virtual void init() override {} - virtual void run() override { build_segment(_dataset, &_segment); }; + virtual void run() override { build_segment(_dataset, &_segment); } private: std::vector> _dataset; @@ -399,7 +471,7 @@ class SegmentScanBenchmark : public SegmentBenchmark { left -= rows_read; rowid += rows_read; } - }; + } private: std::vector> _dataset; @@ -446,7 +518,7 @@ class SegmentScanByFileBenchmark : public SegmentBenchmark { left -= rows_read; rowid += rows_read; } - }; + } private: std::vector> _dataset; @@ -490,7 +562,7 @@ void custom_run_mod() { class MultiBenchmark { public: - MultiBenchmark() {}; + MultiBenchmark() {} ~MultiBenchmark() { for (auto bm : benchmarks) { delete bm; @@ -508,6 +580,9 @@ class MultiBenchmark { } else if (equal_ignore_case(FLAGS_operation, "BinaryDictPageEncode")) { benchmarks.emplace_back(new doris::BinaryDictPageEncodeBenchmark( FLAGS_operation, std::stoi(FLAGS_iterations), std::stoi(FLAGS_rows_number))); + } else if (equal_ignore_case(FLAGS_operation, "BinaryDictPageDecode")) { + benchmarks.emplace_back(new doris::BinaryDictPageDecodeBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), std::stoi(FLAGS_rows_number))); } else if (equal_ignore_case(FLAGS_operation, "SegmentScan")) { benchmarks.emplace_back(new doris::SegmentScanBenchmark( FLAGS_operation, std::stoi(FLAGS_iterations), FLAGS_column_type, diff --git a/docs/en/developer-guide/benchmark-tool.md b/docs/en/developer-guide/benchmark-tool.md index 0936c9b15c62fb..536881d7d4c70d 100644 --- a/docs/en/developer-guide/benchmark-tool.md +++ b/docs/en/developer-guide/benchmark-tool.md @@ -87,6 +87,18 @@ The type support is also `int`, `char`, `varchar`, `string`. Note that the data > ./benchmark_tool --operation=SegmentWriteByFile --input_file=./sample.dat +#### Use randomly generated data set for page dictionary encoding test + +> ./benchmark_tool --operation=BinaryDictPageEncode --rows_number=10000 --iterations=0 + +Randomly generate varchar with a length between [1,8], and perform time-consuming statistics on encoding. + +#### Use randomly generated data set for page dictionary decoding test + +> ./benchmark_tool --operation=BinaryDictPageDecode + +Randomly generate varchar with a length between [1,8] and encode, and perform time-consuming statistics on decoding. + ## Custom test Here, users are supported to use their own functions for performance testing, which can be implemented in `/be/test/tools/benchmark_tool.cpp`. diff --git a/docs/zh-CN/developer-guide/benchmark-tool.md b/docs/zh-CN/developer-guide/benchmark-tool.md index c37265a0f799b0..8b1694395ea492 100644 --- a/docs/zh-CN/developer-guide/benchmark-tool.md +++ b/docs/zh-CN/developer-guide/benchmark-tool.md @@ -75,7 +75,7 @@ under the License. 数据集文件第一行为表结构定义,之后每行分别对应一行数据,每个数据用`,`隔开。 举例: -`` +``` int,char,varchar 123,hello,world 321,good,bye @@ -87,6 +87,18 @@ int,char,varchar > ./benchmark_tool --operation=SegmentWriteByFile --input_file=./sample.dat +#### 使用随机生成的数据集进行page字典编码测试 + +> ./benchmark_tool --operation=BinaryDictPageEncode --rows_number=10000 --iterations=0 + +会随机生成长度在[1,8]之间的varchar,并对编码进行耗时统计。 + +#### 使用随机生成的数据集进行page字典解码测试 + +> ./benchmark_tool --operation=BinaryDictPageDecode + +会随机生成长度在[1,8]之间的varchar并编码,并对解码进行耗时统计。 + ## Custom测试 这里支持用户使用自己编写的函数进行性能测试,具体可以实现在`/be/test/tools/benchmark_tool.cpp`。