Skip to content

Commit 934438a

Browse files
committed
cgosqlite: remove some cgo allocs
│ before │ after │ │ sec/op │ sec/op vs base │ WALHookAndExec-8 2.354µ ± 7% 2.465µ ± 28% ~ (p=0.812 n=10) Persist-8 2.054µ ± 1% 2.026µ ± 3% -1.36% (p=0.043 n=10) QueryRows100MixedTypes-8 42.79µ ± 1% 37.75µ ± 1% -11.78% (p=0.000 n=10) EmptyExec-8 378.2n ± 0% 355.2n ± 1% -6.09% (p=0.000 n=10) BeginTxNoop-8 9.445µ ± 38% 9.258µ ± 2% -1.98% (p=0.002 n=10) geomean 3.747µ 3.618µ -3.45% │ before │ after │ │ B/op │ B/op vs base │ WALHookAndExec-8 56.00 ± 0% 48.00 ± 0% -14.29% (p=0.000 n=10) Persist-8 488.0 ± 0% 449.0 ± 0% -7.99% (p=0.000 n=10) QueryRows100MixedTypes-8 7.422Ki ± 0% 4.281Ki ± 0% -42.32% (p=0.000 n=10) EmptyExec-8 40.00 ± 0% 32.00 ± 0% -20.00% (p=0.000 n=10) BeginTxNoop-8 656.0 ± 0% 624.0 ± 0% -4.88% (p=0.000 n=10) geomean 352.6 285.2 -19.12% │ before │ after │ │ allocs/op │ allocs/op vs base │ WALHookAndExec-8 3.000 ± 0% 2.000 ± 0% -33.33% (p=0.000 n=10) Persist-8 16.00 ± 0% 12.00 ± 0% -25.00% (p=0.000 n=10) QueryRows100MixedTypes-8 609.0 ± 0% 207.0 ± 0% -66.01% (p=0.000 n=10) EmptyExec-8 2.000 ± 0% 1.000 ± 0% -50.00% (p=0.000 n=10) BeginTxNoop-8 17.00 ± 0% 13.00 ± 0% -23.53% (p=0.000 n=10) geomean 15.83 9.163 -42.12% Updates tailscale/corp#9919 Signed-off-by: Brad Fitzpatrick <[email protected]>
1 parent 85e398e commit 934438a

File tree

2 files changed

+98
-51
lines changed

2 files changed

+98
-51
lines changed

cgosqlite/cgosqlite.go

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,30 @@ type DB struct {
9090
declTypes map[string]string
9191
}
9292

93+
// cStmt is a wrapper around an sqlite3 *sqlite3_stmt. Except it's stored as
94+
// uintptr to avoid allocations due to poor interactions between cgo's pointer
95+
// checker and Go's escape analysis.
96+
//
97+
// The ptr method returns the value as a pointer, for call sites that haven't
98+
// yet or don't need the optimization. This lets us migrate incrementally.
99+
//
100+
// See http://go/corp/9919.
101+
type cStmt struct {
102+
v C.handle_sqlite3_stmt
103+
}
104+
105+
// cStmtFromPtr returns a cStmt from a C pointer.
106+
func cStmtFromPtr(p *C.sqlite3_stmt) cStmt {
107+
return cStmt{v: C.handle_sqlite3_stmt(uintptr(unsafe.Pointer(p)))}
108+
}
109+
110+
func (h cStmt) int() C.handle_sqlite3_stmt { return h.v }
111+
func (h cStmt) ptr() *C.sqlite3_stmt { return (*C.sqlite3_stmt)(unsafe.Pointer(uintptr(h.v))) }
112+
93113
// Stmt implements sqliteh.Stmt.
94114
type Stmt struct {
95115
db *DB
96-
stmt *C.sqlite3_stmt
116+
stmt cStmt
97117
start C.struct_timespec
98118

99119
// used as scratch space when calling into cgo
@@ -199,60 +219,60 @@ func (db *DB) Prepare(query string, prepFlags sqliteh.PrepareFlags) (stmt sqlite
199219
return nil, "", err
200220
}
201221
remainingQuery = query[len(query)-int(C.strlen(csqlTail)):]
202-
return &Stmt{db: db, stmt: cstmt}, remainingQuery, nil
222+
return &Stmt{db: db, stmt: cStmtFromPtr(cstmt)}, remainingQuery, nil
203223
}
204224

