From f1a4636699848f99fc9a08ad365b214a7ad62221 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 26 Jan 2014 00:05:37 +0800 Subject: [PATCH] performance improved for db.ScanStruct2 --- core/db.go | 38 ++++++++++++++++++++++++++++++++------ core/db_test.go | 2 +- core/mapper.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/core/db.go b/core/db.go index f1e52783..f3fbbd43 100644 --- a/core/db.go +++ b/core/db.go @@ -4,6 +4,7 @@ import ( "database/sql" "errors" "reflect" + "sync" ) type DB struct { @@ -13,7 +14,7 @@ type DB struct { func Open(driverName, dataSourceName string) (*DB, error) { db, err := sql.Open(driverName, dataSourceName) - return &DB{db, &SnakeMapper{}}, err + return &DB{db, NewCacheMapper(&SnakeMapper{})}, err } func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { @@ -59,6 +60,18 @@ func (rs *Rows) ScanStruct(dest ...interface{}) error { return rs.Rows.Scan(newDest...) } +type EmptyScanner struct { +} + +func (EmptyScanner) Scan(src interface{}) error { + return nil +} + +var ( + fieldCache = make(map[reflect.Type]map[string]int) + fieldCacheMutex sync.RWMutex +) + // scan data to a struct's pointer according field name func (rs *Rows) ScanStruct2(dest interface{}) error { vv := reflect.ValueOf(dest) @@ -72,14 +85,27 @@ func (rs *Rows) ScanStruct2(dest interface{}) error { } vvv := vv.Elem() - newDest := make([]interface{}, len(cols)) + t := vvv.Type() + fieldCacheMutex.RLock() + cache, ok := fieldCache[t] + fieldCacheMutex.RUnlock() + if !ok { + cache = make(map[string]int) + for i := 0; i < vvv.NumField(); i++ { + cache[vvv.Type().Field(i).Name] = i + } + fieldCacheMutex.Lock() + fieldCache[t] = cache + fieldCacheMutex.Unlock() + } + + newDest := make([]interface{}, len(cols)) + var v EmptyScanner for j, name := range cols { - f := vvv.FieldByName(rs.Mapper.Table2Obj(name)) - if f.IsValid() { - newDest[j] = f.Addr().Interface() + if i, ok := cache[rs.Mapper.Table2Obj(name)]; ok { + newDest[j] = vvv.Field(i).Addr().Interface() } else { - var v interface{} newDest[j] = &v } } diff --git a/core/db_test.go b/core/db_test.go index 41a4cf90..1ea670a5 100644 --- a/core/db_test.go +++ b/core/db_test.go @@ -125,7 +125,7 @@ func BenchmarkStruct2Query(b *testing.B) { } } - db.Mapper = &SnakeMapper{} + db.Mapper = NewCacheMapper(&SnakeMapper{}) b.StartTimer() for i := 0; i < b.N; i++ { diff --git a/core/mapper.go b/core/mapper.go index 0011dde5..fb209a7c 100644 --- a/core/mapper.go +++ b/core/mapper.go @@ -2,6 +2,7 @@ package core import ( "strings" + "sync" ) // name translation between struct, fields names and table, column names @@ -10,6 +11,50 @@ type IMapper interface { Table2Obj(string) string } +type CacheMapper struct { + oriMapper IMapper + obj2tableCache map[string]string + obj2tableMutex sync.RWMutex + table2objCache map[string]string + table2objMutex sync.RWMutex +} + +func NewCacheMapper(mapper IMapper) *CacheMapper { + return &CacheMapper{oriMapper: mapper, obj2tableCache: make(map[string]string), + table2objCache: make(map[string]string), + } +} + +func (m *CacheMapper) Obj2Table(o string) string { + m.obj2tableMutex.RLock() + t, ok := m.obj2tableCache[o] + m.obj2tableMutex.RUnlock() + if ok { + return t + } + + t = m.oriMapper.Obj2Table(o) + m.obj2tableMutex.Lock() + m.obj2tableCache[o] = t + m.obj2tableMutex.Unlock() + return t +} + +func (m *CacheMapper) Table2Obj(t string) string { + m.table2objMutex.RLock() + o, ok := m.table2objCache[t] + m.table2objMutex.RUnlock() + if ok { + return o + } + + o = m.oriMapper.Table2Obj(t) + m.table2objMutex.Lock() + m.table2objCache[t] = o + m.table2objMutex.Unlock() + return o +} + // SameMapper implements IMapper and provides same name between struct and // database table type SameMapper struct {