From 2f62c430e0061a2ead7bc81fe085d0e790b05c88 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Tue, 28 Jan 2020 14:25:27 -0800 Subject: [PATCH] url: reduce allocations in ParseURL If you use e.g. "postgres://" as the connection string, ParseURL will be called and the result will be computed each time a connection is established to the database. This can be expensive because a new strings.Replacer is created each time ParseURL is called. It's also unnecessary because the output can be computed by the input and for most processes only a single input will ever exist for the lifetime of the process. Reuse the same *Replacer across calls, and also cache the first 100 unique results from ParseURL, to speed up the second and third calls. --- url.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/url.go b/url.go index f4d8a7c2..609c37a8 100644 --- a/url.go +++ b/url.go @@ -6,8 +6,15 @@ import ( nurl "net/url" "sort" "strings" + "sync" ) +var escaperOnce sync.Once +var escaper *strings.Replacer + +var parsedURLCache map[string]string +var parsedURLMu sync.Mutex + // ParseURL no longer needs to be used by clients of this library since supplying a URL as a // connection string to sql.Open() is now supported: // @@ -30,6 +37,12 @@ import ( // // This will be blank, causing driver.Open to use all of the defaults func ParseURL(url string) (string, error) { + parsedURLMu.Lock() + cachedVal, ok := parsedURLCache[url] + parsedURLMu.Unlock() + if ok { + return cachedVal, nil + } u, err := nurl.Parse(url) if err != nil { return "", err @@ -40,7 +53,9 @@ func ParseURL(url string) (string, error) { } var kvs []string - escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`) + escaperOnce.Do(func() { + escaper = strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`) + }) accrue := func(k, v string) { if v != "" { kvs = append(kvs, k+"="+escaper.Replace(v)) @@ -72,5 +87,15 @@ func ParseURL(url string) (string, error) { } sort.Strings(kvs) // Makes testing easier (not a performance concern) - return strings.Join(kvs, " "), nil + result := strings.Join(kvs, " ") + parsedURLMu.Lock() + if parsedURLCache == nil { + parsedURLCache = make(map[string]string) + } + // don't take up an unbounded amount of memory + if len(parsedURLCache) < 100 { + parsedURLCache[url] = result + } + parsedURLMu.Unlock() + return result, nil }