Skip to content

Commit 9509730

Browse files
Optimize memory allocations in query execution (#20)
* perf: reduce allocations in Exec, execIntoMaps and Next This change optimizes the main query execution paths to reduce memory allocations and improve performance. - In `QuerySet.Exec`, the slice for scanning row values is now allocated only once outside the loop. - In `execIntoMaps`, the slice for scannable values is also created only once. - In `resultIterator.Next`, the slice for scan values is now part of the iterator struct and is reused for each row, avoiding reallocation on each call. These changes significantly reduce the garbage collector pressure when processing large result sets. * perf: reduce allocations in Exec and Next This change optimizes the main query execution paths to reduce memory allocations and improve performance. - In `QuerySet.Exec`, the slice for scanning row values is now allocated only once outside the loop. - In `resultIterator.Next`, the slice for scan values is now part of the iterator struct and is reused for each row, avoiding reallocation on each call. These changes significantly reduce the garbage collector pressure when processing large result sets. An incorrect optimization for `execIntoMaps` was reverted. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 5622d50 commit 9509730

File tree

3 files changed

+9
-7
lines changed

3 files changed

+9
-7
lines changed

iterator.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type resultIterator[T any] struct {
1111
rows pgx.Rows
1212
orderedSelectors []ColumnAccessor
1313
params []any
14+
scanValues []any
1415
}
1516

1617
// Close closes the rows, making the connection ready for use again. It is safe
@@ -55,11 +56,10 @@ func (i *resultIterator[T]) HasNext() bool {
5556

5657
func (i *resultIterator[T]) Next() (*T, error) {
5758
entity := new(T)
58-
toScan := []any{}
59-
for _, each := range i.orderedSelectors {
60-
toScan = append(toScan, each.FieldValueToScan(entity))
59+
for j, each := range i.orderedSelectors {
60+
i.scanValues[j] = each.FieldValueToScan(entity)
6161
}
62-
if err := i.rows.Scan(toScan...); err != nil {
62+
if err := i.rows.Scan(i.scanValues...); err != nil {
6363
return nil, err
6464
}
6565
return entity, nil

iterator_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func TestResultIterator_Next(t *testing.T) {
2525
i := &resultIterator[testEntity]{
2626
rows: rows,
2727
orderedSelectors: selectors,
28+
scanValues: make([]any, len(selectors)),
2829
}
2930
if !i.HasNext() {
3031
t.Fatal("expected next")

queryset.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ func (d QuerySet[T]) Iterate(ctx context.Context, conn querier, parameters ...*Q
158158
rows: rows,
159159
orderedSelectors: ordered,
160160
params: params,
161+
scanValues: make([]any, len(ordered)),
161162
}, err
162163
}
163164

@@ -168,11 +169,11 @@ func (d QuerySet[T]) Exec(ctx context.Context, conn querier, parameters ...*Quer
168169
return
169170
}
170171
defer rows.Close()
172+
sw := make([]any, len(d.selectors))
171173
for rows.Next() {
172174
entity := new(T)
173-
sw := []any{}
174-
for _, each := range d.selectors {
175-
sw = append(sw, each.FieldValueToScan(entity))
175+
for i, each := range d.selectors {
176+
sw[i] = each.FieldValueToScan(entity)
176177
}
177178
if err := rows.Scan(sw...); err != nil {
178179
return list, err

0 commit comments

Comments
 (0)