Skip to content

Commit 4954048

Browse files
committed
reduce CGO call overhead for exec and bind paths
- Add _sqlite3_exec_no_args() C function that combines prepare+step+finalize into a single CGO crossing for parameterless exec (most common case) - Add _sqlite3_reset_clear() C function that combines sqlite3_reset and sqlite3_clear_bindings into a single CGO crossing - Use semaphore channel instead of result struct channel in context-aware exec/Next paths to reduce allocations - Use time.AppendFormat with stack buffer to avoid heap allocation in time.Time binding - Optimize bindNamedIndices to reuse a single buffer instead of 3 separate C.CString allocations - Remove intermediate bindIndices slice allocation in named parameter binding path - Pass explicit query length to sqlite3_prepare_v2 to avoid C-side strlen benchstat (n=8): BenchmarkExec: -29.44% sec/op, -50% B/op, -33% allocs/op BenchmarkQuery: -9.83% sec/op BenchmarkParams: -6.38% sec/op geomean: -6.72% sec/op
1 parent 4a311ff commit 4954048

1 file changed

Lines changed: 139 additions & 70 deletions

File tree

sqlite3.go

Lines changed: 139 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ _sqlite3_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
7878
return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
7979
}
8080
81+
static int
82+
_sqlite3_prepare_v2_nolen(sqlite3 *db, const char *zSql, int nBytes, sqlite3_stmt **ppStmt, const char **pzTail)
83+
{
84+
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
85+
return _sqlite3_prepare_v2_blocking(db, zSql, nBytes, ppStmt, pzTail);
86+
#else
87+
return sqlite3_prepare_v2(db, zSql, nBytes, ppStmt, pzTail);
88+
#endif
89+
}
90+
8191
typedef struct {
8292
int typ;
8393
sqlite3_int64 i64;
@@ -126,6 +136,50 @@ _sqlite3_exec(sqlite3* db, const char* pcmd, long long* rowid, long long* change
126136
return rv;
127137
}
128138
139+
// Combined prepare+step+finalize for simple exec without parameters.
140+
// Reduces CGO crossings from ~6 to 1 for the common no-args exec case.
141+
static int
142+
_sqlite3_exec_no_args(sqlite3* db, const char* zSql, int nBytes, long long* rowid, long long* changes, const char** pzTail)
143+
{
144+
sqlite3_stmt *stmt = 0;
145+
const char *tail = 0;
146+
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
147+
int rv = _sqlite3_prepare_v2_blocking(db, zSql, nBytes, &stmt, &tail);
148+
#else
149+
int rv = sqlite3_prepare_v2(db, zSql, nBytes, &stmt, &tail);
150+
#endif
151+
if (rv != SQLITE_OK) {
152+
*pzTail = 0;
153+
return rv;
154+
}
155+
if (stmt == 0) {
156+
// Empty statement
157+
*rowid = 0;
158+
*changes = 0;
159+
*pzTail = tail;
160+
return SQLITE_OK;
161+
}
162+
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
163+
rv = _sqlite3_step_row_blocking(stmt, rowid, changes);
164+
#else
165+
rv = sqlite3_step(stmt);
166+
*rowid = (long long) sqlite3_last_insert_rowid(db);
167+
*changes = (long long) sqlite3_changes(db);
168+
#endif
169+
sqlite3_finalize(stmt);
170+
*pzTail = tail;
171+
return rv;
172+
}
173+
174+
// Combined reset + clear_bindings in a single C call to reduce CGO crossings.
175+
static int
176+
_sqlite3_reset_clear(sqlite3_stmt* stmt)
177+
{
178+
int rv = sqlite3_reset(stmt);
179+
sqlite3_clear_bindings(stmt);
180+
return rv;
181+
}
182+
129183
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
130184
extern int _sqlite3_step_blocking(sqlite3_stmt *stmt);
131185
extern int _sqlite3_step_row_blocking(sqlite3_stmt* stmt, long long* rowid, long long* changes);
@@ -886,17 +940,15 @@ func lastError(db *C.sqlite3) error {
886940

887941
// Exec implements Execer.
888942
func (c *SQLiteConn) Exec(query string, args []driver.Value) (driver.Result, error) {
889-
list := make([]driver.NamedValue, len(args))
890-
for i, v := range args {
891-
list[i] = driver.NamedValue{
892-
Ordinal: i + 1,
893-
Value: v,
894-
}
895-
}
896-
return c.exec(context.Background(), query, list)
943+
return c.exec(context.Background(), query, valueToNamedValue(args))
897944
}
898945

899946
func (c *SQLiteConn) exec(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
947+
// Fast path: no args, no context cancellation → single CGO call per statement
948+
if len(args) == 0 && ctx.Done() == nil {
949+
return c.execNoArgs(query)
950+
}
951+
900952
start := 0
901953
for {
902954
s, err := c.prepare(ctx, query)
@@ -931,16 +983,34 @@ func (c *SQLiteConn) exec(ctx context.Context, query string, args []driver.Named
931983
}
932984
}
933985

934-
// Query implements Queryer.
935-
func (c *SQLiteConn) Query(query string, args []driver.Value) (driver.Rows, error) {
936-
list := make([]driver.NamedValue, len(args))
937-
for i, v := range args {
938-
list[i] = driver.NamedValue{
939-
Ordinal: i + 1,
940-
Value: v,
986+
// execNoArgs executes a query with no parameters in a single CGO call per statement.
987+
func (c *SQLiteConn) execNoArgs(query string) (driver.Result, error) {
988+
var res *SQLiteResult
989+
for len(query) > 0 {
990+
var rowid, changes C.longlong
991+
var tail *C.char
992+
pquery := C.CString(query)
993+
rv := C._sqlite3_exec_no_args(c.db, pquery, C.int(len(query)), &rowid, &changes, &tail)
994+
if tail != nil && *tail != '\000' {
995+
query = strings.TrimSpace(C.GoString(tail))
996+
} else {
997+
query = ""
941998
}
999+
C.free(unsafe.Pointer(pquery))
1000+
if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE {
1001+
return nil, c.lastError()
1002+
}
1003+
res = &SQLiteResult{id: int64(rowid), changes: int64(changes)}
1004+
}
1005+
if res == nil {
1006+
res = &SQLiteResult{0, 0}
9421007
}
943-
return c.query(context.Background(), query, list)
1008+
return res, nil
1009+
}
1010+
1011+
// Query implements Queryer.
1012+
func (c *SQLiteConn) Query(query string, args []driver.Value) (driver.Rows, error) {
1013+
return c.query(context.Background(), query, valueToNamedValue(args))
9441014
}
9451015

9461016
func (c *SQLiteConn) query(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
@@ -1830,7 +1900,7 @@ func (c *SQLiteConn) prepare(ctx context.Context, query string) (driver.Stmt, er
18301900
defer C.free(unsafe.Pointer(pquery))
18311901
var s *C.sqlite3_stmt
18321902
var tail *C.char
1833-
rv := C._sqlite3_prepare_v2_internal(c.db, pquery, C.int(-1), &s, &tail)
1903+
rv := C._sqlite3_prepare_v2_internal(c.db, pquery, C.int(len(query)), &s, &tail)
18341904
if rv != C.SQLITE_OK {
18351905
return nil, c.lastError()
18361906
}
@@ -2002,7 +2072,12 @@ func bindValue(s *C.sqlite3_stmt, n C.int, value driver.Value) C.int {
20022072
}
20032073
return C._sqlite3_bind_blob(s, n, unsafe.Pointer(&v[0]), C.int(ln))
20042074
case time.Time:
2005-
return bindText(s, n, v.Format(SQLiteTimestampFormats[0]))
2075+
var buf [64]byte
2076+
b := v.AppendFormat(buf[:0], SQLiteTimestampFormats[0])
2077+
if len(b) == 0 {
2078+
return C._sqlite3_bind_text(s, n, (*C.char)(unsafe.Pointer(&placeHolder[0])), C.int(0))
2079+
}
2080+
return C._sqlite3_bind_text(s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
20062081
default:
20072082
return C.SQLITE_MISUSE
20082083
}
@@ -2015,12 +2090,17 @@ func (s *SQLiteStmt) bindNamedIndices(name string) [3]int {
20152090
return indices
20162091
}
20172092

2018-
prefixes := [3]string{":", "@", "$"}
2093+
// Build ":name\0" once and rewrite prefix byte to avoid 3 C.CString allocs.
2094+
buf := make([]byte, 1+len(name)+1) // prefix + name + null terminator
2095+
copy(buf[1:], name)
2096+
buf[len(buf)-1] = 0
2097+
cname := (*C.char)(unsafe.Pointer(&buf[0]))
2098+
20192099
var indices [3]int
2020-
for i := range prefixes {
2021-
cname := C.CString(prefixes[i] + name)
2100+
prefixes := [3]byte{':', '@', '$'}
2101+
for i, p := range prefixes {
2102+
buf[0] = p
20222103
indices[i] = int(C.sqlite3_bind_parameter_index(s.s, cname))
2023-
C.free(unsafe.Pointer(cname))
20242104
}
20252105
s.namedParams[name] = indices
20262106
return indices
@@ -2057,13 +2137,11 @@ func stmtArgs(args []driver.NamedValue, start, na int) []driver.NamedValue {
20572137
}
20582138

20592139
func (s *SQLiteStmt) bind(args []driver.NamedValue) error {
2060-
rv := C.sqlite3_reset(s.s)
2140+
rv := C._sqlite3_reset_clear(s.s)
20612141
if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE {
20622142
return s.c.lastError()
20632143
}
20642144

2065-
C.sqlite3_clear_bindings(s.s)
2066-
20672145
hasNamed := false
20682146
for i := range args {
20692147
if args[i].Name != "" {
@@ -2083,23 +2161,20 @@ func (s *SQLiteStmt) bind(args []driver.NamedValue) error {
20832161
return nil
20842162
}
20852163

2086-
bindIndices := make([][4]int, len(args))
2087-
for i, v := range args {
2088-
if v.Name == "" {
2089-
bindIndices[i][0] = v.Ordinal
2164+
for _, arg := range args {
2165+
if arg.Name == "" {
2166+
rv = bindValue(s.s, C.int(arg.Ordinal), arg.Value)
2167+
if rv != C.SQLITE_OK {
2168+
return s.c.lastError()
2169+
}
20902170
continue
20912171
}
2092-
indices := s.bindNamedIndices(v.Name)
2093-
copy(bindIndices[i][1:], indices[:])
2094-
}
2095-
2096-
for i, arg := range args {
2097-
for _, idx := range bindIndices[i] {
2172+
indices := s.bindNamedIndices(arg.Name)
2173+
for _, idx := range indices {
20982174
if idx == 0 {
20992175
continue
21002176
}
2101-
n := C.int(idx)
2102-
rv = bindValue(s.s, n, arg.Value)
2177+
rv = bindValue(s.s, C.int(idx), arg.Value)
21032178
if rv != C.SQLITE_OK {
21042179
return s.c.lastError()
21052180
}
@@ -2110,14 +2185,7 @@ func (s *SQLiteStmt) bind(args []driver.NamedValue) error {
21102185

21112186
// Query the statement with arguments. Return records.
21122187
func (s *SQLiteStmt) Query(args []driver.Value) (driver.Rows, error) {
2113-
list := make([]driver.NamedValue, len(args))
2114-
for i, v := range args {
2115-
list[i] = driver.NamedValue{
2116-
Ordinal: i + 1,
2117-
Value: v,
2118-
}
2119-
}
2120-
return s.query(context.Background(), list)
2188+
return s.query(context.Background(), valueToNamedValue(args))
21212189
}
21222190

21232191
func (s *SQLiteStmt) query(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
@@ -2156,14 +2224,18 @@ func (r *SQLiteResult) RowsAffected() (int64, error) {
21562224

21572225
// Exec execute the statement with arguments. Return result object.
21582226
func (s *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) {
2227+
return s.exec(context.Background(), valueToNamedValue(args))
2228+
}
2229+
2230+
func valueToNamedValue(args []driver.Value) []driver.NamedValue {
21592231
list := make([]driver.NamedValue, len(args))
21602232
for i, v := range args {
21612233
list[i] = driver.NamedValue{
21622234
Ordinal: i + 1,
21632235
Value: v,
21642236
}
21652237
}
2166-
return s.exec(context.Background(), list)
2238+
return list
21672239
}
21682240

21692241
func isInterruptErr(err error) bool {
@@ -2180,47 +2252,43 @@ func (s *SQLiteStmt) exec(ctx context.Context, args []driver.NamedValue) (driver
21802252
return s.execSync(args)
21812253
}
21822254

2183-
type result struct {
2184-
r driver.Result
2185-
err error
2186-
}
2187-
resultCh := make(chan result)
2188-
defer close(resultCh)
2255+
sema := make(chan struct{})
2256+
var r driver.Result
2257+
var err error
21892258
go func() {
2190-
r, err := s.execSync(args)
2191-
resultCh <- result{r, err}
2259+
r, err = s.execSync(args)
2260+
close(sema)
21922261
}()
2193-
var rv result
21942262
select {
2195-
case rv = <-resultCh:
2263+
case <-sema:
2264+
return r, err
21962265
case <-ctx.Done():
21972266
select {
2198-
case rv = <-resultCh: // no need to interrupt, operation completed in db
2267+
case <-sema: // no need to interrupt, operation completed in db
2268+
return r, err
21992269
default:
22002270
// this is still racy and can be no-op if executed between sqlite3_* calls in execSync.
22012271
C.sqlite3_interrupt(s.c.db)
2202-
rv = <-resultCh // wait for goroutine completed
2203-
if isInterruptErr(rv.err) {
2272+
<-sema // wait for goroutine completed
2273+
if isInterruptErr(err) {
22042274
return nil, ctx.Err()
22052275
}
2276+
return r, err
22062277
}
22072278
}
2208-
return rv.r, rv.err
22092279
}
22102280

22112281
func (s *SQLiteStmt) execSync(args []driver.NamedValue) (driver.Result, error) {
22122282
if err := s.bind(args); err != nil {
2213-
C.sqlite3_reset(s.s)
2214-
C.sqlite3_clear_bindings(s.s)
2283+
C._sqlite3_reset_clear(s.s)
22152284
return nil, err
22162285
}
22172286

22182287
var rowid, changes C.longlong
22192288
rv := C._sqlite3_step_row_internal(s.s, &rowid, &changes)
22202289
if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE {
22212290
err := s.c.lastError()
2222-
C.sqlite3_reset(s.s)
2223-
C.sqlite3_clear_bindings(s.s)
2291+
C._sqlite3_reset_clear(s.s)
22242292
return nil, err
22252293
}
22262294

@@ -2311,21 +2379,22 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
23112379
if rc.ctx.Done() == nil {
23122380
return rc.nextSyncLocked(dest)
23132381
}
2314-
resultCh := make(chan error)
2315-
defer close(resultCh)
2382+
sema := make(chan struct{})
2383+
var err error
23162384
go func() {
2317-
resultCh <- rc.nextSyncLocked(dest)
2385+
err = rc.nextSyncLocked(dest)
2386+
close(sema)
23182387
}()
23192388
select {
2320-
case err := <-resultCh:
2389+
case <-sema:
23212390
return err
23222391
case <-rc.ctx.Done():
23232392
select {
2324-
case <-resultCh: // no need to interrupt
2393+
case <-sema: // no need to interrupt
23252394
default:
23262395
// this is still racy and can be no-op if executed between sqlite3_* calls in nextSyncLocked.
23272396
C.sqlite3_interrupt(rc.s.c.db)
2328-
<-resultCh // ensure goroutine completed
2397+
<-sema // ensure goroutine completed
23292398
}
23302399
return rc.ctx.Err()
23312400
}

0 commit comments

Comments
 (0)