From 625f6c9307e7d88bc587f5719cf5707bfec42c6d Mon Sep 17 00:00:00 2001 From: Catena cyber <35799796+catenacyber@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:05:58 +0200 Subject: [PATCH] perf: avoid string concatenation in loops (#7572) * perf: avoid string concatenation in loops Apply perfpsrint linter Signed-off-by: Philippe Antoine * ci: enable perfsprint Signed-off-by: Philippe Antoine --------- Signed-off-by: Philippe Antoine --- .golangci.yml | 8 ++++++++ core/dnsserver/onstartup.go | 14 +++++++------- plugin/auto/regexp.go | 13 ++++++++----- plugin/pkg/doh/doh.go | 2 +- plugin/route53/route53.go | 10 +++++----- plugin/route53/setup.go | 2 +- plugin/sign/keys.go | 5 +++-- plugin/test/scrape.go | 6 +++--- 8 files changed, 36 insertions(+), 24 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index a43a00d96..26b005c16 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,6 +15,7 @@ linters: - intrange - nakedret - nolintlint + - perfsprint - prealloc - protogetter - staticcheck @@ -31,10 +32,17 @@ linters: - common-false-positives - legacy - std-error-handling + rules: + # Exclude some linters from running on tests files. + - path: _test\.go + linters: + - perfsprint settings: govet: enable: - nilness + perfsprint: + error-format: false revive: rules: - name: blank-imports diff --git a/core/dnsserver/onstartup.go b/core/dnsserver/onstartup.go index 572b3589f..90a270bad 100644 --- a/core/dnsserver/onstartup.go +++ b/core/dnsserver/onstartup.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "sort" + "strings" "github.com/coredns/coredns/plugin/pkg/dnsutil" ) @@ -22,8 +23,6 @@ func checkZoneSyntax(zone string) bool { // grpc://example.com.:1055 // example.com.:1053 on 127.0.0.1 func startUpZones(protocol, addr string, zones map[string][]*Config) string { - s := "" - keys := make([]string, len(zones)) i := 0 @@ -33,25 +32,26 @@ func startUpZones(protocol, addr string, zones map[string][]*Config) string { } sort.Strings(keys) + var sb strings.Builder for _, zone := range keys { if !checkZoneSyntax(zone) { - s += fmt.Sprintf("Warning: Domain %q does not follow RFC1035 preferred syntax\n", zone) + sb.WriteString(fmt.Sprintf("Warning: Domain %q does not follow RFC1035 preferred syntax\n", zone)) } // split addr into protocol, IP and Port _, ip, port, err := SplitProtocolHostPort(addr) if err != nil { // this should not happen, but we need to take care of it anyway - s += fmt.Sprintln(protocol + zone + ":" + addr) + sb.WriteString(fmt.Sprintln(protocol + zone + ":" + addr)) continue } if ip == "" { - s += fmt.Sprintln(protocol + zone + ":" + port) + sb.WriteString(fmt.Sprintln(protocol + zone + ":" + port)) continue } // if the server is listening on a specific address let's make it visible in the log, // so one can differentiate between all active listeners - s += fmt.Sprintln(protocol + zone + ":" + port + " on " + ip) + sb.WriteString(fmt.Sprintln(protocol + zone + ":" + port + " on " + ip)) } - return s + return sb.String() } diff --git a/plugin/auto/regexp.go b/plugin/auto/regexp.go index fa424ec7e..c203e559a 100644 --- a/plugin/auto/regexp.go +++ b/plugin/auto/regexp.go @@ -1,5 +1,9 @@ package auto +import ( + "strings" +) + // rewriteToExpand rewrites our template string to one that we can give to regexp.ExpandString. This basically // involves prefixing any '{' with a '$'. func rewriteToExpand(s string) string { @@ -7,14 +11,13 @@ func rewriteToExpand(s string) string { // Also wasteful as we build the string with +=. This is OKish // as we do this during config parsing. - copy := "" - + var copySb strings.Builder for _, c := range s { if c == '{' { - copy += "$" + copySb.WriteString("$") } - copy += string(c) + copySb.WriteString(string(c)) } - return copy + return copySb.String() } diff --git a/plugin/pkg/doh/doh.go b/plugin/pkg/doh/doh.go index d125dd681..e14c2ed28 100644 --- a/plugin/pkg/doh/doh.go +++ b/plugin/pkg/doh/doh.go @@ -29,7 +29,7 @@ func NewRequest(method, url string, m *dns.Msg) (*http.Request, error) { } if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { - url = fmt.Sprintf("https://%s", url) + url = "https://" + url } switch method { diff --git a/plugin/route53/route53.go b/plugin/route53/route53.go index fc1f06d16..4d31f7dec 100644 --- a/plugin/route53/route53.go +++ b/plugin/route53/route53.go @@ -171,14 +171,14 @@ const escapeSeq = "\\" // `\\052.example.com.` -> `*.example.com` // `\\137.example.com.` -> error ('_' is not valid) func maybeUnescape(s string) (string, error) { - var out string + var outSb strings.Builder for { i := strings.Index(s, escapeSeq) if i < 0 { - return out + s, nil + return outSb.String() + s, nil } - out += s[:i] + outSb.WriteString(s[:i]) li, ri := i+len(escapeSeq), i+len(escapeSeq)+3 if ri > len(s) { @@ -196,7 +196,7 @@ func maybeUnescape(s string) (string, error) { case r >= rune('a') && r <= rune('z'): // Route53 converts everything to lowercase. case r >= rune('0') && r <= rune('9'): case r == rune('*'): - if out != "" { + if outSb.Len() != 0 { return "", errors.New("`*' only supported as wildcard (leftmost label)") } case r == rune('-'): @@ -205,7 +205,7 @@ func maybeUnescape(s string) (string, error) { return "", fmt.Errorf("invalid character: %s%#03o", escapeSeq, r) } - out += string(r) + outSb.WriteString(string(r)) s = s[i+len(escapeSeq)+3:] } diff --git a/plugin/route53/setup.go b/plugin/route53/setup.go index 857158053..cff72c2cc 100644 --- a/plugin/route53/setup.go +++ b/plugin/route53/setup.go @@ -127,7 +127,7 @@ func setup(c *caddy.Controller) error { refreshStr := c.Val() _, err := strconv.Atoi(refreshStr) if err == nil { - refreshStr = fmt.Sprintf("%ss", c.Val()) + refreshStr = c.Val() + "s" } refresh, err = time.ParseDuration(refreshStr) if err != nil { diff --git a/plugin/sign/keys.go b/plugin/sign/keys.go index b99958442..2d2476495 100644 --- a/plugin/sign/keys.go +++ b/plugin/sign/keys.go @@ -111,9 +111,10 @@ func keyTag(ps []Pair) string { if len(ps) == 0 { return "" } - s := "" + var sb strings.Builder for _, p := range ps { - s += strconv.Itoa(int(p.KeyTag)) + "," + sb.WriteString(strconv.Itoa(int(p.KeyTag)) + ",") } + s := sb.String() return s[:len(s)-1] } diff --git a/plugin/test/scrape.go b/plugin/test/scrape.go index 71fdff329..df8450f19 100644 --- a/plugin/test/scrape.go +++ b/plugin/test/scrape.go @@ -157,14 +157,14 @@ func newMetricFamily(dtoMF *dto.MetricFamily) *MetricFamily { mf.Metrics[i] = summary{ Labels: makeLabels(m), Quantiles: makeQuantiles(m), - Count: fmt.Sprint(m.GetSummary().GetSampleCount()), + Count: strconv.FormatUint(m.GetSummary().GetSampleCount(), 10), Sum: fmt.Sprint(m.GetSummary().GetSampleSum()), } } else if dtoMF.GetType() == dto.MetricType_HISTOGRAM { mf.Metrics[i] = histogram{ Labels: makeLabels(m), Buckets: makeBuckets(m), - Count: fmt.Sprint(m.GetHistogram().GetSampleCount()), + Count: strconv.FormatUint(m.GetHistogram().GetSampleCount(), 10), Sum: fmt.Sprint(m.GetSummary().GetSampleSum()), } } else { @@ -209,7 +209,7 @@ func makeQuantiles(m *dto.Metric) map[string]string { func makeBuckets(m *dto.Metric) map[string]string { result := map[string]string{} for _, b := range m.GetHistogram().GetBucket() { - result[fmt.Sprint(b.GetUpperBound())] = fmt.Sprint(b.GetCumulativeCount()) + result[fmt.Sprint(b.GetUpperBound())] = strconv.FormatUint(b.GetCumulativeCount(), 10) } return result }