• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <array>
23 #include <random>
24 
25 #include <benchmark/benchmark.h>
26 #include <sqlite3.h>
27 
28 #include "perfetto/base/compiler.h"
29 #include "src/trace_processor/sqlite/scoped_db.h"
30 
31 namespace {
32 
33 using benchmark::Counter;
34 using perfetto::trace_processor::ScopedDb;
35 using perfetto::trace_processor::ScopedStmt;
36 
IsBenchmarkFunctionalOnly()37 bool IsBenchmarkFunctionalOnly() {
38   return getenv("BENCHMARK_FUNCTIONAL_TEST_ONLY") != nullptr;
39 }
40 
SizeBenchmarkArgs(benchmark::internal::Benchmark * b)41 void SizeBenchmarkArgs(benchmark::internal::Benchmark* b) {
42   if (IsBenchmarkFunctionalOnly()) {
43     b->Ranges({{1024, 1024}});
44   } else {
45     b->RangeMultiplier(2)->Ranges({{1024, 1024 * 128}});
46   }
47 }
48 
BenchmarkArgs(benchmark::internal::Benchmark * b)49 void BenchmarkArgs(benchmark::internal::Benchmark* b) {
50   if (IsBenchmarkFunctionalOnly()) {
51     b->Ranges({{1024, 1024}, {1, 1}});
52   } else {
53     b->RangeMultiplier(2)->Ranges({{1024, 1024 * 128}, {1, 8}});
54   }
55 }
56 
57 struct VtabContext {
58   size_t batch_size;
59   size_t num_cols;
60   bool end_on_batch;
61 };
62 
63 class BenchmarkCursor : public sqlite3_vtab_cursor {
64  public:
BenchmarkCursor(size_t num_cols,size_t batch_size,bool end_on_batch)65   explicit BenchmarkCursor(size_t num_cols,
66                            size_t batch_size,
67                            bool end_on_batch)
68       : num_cols_(num_cols),
69         batch_size_(batch_size),
70         end_on_batch_(end_on_batch),
71         rnd_engine_(kRandomSeed) {
72     column_buffer_.resize(num_cols);
73     for (auto& col : column_buffer_)
74       col.resize(batch_size);
75     RandomFill();
76   }
77   PERFETTO_NO_INLINE int Next();
78   PERFETTO_NO_INLINE int Column(sqlite3_context* ctx, int);
79   PERFETTO_NO_INLINE int Eof();
80   void RandomFill();
81 
82  private:
83   size_t num_cols_ = 0;
84   size_t batch_size_ = 0;
85   bool eof_ = false;
86   bool end_on_batch_ = false;
87   static constexpr uint32_t kRandomSeed = 476;
88 
89   uint32_t row_ = 0;
90   using ColBatch = std::vector<int64_t>;
91   std::vector<ColBatch> column_buffer_;
92 
93   std::minstd_rand0 rnd_engine_;
94 };
95 
RandomFill()96 void BenchmarkCursor::RandomFill() {
97   for (size_t col = 0; col < num_cols_; col++) {
98     for (size_t row = 0; row < batch_size_; row++) {
99       column_buffer_[col][row] = static_cast<int64_t>(rnd_engine_());
100     }
101   }
102 }
103 
Next()104 int BenchmarkCursor::Next() {
105   if (end_on_batch_) {
106     row_++;
107     eof_ = row_ == batch_size_;
108   } else {
109     row_ = (row_ + 1) % batch_size_;
110     if (row_ == 0)
111       RandomFill();
112   }
113   return SQLITE_OK;
114 }
115 
Eof()116 int BenchmarkCursor::Eof() {
117   return eof_;
118 }
119 
Column(sqlite3_context * ctx,int col_int)120 int BenchmarkCursor::Column(sqlite3_context* ctx, int col_int) {
121   const auto col = static_cast<size_t>(col_int);
122   PERFETTO_CHECK(col < column_buffer_.size());
123   sqlite3_result_int64(ctx, column_buffer_[col][row_]);
124   return SQLITE_OK;
125 }
126 
CreateDbAndRegisterVtable(sqlite3_module & module,VtabContext & context)127 ScopedDb CreateDbAndRegisterVtable(sqlite3_module& module,
128                                    VtabContext& context) {
129   struct BenchmarkVtab : public sqlite3_vtab {
130     size_t num_cols;
131     size_t batch_size;
132     bool end_on_batch;
133   };
134 
135   sqlite3_initialize();
136 
137   ScopedDb db;
138   sqlite3* raw_db = nullptr;
139   PERFETTO_CHECK(sqlite3_open(":memory:", &raw_db) == SQLITE_OK);
140   db.reset(raw_db);
141 
142   auto create_fn = [](sqlite3* xdb, void* aux, int, const char* const*,
143                       sqlite3_vtab** tab, char**) {
144     auto& _context = *static_cast<VtabContext*>(aux);
145     std::string sql = "CREATE TABLE x(";
146     for (size_t col = 0; col < _context.num_cols; col++)
147       sql += "c" + std::to_string(col) + " BIGINT,";
148     sql[sql.size() - 1] = ')';
149     int res = sqlite3_declare_vtab(xdb, sql.c_str());
150     PERFETTO_CHECK(res == SQLITE_OK);
151     auto* vtab = new BenchmarkVtab();
152     vtab->batch_size = _context.batch_size;
153     vtab->num_cols = _context.num_cols;
154     vtab->end_on_batch = _context.end_on_batch;
155     *tab = vtab;
156     return SQLITE_OK;
157   };
158 
159   auto destroy_fn = [](sqlite3_vtab* t) {
160     delete static_cast<BenchmarkVtab*>(t);
161     return SQLITE_OK;
162   };
163 
164   module.xCreate = create_fn;
165   module.xConnect = create_fn;
166   module.xDisconnect = destroy_fn;
167   module.xDestroy = destroy_fn;
168 
169   module.xOpen = [](sqlite3_vtab* tab, sqlite3_vtab_cursor** c) {
170     auto* vtab = static_cast<BenchmarkVtab*>(tab);
171     *c = new BenchmarkCursor(vtab->num_cols, vtab->batch_size,
172                              vtab->end_on_batch);
173     return SQLITE_OK;
174   };
175   module.xBestIndex = [](sqlite3_vtab*, sqlite3_index_info* idx) {
176     idx->orderByConsumed = true;
177     for (int i = 0; i < idx->nConstraint; ++i) {
178       idx->aConstraintUsage[i].omit = true;
179     }
180     return SQLITE_OK;
181   };
182   module.xClose = [](sqlite3_vtab_cursor* c) {
183     delete static_cast<BenchmarkCursor*>(c);
184     return SQLITE_OK;
185   };
186   module.xFilter = [](sqlite3_vtab_cursor*, int, const char*, int,
187                       sqlite3_value**) { return SQLITE_OK; };
188   module.xNext = [](sqlite3_vtab_cursor* c) {
189     return static_cast<BenchmarkCursor*>(c)->Next();
190   };
191   module.xEof = [](sqlite3_vtab_cursor* c) {
192     return static_cast<BenchmarkCursor*>(c)->Eof();
193   };
194   module.xColumn = [](sqlite3_vtab_cursor* c, sqlite3_context* a, int b) {
195     return static_cast<BenchmarkCursor*>(c)->Column(a, b);
196   };
197 
198   int res =
199       sqlite3_create_module_v2(*db, "benchmark", &module, &context, nullptr);
200   PERFETTO_CHECK(res == SQLITE_OK);
201 
202   return db;
203 }
204 
BM_SqliteStepAndResult(benchmark::State & state)205 static void BM_SqliteStepAndResult(benchmark::State& state) {
206   size_t batch_size = static_cast<size_t>(state.range(0));
207   size_t num_cols = static_cast<size_t>(state.range(1));
208 
209   // Make sure the module outlives the ScopedDb. SQLite calls xDisconnect in
210   // the database close function and so this struct needs to be available then.
211   sqlite3_module module{};
212   VtabContext context{batch_size, num_cols, false};
213   ScopedDb db = CreateDbAndRegisterVtable(module, context);
214 
215   ScopedStmt stmt;
216   sqlite3_stmt* raw_stmt;
217   std::string sql = "SELECT * from benchmark";
218   int err = sqlite3_prepare_v2(*db, sql.c_str(), static_cast<int>(sql.size()),
219                                &raw_stmt, nullptr);
220   PERFETTO_CHECK(err == SQLITE_OK);
221   stmt.reset(raw_stmt);
222 
223   for (auto _ : state) {
224     for (size_t i = 0; i < batch_size; i++) {
225       PERFETTO_CHECK(sqlite3_step(*stmt) == SQLITE_ROW);
226       for (int col = 0; col < static_cast<int>(num_cols); col++) {
227         benchmark::DoNotOptimize(sqlite3_column_int64(*stmt, col));
228       }
229     }
230   }
231 
232   state.counters["s/row"] =
233       Counter(static_cast<double>(batch_size),
234               Counter::kIsIterationInvariantRate | Counter::kInvert);
235 }
236 
237 BENCHMARK(BM_SqliteStepAndResult)->Apply(BenchmarkArgs);
238 
BM_SqliteCountOne(benchmark::State & state)239 static void BM_SqliteCountOne(benchmark::State& state) {
240   size_t batch_size = static_cast<size_t>(state.range(0));
241 
242   // Make sure the module outlives the ScopedDb. SQLite calls xDisconnect in
243   // the database close function and so this struct needs to be available then.
244   sqlite3_module module{};
245   VtabContext context{batch_size, 1, true};
246   ScopedDb db = CreateDbAndRegisterVtable(module, context);
247 
248   ScopedStmt stmt;
249   sqlite3_stmt* raw_stmt;
250   std::string sql = "SELECT COUNT(1) from benchmark";
251   int err = sqlite3_prepare_v2(*db, sql.c_str(), static_cast<int>(sql.size()),
252                                &raw_stmt, nullptr);
253   PERFETTO_CHECK(err == SQLITE_OK);
254   stmt.reset(raw_stmt);
255 
256   for (auto _ : state) {
257     sqlite3_reset(raw_stmt);
258     PERFETTO_CHECK(sqlite3_step(*stmt) == SQLITE_ROW);
259     benchmark::DoNotOptimize(sqlite3_column_int64(*stmt, 0));
260     PERFETTO_CHECK(sqlite3_step(*stmt) == SQLITE_DONE);
261   }
262 
263   state.counters["s/row"] =
264       Counter(static_cast<double>(batch_size),
265               Counter::kIsIterationInvariantRate | Counter::kInvert);
266 }
267 
268 BENCHMARK(BM_SqliteCountOne)->Apply(SizeBenchmarkArgs);
269 
270 }  // namespace
271