The new QueryContext and ExecContext API's both take a driver.NamedValue instead of a driver.Value. Because pq internally uses driver.Value this means that the first thing that happens with both API's is a copy:
// Implement the "StmtExecContext" interface
func (st *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
list := make([]driver.Value, len(args))
for i, nv := range args {
list[i] = nv.Value
}
This means that every call to this function with arguments allocates. Note also that database/sql will use QueryContext if it exists, so every call from database/sql is going through that call path now:
// queryDC executes a query on the given connection.
// The connection gets released by the releaseConn function.
// The ctx context is from a query method and the txctx context is from an
// optional transaction context.
func (db *DB) queryDC(ctx, txctx context.Context, dc *driverConn, releaseConn func(error), query string, args []interface{}) (*Rows, error) {
queryerCtx, ok := dc.ci.(driver.QueryerContext)
var queryer driver.Queryer
if !ok {
queryer, ok = dc.ci.(driver.Queryer)
}
if ok {
var nvdargs []driver.NamedValue
var rowsi driver.Rows
var err error
withLock(dc, func() {
nvdargs, err = driverArgsConnLocked(dc.ci, nil, args)
if err != nil {
return
}
rowsi, err = ctxDriverQuery(ctx, queryerCtx, queryer, query, nvdargs)
})
Instead of using driver.Value internally, if all of the pq internal API's use driver.NamedValue, this saves an allocation in the most common case.
This patch improves performance on my rickover dequeue benchmark (github.com/kevinburke/rickover), which measures how fast I can get rows out of the database. I can try to get statistically significant results, but you can see it reduces the number of allocations and it's reasonable to assume that performance is also improved.
$ benchstat /tmp/old /tmp/new
name old time/op new time/op delta
Dequeue/Dequeue1-10 8.00ms ±10% 7.74ms ± 4% ~ (p=0.421 n=5+5)
name old speed new speed delta
Dequeue/Dequeue1-10 0.00B/s 0.00B/s ~ (all equal)
name old alloc/op new alloc/op delta
Dequeue/Dequeue1-10 12.3kB ±13% 12.1kB ± 2% ~ (p=0.690 n=5+5)
name old allocs/op new allocs/op delta
Dequeue/Dequeue1-10 160 ±13% 155 ± 1% ~ (p=1.000 n=5+5)
The new
QueryContext
andExecContext
API's both take adriver.NamedValue
instead of adriver.Value
. Becausepq
internally usesdriver.Value
this means that the first thing that happens with both API's is a copy:This means that every call to this function with arguments allocates. Note also that
database/sql
will use QueryContext if it exists, so every call from database/sql is going through that call path now:Instead of using
driver.Value
internally, if all of the pq internal API's usedriver.NamedValue
, this saves an allocation in the most common case.The patch implemented here: https://github.com/kevinburke/pq/compare/named-value?expand=1 improves on the PreparedSelect benchmark by about 4% on my Mac (the rest of the results appear to be noise)
This patch improves performance on my
rickover
dequeue benchmark (github.com/kevinburke/rickover), which measures how fast I can get rows out of the database. I can try to get statistically significant results, but you can see it reduces the number of allocations and it's reasonable to assume that performance is also improved.