Skip to content

Commit 1edee76

Browse files
committed
(improvement): perf - eliminate reflection overhead for collection and tuple types
Similiar to previous commit: Add type-specific fast paths in CollectionType.NewWithError() and TupleTypeInfo.NewWithError() to avoid expensive reflection calls during row data allocation. Changes: - CollectionType.NewWithError(): Fast paths for common patterns: * Lists/sets: []int, []int64, []string, []bool, []float32, []float64, []UUID, []time.Time, []int16, []int8, [][]byte * Maps: map[string]int, map[string]int64, map[string]string, map[string]bool, map[string]float64, map[string]UUID, map[int]string, map[int]int * Falls back to reflection for complex nested collections - TupleTypeInfo.NewWithError(): Simplified to always return new([]interface{}) since tuples unmarshal to []interface{} regardless of element types, completely eliminating reflection (Note - we may need to think of moving from interface to any? ) Performance impact: - Tuple-heavy queries: ~3% faster (1047→1017 ns/op) - Maintains performance for primitive-heavy workloads - Part of broader RowData() optimization series: * Combined improvements: 58.7% faster overall * Memory: -44% (720→400 B/op) * Allocations: -45% (22→12 allocs/op) Benchmarks show targeted benefits for queries using collections and tuples while preserving fast-path performance for queries dominated by native types. Signed-off-by: Yaniv Kaul <yaniv.kaul@scylladb.com>
1 parent f124115 commit 1edee76

File tree

2 files changed

+126
-5
lines changed

2 files changed

+126
-5
lines changed

helpers_bench_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package gocql
2020

2121
import (
2222
"fmt"
23+
"reflect"
2324
"testing"
2425
)
2526

@@ -227,3 +228,61 @@ func BenchmarkRowDataAllocation(b *testing.B) {
227228
})
228229
}
229230
}
231+
232+
// TestNewWithErrorConsistentWithGoType verifies that the fast-path type mapping
233+
// in NativeType.NewWithError() stays consistent with the canonical goType() mapping.
234+
// This guards against future changes to one mapping that forget to update the other.
235+
func TestNewWithErrorConsistentWithGoType(t *testing.T) {
236+
// All NativeType type codes that goType handles (excluding collection/tuple/UDT
237+
// which are separate TypeInfo implementations).
238+
nativeTypes := []Type{
239+
TypeVarchar, TypeAscii, TypeText, TypeInet,
240+
TypeBigInt, TypeCounter,
241+
TypeTime,
242+
TypeTimestamp,
243+
TypeBlob,
244+
TypeBoolean,
245+
TypeFloat,
246+
TypeDouble,
247+
TypeInt,
248+
TypeSmallInt,
249+
TypeTinyInt,
250+
TypeDecimal,
251+
TypeUUID, TypeTimeUUID,
252+
TypeVarint,
253+
TypeDate,
254+
TypeDuration,
255+
}
256+
257+
for _, typ := range nativeTypes {
258+
nt := NativeType{typ: typ, proto: protoVersion4}
259+
260+
// Get the fast-path result from NewWithError
261+
fastVal, err := nt.NewWithError()
262+
if err != nil {
263+
t.Errorf("NewWithError(%s): unexpected error: %v", typ, err)
264+
continue
265+
}
266+
267+
// Get the canonical type from goType
268+
canonicalType, err := goType(nt)
269+
if err != nil {
270+
t.Errorf("goType(%s): unexpected error: %v", typ, err)
271+
continue
272+
}
273+
274+
// NewWithError returns a pointer (reflect.New(typ).Interface()), so the
275+
// underlying type is reflect.TypeOf(val).Elem()
276+
fastType := reflect.TypeOf(fastVal)
277+
if fastType.Kind() != reflect.Ptr {
278+
t.Errorf("NewWithError(%s): expected pointer, got %s", typ, fastType.Kind())
279+
continue
280+
}
281+
fastElemType := fastType.Elem()
282+
283+
if fastElemType != canonicalType {
284+
t.Errorf("NewWithError(%s) fast-path type %s does not match goType() canonical type %s",
285+
typ, fastElemType, canonicalType)
286+
}
287+
}
288+
}

marshal.go

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,6 +1817,71 @@ func (v VectorType) Zero() interface{} {
18171817
}
18181818

18191819
func (t CollectionType) NewWithError() (interface{}, error) {
1820+
// Fast path for common collection patterns
1821+
switch t.typ {
1822+
case TypeList, TypeSet:
1823+
// Fast path for lists/sets of primitive types
1824+
if nt, ok := t.Elem.(NativeType); ok {
1825+
switch nt.typ {
1826+
case TypeInt:
1827+
return new([]int), nil
1828+
case TypeBigInt, TypeCounter:
1829+
return new([]int64), nil
1830+
case TypeText, TypeVarchar, TypeAscii:
1831+
return new([]string), nil
1832+
case TypeBoolean:
1833+
return new([]bool), nil
1834+
case TypeFloat:
1835+
return new([]float32), nil
1836+
case TypeDouble:
1837+
return new([]float64), nil
1838+
case TypeUUID, TypeTimeUUID:
1839+
return new([]UUID), nil
1840+
case TypeTimestamp, TypeDate:
1841+
return new([]time.Time), nil
1842+
case TypeSmallInt:
1843+
return new([]int16), nil
1844+
case TypeTinyInt:
1845+
return new([]int8), nil
1846+
case TypeBlob:
1847+
return new([][]byte), nil
1848+
}
1849+
}
1850+
case TypeMap:
1851+
// Fast path for maps with primitive key/value types
1852+
if keyNT, keyOk := t.Key.(NativeType); keyOk {
1853+
if valNT, valOk := t.Elem.(NativeType); valOk {
1854+
// String keys are most common
1855+
if keyNT.typ == TypeText || keyNT.typ == TypeVarchar {
1856+
switch valNT.typ {
1857+
case TypeInt:
1858+
return new(map[string]int), nil
1859+
case TypeBigInt:
1860+
return new(map[string]int64), nil
1861+
case TypeText, TypeVarchar:
1862+
return new(map[string]string), nil
1863+
case TypeBoolean:
1864+
return new(map[string]bool), nil
1865+
case TypeDouble:
1866+
return new(map[string]float64), nil
1867+
case TypeUUID:
1868+
return new(map[string]UUID), nil
1869+
}
1870+
}
1871+
// Int keys
1872+
if keyNT.typ == TypeInt {
1873+
switch valNT.typ {
1874+
case TypeText, TypeVarchar:
1875+
return new(map[int]string), nil
1876+
case TypeInt:
1877+
return new(map[int]int), nil
1878+
}
1879+
}
1880+
}
1881+
}
1882+
}
1883+
1884+
// Fallback to reflection for complex types
18201885
typ, err := goType(t)
18211886
if err != nil {
18221887
return nil, err
@@ -1861,11 +1926,8 @@ func (t TupleTypeInfo) String() string {
18611926
}
18621927

18631928
func (t TupleTypeInfo) NewWithError() (interface{}, error) {
1864-
typ, err := goType(t)
1865-
if err != nil {
1866-
return nil, err
1867-
}
1868-
return reflect.New(typ).Interface(), nil
1929+
// Tuples scan into *[]interface{} (pointer to a slice of interface values).
1930+
return new([]interface{}), nil
18691931
}
18701932

18711933
type UDTField struct {

0 commit comments

Comments
 (0)