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