205225
func (stmt *Stmt) DBHandle() sqliteh.DB {
206-
cdb := C.sqlite3_db_handle(stmt.stmt)
226+
cdb := C.sqlite3_db_handle(stmt.stmt.ptr())
207227
if cdb != nil {
208228
return &DB{db: cdb}
209229
}
210230
return nil
211231
}
212232

213233
func (stmt *Stmt) SQL() string {
214-
return C.GoString(C.sqlite3_sql(stmt.stmt))
234+
return C.GoString(C.sqlite3_sql(stmt.stmt.ptr()))
215235
}
216236

217237
func (stmt *Stmt) ExpandedSQL() string {
218-
return C.GoString(C.sqlite3_expanded_sql(stmt.stmt))
238+
return C.GoString(C.sqlite3_expanded_sql(stmt.stmt.ptr()))
219239
}
220240

221241
func (stmt *Stmt) Reset() error {
222-
return errCode(C.sqlite3_reset(stmt.stmt))
242+
return errCode(C.sqlite3_reset(stmt.stmt.ptr()))
223243
}
224244

225245
func (stmt *Stmt) Finalize() error {
226-
return errCode(C.sqlite3_finalize(stmt.stmt))
246+
return errCode(C.sqlite3_finalize(stmt.stmt.ptr()))
227247
}
228248

229249
func (stmt *Stmt) ClearBindings() error {
230-
return errCode(C.sqlite3_clear_bindings(stmt.stmt))
250+
return errCode(C.sqlite3_clear_bindings(stmt.stmt.ptr()))
231251
}
232252

233253
func (stmt *Stmt) ResetAndClear() (time.Duration, error) {
234254
if stmt.start != (C.struct_timespec{}) {
235255
stmt.duration = 0
236-
err := errCode(C.reset_and_clear(stmt.stmt, &stmt.start, &stmt.duration))
256+
err := errCode(C.reset_and_clear(stmt.stmt.int(), &stmt.start, &stmt.duration))
237257
return time.Duration(stmt.duration), err
238258
}
239-
return 0, errCode(C.reset_and_clear(stmt.stmt, nil, nil))
259+
return 0, errCode(C.reset_and_clear(stmt.stmt.int(), nil, nil))
240260
}
241261

242262
func (stmt *Stmt) StartTimer() {
243263
C.monotonic_clock_gettime(&stmt.start)
244264
}
245265

246266
func (stmt *Stmt) ColumnDatabaseName(col int) string {
247-
return C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_database_name(stmt.stmt, C.int(col)))))
267+
return C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_database_name(stmt.stmt.ptr(), C.int(col)))))
248268
}
249269

250270
func (stmt *Stmt) ColumnTableName(col int) string {
251-
return C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_table_name(stmt.stmt, C.int(col)))))
271+
return C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_table_name(stmt.stmt.ptr(), C.int(col)))))
252272
}
253273

