From 1b68b5970e8329ad3035b32a97bc78e38207951e Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Wed, 7 Jun 2023 15:25:31 -0400 Subject: [PATCH] pgtype/hstore: Save 2 allocs in database/sql Scan implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unneeded string to []byte to string conversion, which saves 2 allocs and should make Hstore text scanning slightly faster. The Hstore.Scan() function takes a string as input, converts it to []byte, and calls scanPlanTextAnyToHstoreScanner.Scan(). That function converts []byte back to string and calls parseHstore. This refactors scanPlanTextAnyToHstoreScanner.Scan into scanPlanTextAnyToHstoreScanner.scanString so the database/sql Scan function can call it directly, bypassing this conversion. The added Benchmark shows this saves 2 allocs for longer strings, and saves about 5% CPU overall on my M1 Pro. benchstat output: goos: darwin goarch: arm64 pkg: github.com/jackc/pgx/v5/pgtype │ orig.txt │ new.txt │ │ sec/op │ sec/op vs base │ HstoreScan-10 1.334µ ± 2% 1.257µ ± 2% -5.77% (p=0.000 n=10) │ orig.txt │ new.txt │ │ B/op │ B/op vs base │ HstoreScan-10 2.094Ki ± 0% 1.969Ki ± 0% -5.97% (p=0.000 n=10) │ orig.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ HstoreScan-10 36.00 ± 0% 34.00 ± 0% -5.56% (p=0.000 n=10) --- pgtype/hstore.go | 10 +++++++--- pgtype/hstore_test.go | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/pgtype/hstore.go b/pgtype/hstore.go index ca0c3f6f7..e4695819a 100644 --- a/pgtype/hstore.go +++ b/pgtype/hstore.go @@ -42,7 +42,7 @@ func (h *Hstore) Scan(src any) error { switch src := src.(type) { case string: - return scanPlanTextAnyToHstoreScanner{}.Scan([]byte(src), h) + return scanPlanTextAnyToHstoreScanner{}.scanString(src, h) } return fmt.Errorf("cannot scan %T", src) @@ -236,14 +236,18 @@ func (scanPlanBinaryHstoreToHstoreScanner) Scan(src []byte, dst any) error { type scanPlanTextAnyToHstoreScanner struct{} -func (scanPlanTextAnyToHstoreScanner) Scan(src []byte, dst any) error { +func (s scanPlanTextAnyToHstoreScanner) Scan(src []byte, dst any) error { scanner := (dst).(HstoreScanner) if src == nil { return scanner.ScanHstore(Hstore(nil)) } + return s.scanString(string(src), scanner) +} - keys, values, err := parseHstore(string(src)) +// scanString does not return nil hstore values because string cannot be nil. +func (scanPlanTextAnyToHstoreScanner) scanString(src string, scanner HstoreScanner) error { + keys, values, err := parseHstore(src) if err != nil { return err } diff --git a/pgtype/hstore_test.go b/pgtype/hstore_test.go index e2b6bb4ef..4f93a94d9 100644 --- a/pgtype/hstore_test.go +++ b/pgtype/hstore_test.go @@ -288,3 +288,22 @@ func BenchmarkHstoreEncode(b *testing.B) { }) } } + +func BenchmarkHstoreScan(b *testing.B) { + strs := []string{ + "", + `"a"=>"b"`, + `"a"=>"100", "b"=>"200", "c"=>"300", "d"=>"400", "e"=>"500"`, + } + + var h pgtype.Hstore + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for _, str := range strs { + err := h.Scan(str) + if err != nil { + b.Fatal(err) + } + } + } +}