|  | // Copyright (C) 2020 The Android Open Source Project | 
|  | // | 
|  | // Licensed 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. | 
|  |  | 
|  | // Benchmark for the SQLite VTable interface. | 
|  | // This benchmark measures the speed-of-light obtainable through a SQLite | 
|  | // virtual table. The code here implements an ideal virtual table which fetches | 
|  | // data in blocks and serves the xNext/xCol requests by just advancing a pointer | 
|  | // in a buffer. This is to have a fair estimate w.r.t. cache-misses and pointer | 
|  | // chasing of what an upper-bound can be for a virtual table implementation. | 
|  |  | 
|  | #include <array> | 
|  | #include <random> | 
|  |  | 
|  | #include <benchmark/benchmark.h> | 
|  | #include <sqlite3.h> | 
|  |  | 
|  | #include "perfetto/base/compiler.h" | 
|  | #include "src/trace_processor/sqlite/scoped_db.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | using benchmark::Counter; | 
|  | using perfetto::trace_processor::ScopedDb; | 
|  | using perfetto::trace_processor::ScopedStmt; | 
|  |  | 
|  | bool IsBenchmarkFunctionalOnly() { | 
|  | return getenv("BENCHMARK_FUNCTIONAL_TEST_ONLY") != nullptr; | 
|  | } | 
|  |  | 
|  | void SizeBenchmarkArgs(benchmark::internal::Benchmark* b) { | 
|  | if (IsBenchmarkFunctionalOnly()) { | 
|  | b->Ranges({{1024, 1024}}); | 
|  | } else { | 
|  | b->RangeMultiplier(2)->Ranges({{1024, 1024 * 128}}); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BenchmarkArgs(benchmark::internal::Benchmark* b) { | 
|  | if (IsBenchmarkFunctionalOnly()) { | 
|  | b->Ranges({{1024, 1024}, {1, 1}}); | 
|  | } else { | 
|  | b->RangeMultiplier(2)->Ranges({{1024, 1024 * 128}, {1, 8}}); | 
|  | } | 
|  | } | 
|  |  | 
|  | struct VtabContext { | 
|  | size_t batch_size; | 
|  | size_t num_cols; | 
|  | bool end_on_batch; | 
|  | }; | 
|  |  | 
|  | class BenchmarkCursor : public sqlite3_vtab_cursor { | 
|  | public: | 
|  | explicit BenchmarkCursor(size_t num_cols, | 
|  | size_t batch_size, | 
|  | bool end_on_batch) | 
|  | : num_cols_(num_cols), | 
|  | batch_size_(batch_size), | 
|  | end_on_batch_(end_on_batch), | 
|  | rnd_engine_(kRandomSeed) { | 
|  | column_buffer_.resize(num_cols); | 
|  | for (auto& col : column_buffer_) | 
|  | col.resize(batch_size); | 
|  | RandomFill(); | 
|  | } | 
|  | PERFETTO_NO_INLINE int Next(); | 
|  | PERFETTO_NO_INLINE int Column(sqlite3_context* ctx, int); | 
|  | PERFETTO_NO_INLINE int Eof(); | 
|  | void RandomFill(); | 
|  |  | 
|  | private: | 
|  | size_t num_cols_ = 0; | 
|  | size_t batch_size_ = 0; | 
|  | bool eof_ = false; | 
|  | bool end_on_batch_ = false; | 
|  | static constexpr uint32_t kRandomSeed = 476; | 
|  |  | 
|  | uint32_t row_ = 0; | 
|  | using ColBatch = std::vector<int64_t>; | 
|  | std::vector<ColBatch> column_buffer_; | 
|  |  | 
|  | std::minstd_rand0 rnd_engine_; | 
|  | }; | 
|  |  | 
|  | void BenchmarkCursor::RandomFill() { | 
|  | for (size_t col = 0; col < num_cols_; col++) { | 
|  | for (size_t row = 0; row < batch_size_; row++) { | 
|  | column_buffer_[col][row] = static_cast<int64_t>(rnd_engine_()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | int BenchmarkCursor::Next() { | 
|  | if (end_on_batch_) { | 
|  | row_++; | 
|  | eof_ = row_ == batch_size_; | 
|  | } else { | 
|  | row_ = (row_ + 1) % batch_size_; | 
|  | if (row_ == 0) | 
|  | RandomFill(); | 
|  | } | 
|  | return SQLITE_OK; | 
|  | } | 
|  |  | 
|  | int BenchmarkCursor::Eof() { | 
|  | return eof_; | 
|  | } | 
|  |  | 
|  | int BenchmarkCursor::Column(sqlite3_context* ctx, int col_int) { | 
|  | const auto col = static_cast<size_t>(col_int); | 
|  | PERFETTO_CHECK(col < column_buffer_.size()); | 
|  | sqlite3_result_int64(ctx, column_buffer_[col][row_]); | 
|  | return SQLITE_OK; | 
|  | } | 
|  |  | 
|  | ScopedDb CreateDbAndRegisterVtable(sqlite3_module& module, | 
|  | VtabContext& context) { | 
|  | struct BenchmarkVtab : public sqlite3_vtab { | 
|  | size_t num_cols; | 
|  | size_t batch_size; | 
|  | bool end_on_batch; | 
|  | }; | 
|  |  | 
|  | sqlite3_initialize(); | 
|  |  | 
|  | ScopedDb db; | 
|  | sqlite3* raw_db = nullptr; | 
|  | PERFETTO_CHECK(sqlite3_open(":memory:", &raw_db) == SQLITE_OK); | 
|  | db.reset(raw_db); | 
|  |  | 
|  | auto create_fn = [](sqlite3* xdb, void* aux, int, const char* const*, | 
|  | sqlite3_vtab** tab, char**) { | 
|  | auto& _context = *static_cast<VtabContext*>(aux); | 
|  | std::string sql = "CREATE TABLE x("; | 
|  | for (size_t col = 0; col < _context.num_cols; col++) | 
|  | sql += "c" + std::to_string(col) + " BIGINT,"; | 
|  | sql[sql.size() - 1] = ')'; | 
|  | int res = sqlite3_declare_vtab(xdb, sql.c_str()); | 
|  | PERFETTO_CHECK(res == SQLITE_OK); | 
|  | auto* vtab = new BenchmarkVtab(); | 
|  | vtab->batch_size = _context.batch_size; | 
|  | vtab->num_cols = _context.num_cols; | 
|  | vtab->end_on_batch = _context.end_on_batch; | 
|  | *tab = vtab; | 
|  | return SQLITE_OK; | 
|  | }; | 
|  |  | 
|  | auto destroy_fn = [](sqlite3_vtab* t) { | 
|  | delete static_cast<BenchmarkVtab*>(t); | 
|  | return SQLITE_OK; | 
|  | }; | 
|  |  | 
|  | module.xCreate = create_fn; | 
|  | module.xConnect = create_fn; | 
|  | module.xDisconnect = destroy_fn; | 
|  | module.xDestroy = destroy_fn; | 
|  |  | 
|  | module.xOpen = [](sqlite3_vtab* tab, sqlite3_vtab_cursor** c) { | 
|  | auto* vtab = static_cast<BenchmarkVtab*>(tab); | 
|  | *c = new BenchmarkCursor(vtab->num_cols, vtab->batch_size, | 
|  | vtab->end_on_batch); | 
|  | return SQLITE_OK; | 
|  | }; | 
|  | module.xBestIndex = [](sqlite3_vtab*, sqlite3_index_info* idx) { | 
|  | idx->orderByConsumed = true; | 
|  | for (int i = 0; i < idx->nConstraint; ++i) { | 
|  | idx->aConstraintUsage[i].omit = true; | 
|  | } | 
|  | return SQLITE_OK; | 
|  | }; | 
|  | module.xClose = [](sqlite3_vtab_cursor* c) { | 
|  | delete static_cast<BenchmarkCursor*>(c); | 
|  | return SQLITE_OK; | 
|  | }; | 
|  | module.xFilter = [](sqlite3_vtab_cursor*, int, const char*, int, | 
|  | sqlite3_value**) { return SQLITE_OK; }; | 
|  | module.xNext = [](sqlite3_vtab_cursor* c) { | 
|  | return static_cast<BenchmarkCursor*>(c)->Next(); | 
|  | }; | 
|  | module.xEof = [](sqlite3_vtab_cursor* c) { | 
|  | return static_cast<BenchmarkCursor*>(c)->Eof(); | 
|  | }; | 
|  | module.xColumn = [](sqlite3_vtab_cursor* c, sqlite3_context* a, int b) { | 
|  | return static_cast<BenchmarkCursor*>(c)->Column(a, b); | 
|  | }; | 
|  |  | 
|  | int res = | 
|  | sqlite3_create_module_v2(*db, "benchmark", &module, &context, nullptr); | 
|  | PERFETTO_CHECK(res == SQLITE_OK); | 
|  |  | 
|  | return db; | 
|  | } | 
|  |  | 
|  | static void BM_SqliteStepAndResult(benchmark::State& state) { | 
|  | size_t batch_size = static_cast<size_t>(state.range(0)); | 
|  | size_t num_cols = static_cast<size_t>(state.range(1)); | 
|  |  | 
|  | // Make sure the module outlives the ScopedDb. SQLite calls xDisconnect in | 
|  | // the database close function and so this struct needs to be available then. | 
|  | sqlite3_module module{}; | 
|  | VtabContext context{batch_size, num_cols, false}; | 
|  | ScopedDb db = CreateDbAndRegisterVtable(module, context); | 
|  |  | 
|  | ScopedStmt stmt; | 
|  | sqlite3_stmt* raw_stmt; | 
|  | std::string sql = "SELECT * from benchmark"; | 
|  | int err = sqlite3_prepare_v2(*db, sql.c_str(), static_cast<int>(sql.size()), | 
|  | &raw_stmt, nullptr); | 
|  | PERFETTO_CHECK(err == SQLITE_OK); | 
|  | stmt.reset(raw_stmt); | 
|  |  | 
|  | for (auto _ : state) { | 
|  | for (size_t i = 0; i < batch_size; i++) { | 
|  | PERFETTO_CHECK(sqlite3_step(*stmt) == SQLITE_ROW); | 
|  | for (int col = 0; col < static_cast<int>(num_cols); col++) { | 
|  | benchmark::DoNotOptimize(sqlite3_column_int64(*stmt, col)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | state.counters["s/row"] = | 
|  | Counter(static_cast<double>(batch_size), | 
|  | Counter::kIsIterationInvariantRate | Counter::kInvert); | 
|  | } | 
|  |  | 
|  | BENCHMARK(BM_SqliteStepAndResult)->Apply(BenchmarkArgs); | 
|  |  | 
|  | static void BM_SqliteCountOne(benchmark::State& state) { | 
|  | size_t batch_size = static_cast<size_t>(state.range(0)); | 
|  |  | 
|  | // Make sure the module outlives the ScopedDb. SQLite calls xDisconnect in | 
|  | // the database close function and so this struct needs to be available then. | 
|  | sqlite3_module module{}; | 
|  | VtabContext context{batch_size, 1, true}; | 
|  | ScopedDb db = CreateDbAndRegisterVtable(module, context); | 
|  |  | 
|  | ScopedStmt stmt; | 
|  | sqlite3_stmt* raw_stmt; | 
|  | std::string sql = "SELECT COUNT(1) from benchmark"; | 
|  | int err = sqlite3_prepare_v2(*db, sql.c_str(), static_cast<int>(sql.size()), | 
|  | &raw_stmt, nullptr); | 
|  | PERFETTO_CHECK(err == SQLITE_OK); | 
|  | stmt.reset(raw_stmt); | 
|  |  | 
|  | for (auto _ : state) { | 
|  | sqlite3_reset(raw_stmt); | 
|  | PERFETTO_CHECK(sqlite3_step(*stmt) == SQLITE_ROW); | 
|  | benchmark::DoNotOptimize(sqlite3_column_int64(*stmt, 0)); | 
|  | PERFETTO_CHECK(sqlite3_step(*stmt) == SQLITE_DONE); | 
|  | } | 
|  |  | 
|  | state.counters["s/row"] = | 
|  | Counter(static_cast<double>(batch_size), | 
|  | Counter::kIsIterationInvariantRate | Counter::kInvert); | 
|  | } | 
|  |  | 
|  | BENCHMARK(BM_SqliteCountOne)->Apply(SizeBenchmarkArgs); | 
|  |  | 
|  | }  // namespace |