Skip to content

Commit 041a992

Browse files
authored
Reduce per-row allocations (#130)
Modify several internal functions to avoid allocating unnecessary memory. This commit employs three strategies: - Allocate a scans slice only when scanning the first row, and store it inside the RowScanner to reuse on subsequent rows. - When scanning into slices, use more sophistocated reflection code to extend the slie while avoid temporary slice header allocations. - When scanning into slices of non-pointers, scan directly into the slice index, rather than allocate a new value and copy after scanning.
1 parent 981a63a commit 041a992

File tree

2 files changed

+43
-16
lines changed

2 files changed

+43
-16
lines changed

dbscan/dbscan.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -299,19 +299,37 @@ func (api *API) parseSliceDestination(dst interface{}) (*sliceDestinationMeta, e
299299
}
300300

301301
func scanSliceElement(rs *RowScanner, sliceMeta *sliceDestinationMeta) error {
302-
dstValPtr := reflect.New(sliceMeta.elementBaseType)
302+
s := sliceMeta.val
303+
l := s.Len()
304+
growSliceByOne(s)
305+
var dstValPtr reflect.Value
306+
if sliceMeta.elementByPtr {
307+
dstValPtr = reflect.New(sliceMeta.elementBaseType)
308+
s.Index(l).Set(dstValPtr)
309+
} else {
310+
dstValPtr = s.Index(l).Addr()
311+
}
303312
if err := rs.Scan(dstValPtr.Interface()); err != nil {
313+
// Undo growing the slice. Zero the value to ensure it doesn't retain garbage.
314+
s.Index(l).Set(reflect.Zero(s.Type().Elem()))
315+
s.SetLen(l)
304316
return fmt.Errorf("scanning: %w", err)
305317
}
306-
var elemVal reflect.Value
307-
if sliceMeta.elementByPtr {
308-
elemVal = dstValPtr
309-
} else {
310-
elemVal = dstValPtr.Elem()
318+
return nil
319+
}
320+
321+
func growSliceByOne(s reflect.Value) {
322+
// In go 1.20 and above, this could be made simpler (and possibly more efficient)
323+
// by using Value.Grow.
324+
l := s.Len()
325+
c := s.Cap()
326+
if l < c {
327+
s.SetLen(l + 1)
328+
return
311329
}
312330

313-
sliceMeta.val.Set(reflect.Append(sliceMeta.val, elemVal))
314-
return nil
331+
t := s.Type().Elem()
332+
s.Set(reflect.Append(s, reflect.Zero(t)))
315333
}
316334

317335
// ScanRow is a package-level helper function that uses the DefaultAPI object.

dbscan/rowscanner.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type RowScanner struct {
3434
started bool
3535
scanFn func(dstVal reflect.Value) error
3636
start startScannerFunc
37+
scans []any
3738
}
3839

3940
// NewRowScanner is a package-level helper function that uses the DefaultAPI object.
@@ -124,13 +125,15 @@ func startScanner(rs *RowScanner, dstValue reflect.Value) error {
124125
}
125126

126127
func (rs *RowScanner) scanStruct(structValue reflect.Value) error {
127-
scans := make([]interface{}, len(rs.columns))
128+
if rs.scans == nil {
129+
rs.scans = make([]interface{}, len(rs.columns))
130+
}
128131
for i, column := range rs.columns {
129132
fieldIndex, ok := rs.columnToFieldIndex[column]
130133
if !ok {
131134
if rs.api.allowUnknownColumns {
132135
var tmp interface{}
133-
scans[i] = &tmp
136+
rs.scans[i] = &tmp
134137
continue
135138
}
136139
return fmt.Errorf(
@@ -144,9 +147,9 @@ func (rs *RowScanner) scanStruct(structValue reflect.Value) error {
144147
initializeNested(structValue, fieldIndex)
145148

146149
fieldVal := structValue.FieldByIndex(fieldIndex)
147-
scans[i] = fieldVal.Addr().Interface()
150+
rs.scans[i] = fieldVal.Addr().Interface()
148151
}
149-
if err := rs.rows.Scan(scans...); err != nil {
152+
if err := rs.rows.Scan(rs.scans...); err != nil {
150153
return fmt.Errorf("scany: scan row into struct fields: %w", err)
151154
}
152155
return nil
@@ -157,14 +160,16 @@ func (rs *RowScanner) scanMap(mapValue reflect.Value) error {
157160
mapValue.Set(reflect.MakeMap(mapValue.Type()))
158161
}
159162

160-
scans := make([]interface{}, len(rs.columns))
163+
if rs.scans == nil {
164+
rs.scans = make([]interface{}, len(rs.columns))
165+
}
161166
values := make([]reflect.Value, len(rs.columns))
162167
for i := range rs.columns {
163168
valuePtr := reflect.New(rs.mapElementType)
164-
scans[i] = valuePtr.Interface()
169+
rs.scans[i] = valuePtr.Interface()
165170
values[i] = valuePtr.Elem()
166171
}
167-
if err := rs.rows.Scan(scans...); err != nil {
172+
if err := rs.rows.Scan(rs.scans...); err != nil {
168173
return fmt.Errorf("scany: scan rows into map: %w", err)
169174
}
170175
// We can't set reflect values into destination map before scanning them,
@@ -179,7 +184,11 @@ func (rs *RowScanner) scanMap(mapValue reflect.Value) error {
179184
}
180185

181186
func (rs *RowScanner) scanPrimitive(value reflect.Value) error {
182-
if err := rs.rows.Scan(value.Addr().Interface()); err != nil {
187+
if rs.scans == nil {
188+
rs.scans = make([]interface{}, 1)
189+
}
190+
rs.scans[0] = value.Addr().Interface()
191+
if err := rs.rows.Scan(rs.scans...); err != nil {
183192
return fmt.Errorf("scany: scan row value into a primitive type: %w", err)
184193
}
185194
return nil

0 commit comments

Comments
 (0)