254274
func (stmt *Stmt) Step() (row bool, err error) {
255-
res := C.sqlite3_step(stmt.stmt)
275+
res := C.ts_sqlite3_step(stmt.stmt.int())
256276
switch res {
257277
case C.SQLITE_ROW:
258278
return true, nil
@@ -265,7 +285,7 @@ func (stmt *Stmt) Step() (row bool, err error) {
265285

266286
func (stmt *Stmt) StepResult() (row bool, lastInsertRowID, changes int64, d time.Duration, err error) {
267287
stmt.rowid, stmt.changes, stmt.duration = 0, 0, 0
268-
res := C.step_result(stmt.stmt, &stmt.rowid, &stmt.changes, &stmt.duration)
288+
res := C.step_result(stmt.stmt.int(), &stmt.rowid, &stmt.changes, &stmt.duration)
269289
lastInsertRowID = int64(stmt.rowid)
270290
changes = int64(stmt.changes)
271291
d = time.Duration(stmt.duration)
@@ -281,51 +301,51 @@ func (stmt *Stmt) StepResult() (row bool, lastInsertRowID, changes int64, d time
281301
}
282302

283303
func (stmt *Stmt) BindDouble(col int, val float64) error {
284-
return errCode(C.sqlite3_bind_double(stmt.stmt, C.int(col), C.double(val)))
304+
return errCode(C.sqlite3_bind_double(stmt.stmt.ptr(), C.int(col), C.double(val)))
285305
}
286306

287307
func (stmt *Stmt) BindInt64(col int, val int64) error {
288-
return errCode(C.sqlite3_bind_int64(stmt.stmt, C.int(col), C.sqlite3_int64(val)))
308+
return errCode(C.sqlite3_bind_int64(stmt.stmt.ptr(), C.int(col), C.sqlite3_int64(val)))
289309
}
290310

291311
func (stmt *Stmt) BindNull(col int) error {
292-
return errCode(C.sqlite3_bind_null(stmt.stmt, C.int(col)))
312+
return errCode(C.sqlite3_bind_null(stmt.stmt.ptr(), C.int(col)))
293313
}
294314

295315
func (stmt *Stmt) BindText64(col int, val string) error {
296316
if len(val) == 0 {
297-
return errCode(C.bind_text64_empty(stmt.stmt, C.int(col)))
317+
return errCode(C.bind_text64_empty(stmt.stmt.int(), C.int(col)))
298318
}
299319
v := C.CString(val) // freed by sqlite
300-
return errCode(C.bind_text64(stmt.stmt, C.int(col), v, C.sqlite3_uint64(len(val))))
320+
return errCode(C.bind_text64(stmt.stmt.int(), C.int(col), v, C.sqlite3_uint64(len(val))))
301321
}
302322

303323
func (stmt *Stmt) BindZeroBlob64(col int, n uint64) error {
304-
return errCode(C.sqlite3_bind_zeroblob64(stmt.stmt, C.int(col), C.sqlite3_uint64(n)))
324+
return errCode(C.sqlite3_bind_zeroblob64(stmt.stmt.ptr(), C.int(col), C.sqlite3_uint64(n)))
305325
}
306326

307327
func (stmt *Stmt) BindBlob64(col int, val []byte) error {
308328
var str *C.char
309329
if len(val) > 0 {
310330
str = (*C.char)(unsafe.Pointer(&val[0]))
311331
}
312-
return errCode(C.bind_blob64(stmt.stmt, C.int(col), str, C.sqlite3_uint64(len(val))))
332+
return errCode(C.bind_blob64(stmt.stmt.int(), C.int(col), str, C.sqlite3_uint64(len(val))))
313333
}
314334

315335
func (stmt *Stmt) BindParameterCount() int {
316-
return int(C.sqlite3_bind_parameter_count(stmt.stmt))
336+
return int(C.sqlite3_bind_parameter_count(stmt.stmt.ptr()))
317337
}
318338

319339
func (stmt *Stmt) BindParameterName(col int) string {
320-
cstr := C.sqlite3_bind_parameter_name(stmt.stmt, C.int(col))
340+
cstr := C.sqlite3_bind_parameter_name(stmt.stmt.ptr(), C.int(col))
321341
if cstr == nil {
322342
return ""
323343
}
324344
return C.GoString(cstr)
325345
}
326346

327347
func (stmt *Stmt) BindParameterIndex(name string) int {
328-
return int(C.bind_parameter_index(stmt.stmt, name))
348+
return int(C.bind_parameter_index(stmt.stmt.int(), name))
329349
}
330350

331351
func (stmt *Stmt) BindParameterIndexSearch(name string) int {
@@ -340,51 +360,45 @@ func (stmt *Stmt) BindParameterIndexSearch(name string) int {
340360
}
341361

342362
func (stmt *Stmt) ColumnCount() int {
343-
return int(C.sqlite3_column_count(stmt.stmt))
363+
return int(C.sqlite3_column_count(stmt.stmt.ptr()))
344364
}
345365

346366
func (stmt *Stmt) ColumnName(col int) string {
347-
return C.GoString(C.sqlite3_column_name(stmt.stmt, C.int(col)))
367+
return C.GoString(C.sqlite3_column_name(stmt.stmt.ptr(), C.int(col)))
348368
}
349369

350370
func (stmt *Stmt) ColumnText(col int) string {
351-
str := (*C.char)(unsafe.Pointer(C.sqlite3_column_text(stmt.stmt, C.int(col))))
352-
n := C.sqlite3_column_bytes(stmt.stmt, C.int(col))
371+
str := (*C.char)(unsafe.Pointer(C.ts_sqlite3_column_text(stmt.stmt.int(), C.int(col))))
372+
n := C.ts_sqlite3_column_bytes(stmt.stmt.int(), C.int(col))
353373
if str == nil || n == 0 {
354374
return ""
355375
}
356376
return C.GoStringN(str, n)
357377
}
358378

359379
func (stmt *Stmt) ColumnBlob(col int) []byte {
360-
res := C.sqlite3_column_blob(stmt.stmt, C.int(col))
380+
res := C.sqlite3_column_blob(stmt.stmt.ptr(), C.int(col))
361381
if res == nil {
362382
return nil
363383
}
364-
n := int(C.sqlite3_column_bytes(stmt.stmt, C.int(col)))
365-
366-
slice := struct {
367-
data unsafe.Pointer
368-
len int
369-
cap int
370-
}{data: unsafe.Pointer(res), len: n, cap: n}
371-
return *(*[]byte)(unsafe.Pointer(&slice))
384+
n := int(C.ts_sqlite3_column_bytes(stmt.stmt.int(), C.int(col)))
385+
return unsafe.Slice((*byte)(unsafe.Pointer(res)), n)
372386
}
373387

374388
func (stmt *Stmt) ColumnDouble(col int) float64 {
375-
return float64(C.sqlite3_column_double(stmt.stmt, C.int(col)))
389+
return float64(C.ts_sqlite3_column_double(stmt.stmt.int(), C.int(col)))
376390
}
377391

378392
func (stmt *Stmt) ColumnInt64(col int) int64 {
379-
return int64(C.sqlite3_column_int64(stmt.stmt, C.int(col)))
393+
return int64(C.ts_sqlite3_column_int64(stmt.stmt.int(), C.int(col)))
380394
}
381395

382396
func (stmt *Stmt) ColumnType(col int) sqliteh.ColumnType {
383-
return sqliteh.ColumnType(C.sqlite3_column_type(stmt.stmt, C.int(col)))
397+
return sqliteh.ColumnType(C.ts_sqlite3_column_type(stmt.stmt.int(), C.int(col)))
384398
}
385399

386400
func (stmt *Stmt) ColumnDeclType(col int) string {
387-
cstr := C.sqlite3_column_decltype(stmt.stmt, C.int(col))
401+
cstr := C.sqlite3_column_decltype(stmt.stmt.ptr(), C.int(col))
388402
if cstr == nil {
389403
return ""
390404
}

cgosqlite/cgosqlite.h

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
1+
#include "stdint.h"
2+
3+
// uintptr versions of sqlite3 pointer types, to avoid allocations
4+
// in cgo code. (go/corp/9919)
5+
typedef uintptr_t handle_sqlite3_stmt; // a *sqlite3_stmt
6+
typedef uintptr_t handle_sqlite3; // a *sqlite3 (DB conn)
7+
18
// Forward decls because the warnings make debugging painful.
29
size_t _GoStringLen(_GoString_ s);
310
const char *_GoStringPtr(_GoString_ s);
411

512
// Helper methods to deal with int <-> pointer pain.
613

7-
static int bind_text64(sqlite3_stmt* stmt, int col, const char* str, sqlite3_uint64 len) {
8-
return sqlite3_bind_text64(stmt, col, str, len, free, SQLITE_UTF8);
14+
static int bind_text64(handle_sqlite3_stmt stmt, int col, const char* str, sqlite3_uint64 len) {
15+
return sqlite3_bind_text64((sqlite3_stmt*)(stmt), col, str, len, free, SQLITE_UTF8);
916
}
1017

11-
static int bind_text64_empty(sqlite3_stmt* stmt, int col) {
12-
return sqlite3_bind_text64(stmt, col, "", 0, SQLITE_STATIC, SQLITE_UTF8);
18+
static int bind_text64_empty(handle_sqlite3_stmt stmt, int col) {
19+
return sqlite3_bind_text64((sqlite3_stmt*)(stmt), col, "", 0, SQLITE_STATIC, SQLITE_UTF8);
1320
}
1421

15-
static int bind_blob64(sqlite3_stmt* stmt, int col, char* str, sqlite3_uint64 n) {
16-
return sqlite3_bind_blob64(stmt, col, str, n, SQLITE_TRANSIENT);
22+
static int bind_blob64(handle_sqlite3_stmt stmt, int col, char* str, sqlite3_uint64 n) {
23+
return sqlite3_bind_blob64((sqlite3_stmt*)(stmt), col, str, n, SQLITE_TRANSIENT);
1724
}
1825

1926
// We only need the Go string's memory for the duration of the call,
2027
// and the GC pins it for us if we pass the gostring_t to C, so we
2128
// do the conversion here instead of with C.CString.
22-
static int bind_parameter_index(sqlite3_stmt* stmt, _GoString_ s) {
29+
static int bind_parameter_index(handle_sqlite3_stmt stmt, _GoString_ s) {
2330
size_t n = _GoStringLen(s);
2431
const char *p = (const char *)_GoStringPtr(s);
2532

@@ -29,7 +36,7 @@ static int bind_parameter_index(sqlite3_stmt* stmt, _GoString_ s) {
2936
return 0;
3037
}
3138
memmove(zName, p, n);
32-
return sqlite3_bind_parameter_index(stmt, zName);
39+
return sqlite3_bind_parameter_index((sqlite3_stmt*)(stmt), zName);
3340
}
3441

3542
static void monotonic_clock_gettime(struct timespec* t) {
@@ -45,7 +52,8 @@ static int64_t ns_since(const struct timespec t1)
4552
}
4653

4754
// step_result combines several cgo calls to save overhead.
48-
static int step_result(sqlite3_stmt* stmt, sqlite3_int64* rowid, sqlite3_int64* changes, int64_t* duration_ns) {
55+
static int step_result(handle_sqlite3_stmt stmth, sqlite3_int64* rowid, sqlite3_int64* changes, int64_t* duration_ns) {
56+
sqlite3_stmt* stmt = (sqlite3_stmt*)(stmth);
4957
struct timespec t1;
5058
if (duration_ns) {
5159
monotonic_clock_gettime(&t1);
@@ -63,7 +71,8 @@ static int step_result(sqlite3_stmt* stmt, sqlite3_int64* rowid, sqlite3_int64*
6371
}
6472

6573
// reset_and_clear combines two cgo calls to save overhead.
66-
static int reset_and_clear(sqlite3_stmt* stmt, struct timespec* start, int64_t* duration_ns) {
74+
static int reset_and_clear(handle_sqlite3_stmt stmth, struct timespec* start, int64_t* duration_ns) {
75+
sqlite3_stmt* stmt = (sqlite3_stmt*)(stmth);
6776
int ret = sqlite3_reset(stmt);
6877
int ret2 = sqlite3_clear_bindings(stmt);
6978
if (duration_ns) {
@@ -88,3 +97,27 @@ static int wal_callback_into_go(void *userData, sqlite3 *db, const char *dbName,
8897
static void ts_sqlite3_wal_hook_go(sqlite3* db) {
8998
sqlite3_wal_hook(db, wal_callback_into_go, 0);
9099
}
100+
101+
static int ts_sqlite3_step(handle_sqlite3_stmt stmt) {
102+
return sqlite3_step((sqlite3_stmt*)(stmt));
103+
}
104+
105+
static const unsigned char *ts_sqlite3_column_text(handle_sqlite3_stmt stmt, int iCol) {
106+
return sqlite3_column_text((sqlite3_stmt*)(stmt), iCol);
107+
}
108+
109+
static int ts_sqlite3_column_type(handle_sqlite3_stmt stmt, int iCol) {
110+
return sqlite3_column_type((sqlite3_stmt*)(stmt), iCol);
111+
}
112+
113+
static int ts_sqlite3_column_bytes(handle_sqlite3_stmt stmt, int iCol) {
114+
return sqlite3_column_bytes((sqlite3_stmt*)(stmt), iCol);
115+
}
116+
117+
static double ts_sqlite3_column_double(handle_sqlite3_stmt stmt, int iCol) {
118+
return sqlite3_column_double((sqlite3_stmt*)(stmt), iCol);
119+
}
120+
121+
static sqlite3_int64 ts_sqlite3_column_int64(handle_sqlite3_stmt stmt, int iCol) {
122+
return sqlite3_column_int64((sqlite3_stmt*)(stmt), iCol);
123+
}

0 commit comments

Comments
 (0)