Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cgosqlite: add more string interns, and optimize #97

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 23 additions & 16 deletions cgosqlite/cgosqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,11 @@ func (stmt *Stmt) StartTimer() {
}

func (stmt *Stmt) ColumnDatabaseName(col int) string {
return C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_database_name(stmt.stmt.ptr(), C.int(col)))))
return internStringFromCString(C.sqlite3_column_database_name(stmt.stmt.ptr(), C.int(col)))
}

func (stmt *Stmt) ColumnTableName(col int) string {
return C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_table_name(stmt.stmt.ptr(), C.int(col)))))
return internStringFromCString(C.sqlite3_column_table_name(stmt.stmt.ptr(), C.int(col)))
}

func (stmt *Stmt) Step(colType []sqliteh.ColumnType) (row bool, err error) {
Expand Down Expand Up @@ -330,11 +330,7 @@ func (stmt *Stmt) BindParameterCount() int {
}

func (stmt *Stmt) BindParameterName(col int) string {
cstr := C.sqlite3_bind_parameter_name(stmt.stmt.ptr(), C.int(col))
if cstr == nil {
return ""
}
return C.GoString(cstr)
return internStringFromCString(C.sqlite3_bind_parameter_name(stmt.stmt.ptr(), C.int(col)))
}

func (stmt *Stmt) BindParameterIndex(name string) int {
Expand All @@ -357,7 +353,7 @@ func (stmt *Stmt) ColumnCount() int {
}

func (stmt *Stmt) ColumnName(col int) string {
return C.GoString(C.sqlite3_column_name(stmt.stmt.ptr(), C.int(col)))
return internStringFromCString(C.sqlite3_column_name(stmt.stmt.ptr(), C.int(col)))
}

func (stmt *Stmt) ColumnText(col int) string {
Expand Down Expand Up @@ -395,15 +391,15 @@ func (stmt *Stmt) ColumnDeclType(col int) string {
if cstr == nil {
return ""
}
clen := C.strlen(cstr)
b := (*[1 << 30]byte)(unsafe.Pointer(cstr))[0:clen]
bstr := (*byte)(unsafe.Pointer(cstr))
clen := findnull(bstr)
if stmt.db.declTypes == nil {
stmt.db.declTypes = make(map[string]string)
}
if res, found := stmt.db.declTypes[string(b)]; found {
if res, found := stmt.db.declTypes[unsafe.String(bstr, clen)]; found {
return res
}
res := string(b)
res := C.GoStringN(cstr, C.int(clen))
stmt.db.declTypes[res] = res
return res
}
Expand All @@ -415,18 +411,29 @@ func errCode(code C.int) error { return sqliteh.CodeAsError(sqliteh.Code(code))
// internCache contains interned strings.
var internCache sync.Map // string => string (key == value)

// stringFromBytes returns string(b), interned into a map forever. It's meant
// internStringFromBytes returns string(b), interned into a map forever. It's meant
// for use on hot, small strings from closed set (like database or table or
// column names) where it doesn't matter if it leaks forever.
func stringFromBytes(b []byte) string {
func internStringFromBytes(b []byte) string {
if len(b) == 0 {
return ""
}
v, _ := internCache.Load(unsafe.String(&b[0], len(b)))
return internStringFromPtr((*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
}

func internStringFromPtr(p *C.char, n C.int) string {
if n == 0 {
return ""
}
v, _ := internCache.Load(unsafe.String((*byte)(unsafe.Pointer(p)), int(n)))
if s, ok := v.(string); ok {
return s
}
s := string(b)
s := C.GoStringN(p, n)
internCache.Store(s, s)
return s
}

func internStringFromCString(p *C.char) string {
return internStringFromPtr(p, C.int(findnull((*byte)(unsafe.Pointer(p)))))
}
10 changes: 10 additions & 0 deletions cgosqlite/stubs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package cgosqlite

import _ "unsafe"

// findnull exposes the runtime.findnull function to the cgosqlite package, this
// is a wide instruction optimized page by page null byte search aka fast
// strlen.
//
//go:linkname findnull runtime.findnull
func findnull(*byte) int
2 changes: 1 addition & 1 deletion cgosqlite/walcallback.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func walCallbackGo(db *C.sqlite3, dbNameC *C.char, dbNameLen C.int, pages C.int)
}

dbNameB := unsafe.Slice((*byte)(unsafe.Pointer(dbNameC)), dbNameLen)
dbName := stringFromBytes(dbNameB)
dbName := internStringFromBytes(dbNameB)
hook(dbName, int(pages))
return C.int(0) // result's kinda useless
}