From d01de7232b46987e239ef19a89d9ab192f453894 Mon Sep 17 00:00:00 2001 From: Bexanderthebex Date: Wed, 1 Jun 2022 11:50:57 +0800 Subject: [PATCH] enhancement: Avoid calling reflect.New() when passing in slice of values to `Scan()` (#5388) * fix: reduce allocations when slice of values * chore[test]: Add benchmark for scan * chore[test]: add bench for scan slice * chore[test]: add bench for slice pointer and improve tests * chore[test]: make sure database is empty when doing slice tests * fix[test]: correct sql delete statement * enhancement: skip new if rows affected = 0 --- scan.go | 7 ++++++- tests/benchmark_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/scan.go b/scan.go index a611a9ce8..1bb51560a 100644 --- a/scan.go +++ b/scan.go @@ -237,6 +237,7 @@ func Scan(rows Rows, db *DB, mode ScanMode) { switch reflectValue.Kind() { case reflect.Slice, reflect.Array: var elem reflect.Value + recyclableStruct := reflect.New(reflectValueType) if !update || reflectValue.Len() == 0 { update = false @@ -261,7 +262,11 @@ func Scan(rows Rows, db *DB, mode ScanMode) { } } } else { - elem = reflect.New(reflectValueType) + if isPtr && db.RowsAffected > 0 { + elem = reflect.New(reflectValueType) + } else { + elem = recyclableStruct + } } db.scanIntoStruct(rows, elem, values, fields, joinFields) diff --git a/tests/benchmark_test.go b/tests/benchmark_test.go index d897a6341..22d15898e 100644 --- a/tests/benchmark_test.go +++ b/tests/benchmark_test.go @@ -1,6 +1,7 @@ package tests_test import ( + "fmt" "testing" . "gorm.io/gorm/utils/tests" @@ -24,6 +25,45 @@ func BenchmarkFind(b *testing.B) { } } +func BenchmarkScan(b *testing.B) { + user := *GetUser("scan", Config{}) + DB.Create(&user) + + var u User + b.ResetTimer() + for x := 0; x < b.N; x++ { + DB.Raw("select * from users where id = ?", user.ID).Scan(&u) + } +} + +func BenchmarkScanSlice(b *testing.B) { + DB.Exec("delete from users") + for i := 0; i < 10_000; i++ { + user := *GetUser(fmt.Sprintf("scan-%d", i), Config{}) + DB.Create(&user) + } + + var u []User + b.ResetTimer() + for x := 0; x < b.N; x++ { + DB.Raw("select * from users").Scan(&u) + } +} + +func BenchmarkScanSlicePointer(b *testing.B) { + DB.Exec("delete from users") + for i := 0; i < 10_000; i++ { + user := *GetUser(fmt.Sprintf("scan-%d", i), Config{}) + DB.Create(&user) + } + + var u []*User + b.ResetTimer() + for x := 0; x < b.N; x++ { + DB.Raw("select * from users").Scan(&u) + } +} + func BenchmarkUpdate(b *testing.B) { user := *GetUser("find", Config{}) DB.Create(&user)