xref: /aosp_15_r20/external/perfetto/src/trace_processor/sqlite/sqlite_vtable_benchmark.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 // Copyright (C) 2020 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // Benchmark for the SQLite VTable interface.
16 // This benchmark measures the speed-of-light obtainable through a SQLite
17 // virtual table. The code here implements an ideal virtual table which fetches
18 // data in blocks and serves the xNext/xCol requests by just advancing a pointer
19 // in a buffer. This is to have a fair estimate w.r.t. cache-misses and pointer
20 // chasing of what an upper-bound can be for a virtual table implementation.
21 
22 #include <cstddef>
23 #include <cstdint>
24 #include <cstdlib>
25 #include <random>
26 #include <string>
27 #include <vector>
28 
29 #include <benchmark/benchmark.h>
30 #include <sqlite3.h>
31 
32 #include "perfetto/base/compiler.h"
33 #include "perfetto/base/logging.h"
34 #include "src/trace_processor/sqlite/scoped_db.h"
35 
36 namespace {
37 
38 using benchmark::Counter;
39 using perfetto::trace_processor::ScopedDb;
40 using perfetto::trace_processor::ScopedStmt;
41 
IsBenchmarkFunctionalOnly()42 bool IsBenchmarkFunctionalOnly() {
43   return getenv("BENCHMARK_FUNCTIONAL_TEST_ONLY") != nullptr;
44 }
45 
SizeBenchmarkArgs(benchmark::internal::Benchmark * b)46 void SizeBenchmarkArgs(benchmark::internal::Benchmark* b) {
47   if (IsBenchmarkFunctionalOnly()) {
48     b->Ranges({{1024, 1024}});
49   } else {
50     b->RangeMultiplier(2)->Ranges({{1024, 1024 * 128}});
51   }
52 }
53 
BenchmarkArgs(benchmark::internal::Benchmark * b)54 void BenchmarkArgs(benchmark::internal::Benchmark* b) {
55   if (IsBenchmarkFunctionalOnly()) {
56     b->Ranges({{1024, 1024}, {1, 1}});
57   } else {
58     b->RangeMultiplier(2)->Ranges({{1024, 1024 * 128}, {1, 8}});
59   }
60 }
61 
62 struct VtabContext {
63   size_t batch_size;
64   size_t num_cols;
65   bool end_on_batch;
66 };
67 
68 class BenchmarkCursor : public sqlite3_vtab_cursor {
69  public:
BenchmarkCursor(size_t num_cols,size_t batch_size,bool end_on_batch)70   explicit BenchmarkCursor(size_t num_cols,
71                            size_t batch_size,
72                            bool end_on_batch)
73       : num_cols_(num_cols),
74         batch_size_(batch_size),
75         end_on_batch_(end_on_batch),
76         rnd_engine_(kRandomSeed) {
77     column_buffer_.resize(num_cols);
78     for (auto& col : column_buffer_)
79       col.resize(batch_size);
80     RandomFill();
81   }
82   PERFETTO_NO_INLINE int Next();
83   PERFETTO_NO_INLINE int Column(sqlite3_context* ctx, int);
84   PERFETTO_NO_INLINE int Eof() const;
85   void RandomFill();
86 
87  private:
88   size_t num_cols_ = 0;
89   size_t batch_size_ = 0;
90   bool eof_ = false;
91   bool end_on_batch_ = false;
92   static constexpr uint32_t kRandomSeed = 476;
93 
94   uint32_t row_ = 0;
95   using ColBatch = std::vector<int64_t>;
96   std::vector<ColBatch> column_buffer_;
97 
98   std::minstd_rand0 rnd_engine_;
99 };
100 
RandomFill()101 void BenchmarkCursor::RandomFill() {
102   for (size_t col = 0; col < num_cols_; col++) {
103     for (size_t row = 0; row < batch_size_; row++) {
104       column_buffer_[col][row] = static_cast<int64_t>(rnd_engine_());
105     }
106   }
107 }
108 
Next()109 int BenchmarkCursor::Next() {
110   if (end_on_batch_) {
111     row_++;
112     eof_ = row_ == batch_size_;
113   } else {
114     row_ = (row_ + 1) % batch_size_;
115     if (row_ == 0)
116       RandomFill();
117   }
118   return SQLITE_OK;
119 }
120 
Eof() const121 int BenchmarkCursor::Eof() const {
122   return eof_;
123 }
124 
Column(sqlite3_context * ctx,int col_int)125 int BenchmarkCursor::Column(sqlite3_context* ctx, int col_int) {
126   const auto col = static_cast<size_t>(col_int);
127   PERFETTO_CHECK(col < column_buffer_.size());
128   sqlite3_result_int64(ctx, column_buffer_[col][row_]);
129   return SQLITE_OK;
130 }
131 
CreateDbAndRegisterVtable(sqlite3_module & module,VtabContext & context)132 ScopedDb CreateDbAndRegisterVtable(sqlite3_module& module,
133                                    VtabContext& context) {
134   struct BenchmarkVtab : public sqlite3_vtab {
135     size_t num_cols;
136     size_t batch_size;
137     bool end_on_batch;
138   };
139 
140   sqlite3_initialize();
141 
142   ScopedDb db;
143   sqlite3* raw_db = nullptr;
144   PERFETTO_CHECK(sqlite3_open(":memory:", &raw_db) == SQLITE_OK);
145   db.reset(raw_db);
146 
147   auto create_fn = [](sqlite3* xdb, void* aux, int, const char* const*,
148                       sqlite3_vtab** tab, char**) {
149     auto& _context = *static_cast<VtabContext*>(aux);
150     std::string sql = "CREATE TABLE x(";
151     for (size_t col = 0; col < _context.num_cols; col++)
152       sql += "c" + std::to_string(col) + " BIGINT,";
153     sql[sql.size() - 1] = ')';
154     int res = sqlite3_declare_vtab(xdb, sql.c_str());
155     PERFETTO_CHECK(res == SQLITE_OK);
156     auto* vtab = new BenchmarkVtab();
157     vtab->batch_size = _context.batch_size;
158     vtab->num_cols = _context.num_cols;
159     vtab->end_on_batch = _context.end_on_batch;
160     *tab = vtab;
161     return SQLITE_OK;
162   };
163 
164   auto destroy_fn = [](sqlite3_vtab* t) {
165     delete static_cast<BenchmarkVtab*>(t);
166     return SQLITE_OK;
167   };
168 
169   module.xCreate = create_fn;
170   module.xConnect = create_fn;
171   module.xDisconnect = destroy_fn;
172   module.xDestroy = destroy_fn;
173 
174   module.xOpen = [](sqlite3_vtab* tab, sqlite3_vtab_cursor** c) {
175     auto* vtab = static_cast<BenchmarkVtab*>(tab);
176     *c = new BenchmarkCursor(vtab->num_cols, vtab->batch_size,
177                              vtab->end_on_batch);
178     return SQLITE_OK;
179   };
180   module.xBestIndex = [](sqlite3_vtab*, sqlite3_index_info* idx) {
181     idx->orderByConsumed = true;
182     for (int i = 0; i < idx->nConstraint; ++i) {
183       idx->aConstraintUsage[i].omit = true;
184     }
185     return SQLITE_OK;
186   };
187   module.xClose = [](sqlite3_vtab_cursor* c) {
188     delete static_cast<BenchmarkCursor*>(c);
189     return SQLITE_OK;
190   };
191   module.xFilter = [](sqlite3_vtab_cursor*, int, const char*, int,
192                       sqlite3_value**) { return SQLITE_OK; };
193   module.xNext = [](sqlite3_vtab_cursor* c) {
194     return static_cast<BenchmarkCursor*>(c)->Next();
195   };
196   module.xEof = [](sqlite3_vtab_cursor* c) {
197     return static_cast<BenchmarkCursor*>(c)->Eof();
198   };
199   module.xColumn = [](sqlite3_vtab_cursor* c, sqlite3_context* a, int b) {
200     return static_cast<BenchmarkCursor*>(c)->Column(a, b);
201   };
202 
203   int res =
204       sqlite3_create_module_v2(*db, "benchmark", &module, &context, nullptr);
205   PERFETTO_CHECK(res == SQLITE_OK);
206 
207   return db;
208 }
209 
BM_SqliteStepAndResult(benchmark::State & state)210 void BM_SqliteStepAndResult(benchmark::State& state) {
211   auto batch_size = static_cast<size_t>(state.range(0));
212   auto num_cols = static_cast<size_t>(state.range(1));
213 
214   // Make sure the module outlives the ScopedDb. SQLite calls xDisconnect in
215   // the database close function and so this struct needs to be available then.
216   sqlite3_module module{};
217   VtabContext context{batch_size, num_cols, false};
218   ScopedDb db = CreateDbAndRegisterVtable(module, context);
219 
220   ScopedStmt stmt;
221   sqlite3_stmt* raw_stmt;
222   std::string sql = "SELECT * from benchmark";
223   int err = sqlite3_prepare_v2(*db, sql.c_str(), static_cast<int>(sql.size()),
224                                &raw_stmt, nullptr);
225   PERFETTO_CHECK(err == SQLITE_OK);
226   stmt.reset(raw_stmt);
227 
228   for (auto _ : state) {
229     for (size_t i = 0; i < batch_size; i++) {
230       PERFETTO_CHECK(sqlite3_step(*stmt) == SQLITE_ROW);
231       for (int col = 0; col < static_cast<int>(num_cols); col++) {
232         benchmark::DoNotOptimize(sqlite3_column_int64(*stmt, col));
233       }
234     }
235   }
236 
237   state.counters["s/row"] =
238       Counter(static_cast<double>(batch_size),
239               Counter::kIsIterationInvariantRate | Counter::kInvert);
240 }
241 
242 BENCHMARK(BM_SqliteStepAndResult)->Apply(BenchmarkArgs);
243 
BM_SqliteCountOne(benchmark::State & state)244 void BM_SqliteCountOne(benchmark::State& state) {
245   auto batch_size = static_cast<size_t>(state.range(0));
246 
247   // Make sure the module outlives the ScopedDb. SQLite calls xDisconnect in
248   // the database close function and so this struct needs to be available then.
249   sqlite3_module module{};
250   VtabContext context{batch_size, 1, true};
251   ScopedDb db = CreateDbAndRegisterVtable(module, context);
252 
253   ScopedStmt stmt;
254   sqlite3_stmt* raw_stmt;
255   std::string sql = "SELECT COUNT(1) from benchmark";
256   int err = sqlite3_prepare_v2(*db, sql.c_str(), static_cast<int>(sql.size()),
257                                &raw_stmt, nullptr);
258   PERFETTO_CHECK(err == SQLITE_OK);
259   stmt.reset(raw_stmt);
260 
261   for (auto _ : state) {
262     sqlite3_reset(raw_stmt);
263     PERFETTO_CHECK(sqlite3_step(*stmt) == SQLITE_ROW);
264     benchmark::DoNotOptimize(sqlite3_column_int64(*stmt, 0));
265     PERFETTO_CHECK(sqlite3_step(*stmt) == SQLITE_DONE);
266   }
267 
268   state.counters["s/row"] =
269       Counter(static_cast<double>(batch_size),
270               Counter::kIsIterationInvariantRate | Counter::kInvert);
271 }
272 
273 BENCHMARK(BM_SqliteCountOne)->Apply(SizeBenchmarkArgs);
274 
275 }  // namespace
276