// 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 #include #include #include #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; std::vector 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(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(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(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(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(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(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(c)->Next(); }; module.xEof = [](sqlite3_vtab_cursor* c) { return static_cast(c)->Eof(); }; module.xColumn = [](sqlite3_vtab_cursor* c, sqlite3_context* a, int b) { return static_cast(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(state.range(0)); size_t num_cols = static_cast(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(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(num_cols); col++) { benchmark::DoNotOptimize(sqlite3_column_int64(*stmt, col)); } } } state.counters["s/row"] = Counter(static_cast(batch_size), Counter::kIsIterationInvariantRate | Counter::kInvert); } BENCHMARK(BM_SqliteStepAndResult)->Apply(BenchmarkArgs); static void BM_SqliteCountOne(benchmark::State& state) { size_t batch_size = static_cast(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(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(batch_size), Counter::kIsIterationInvariantRate | Counter::kInvert); } BENCHMARK(BM_SqliteCountOne)->Apply(SizeBenchmarkArgs); } // namespace