libnet/internal/resolvconf: optimize Generate() without text/template

Rewrite this function to not use text/template, which is ~74× faster,
~25× less memory, and ~56× fewer allocations.

Before/After:

    BenchmarkGenerate-10       42550     27439 ns/op    18083 B/op    394 allocs/op
    BenchmarkGenerate-10     3148328       373 ns/op      712 B/op      7 allocs/op

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-07-17 00:46:31 +02:00
parent 0775ab6661
commit f22a3dfdb7

View File

@@ -21,7 +21,6 @@ import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"io/fs"
"net/netip"
@@ -29,7 +28,6 @@ import (
"slices"
"strconv"
"strings"
"text/template"
"github.com/containerd/log"
"github.com/moby/sys/atomicwriter"
@@ -71,7 +69,7 @@ type ExtDNSEntry struct {
func (ed ExtDNSEntry) String() string {
if ed.HostLoopback {
return fmt.Sprintf("host(%s)", ed.Addr)
return "host(" + ed.Addr.String() + ")"
}
return ed.Addr.String()
}
@@ -291,70 +289,106 @@ func (rc *ResolvConf) TransformForIntNS(
// is true, the file will include header information if supplied, and a trailing
// comment that describes how the file was constructed and lists external resolvers.
func (rc *ResolvConf) Generate(comments bool) ([]byte, error) {
s := struct {
Md *metadata
NameServers []netip.Addr
Search []string
Options []string
Other []string
Overrides []string
Comments bool
}{
Md: &rc.md,
NameServers: rc.nameServers,
Search: rc.search,
Options: rc.options,
Other: rc.other,
Comments: comments,
var b bytes.Buffer
b.Grow(512) // estimated size for a regular resolv.conf we produce.
if comments && rc.md.Header != "" {
b.WriteString(rc.md.Header + "\n")
b.WriteByte('\n')
}
if rc.md.NSOverride {
s.Overrides = append(s.Overrides, "nameservers")
for _, ns := range rc.nameServers {
b.WriteString("nameserver ")
b.WriteString(ns.String())
b.WriteByte('\n')
}
if rc.md.SearchOverride {
s.Overrides = append(s.Overrides, "search")
if len(rc.search) > 0 {
b.WriteString("search ")
for i, s := range rc.search {
if i > 0 {
b.WriteByte(' ')
}
b.WriteString(s)
}
b.WriteByte('\n')
}
if rc.md.OptionsOverride {
s.Overrides = append(s.Overrides, "options")
if len(rc.options) > 0 {
b.WriteString("options ")
for i, s := range rc.options {
if i > 0 {
b.WriteByte(' ')
}
b.WriteString(s)
}
b.WriteByte('\n')
}
for _, o := range rc.other {
b.WriteString(o)
b.WriteByte('\n')
}
const templateText = `{{if .Comments}}{{with .Md.Header}}{{.}}
if comments {
b.WriteByte('\n')
b.WriteString("# Based on host file: '" + rc.md.SourcePath + "'")
if rc.md.Transform != "" {
b.WriteString(" (" + rc.md.Transform + ")")
}
b.WriteByte('\n')
for _, w := range rc.md.Warnings {
b.WriteString("# ")
b.WriteString(w)
b.WriteByte('\n')
}
if len(rc.md.ExtNameServers) > 0 {
b.WriteString("# ExtServers: [")
for i, ext := range rc.md.ExtNameServers {
if i > 0 {
b.WriteByte(' ')
}
b.WriteString(ext.String())
}
b.WriteByte(']')
b.WriteByte('\n')
}
if len(rc.md.InvalidNSs) > 0 {
b.WriteString("# Invalid nameservers: [")
for i, ext := range rc.md.InvalidNSs {
if i > 0 {
b.WriteByte(' ')
}
b.WriteString(ext)
}
b.WriteByte(']')
b.WriteByte('\n')
}
{{end}}{{end}}{{range .NameServers -}}
nameserver {{.}}
{{end}}{{with .Search -}}
search {{join . " "}}
{{end}}{{with .Options -}}
options {{join . " "}}
{{end}}{{with .Other -}}
{{join . "\n"}}
{{end}}{{if .Comments}}
# Based on host file: '{{.Md.SourcePath}}'{{with .Md.Transform}} ({{.}}){{end}}
{{range .Md.Warnings -}}
# {{.}}
{{end -}}
{{with .Md.ExtNameServers -}}
# ExtServers: {{.}}
{{end -}}
{{with .Md.InvalidNSs -}}
# Invalid nameservers: {{.}}
{{end -}}
# Overrides: {{.Overrides}}
{{with .Md.NDotsFrom -}}
# Option ndots from: {{.}}
{{end -}}
{{end -}}
`
b.WriteString("# Overrides: [")
var overrides int
if rc.md.NSOverride {
b.WriteString("nameservers")
overrides++
}
if rc.md.SearchOverride {
if overrides > 0 {
b.WriteByte(' ')
}
b.WriteString("search")
overrides++
}
if rc.md.OptionsOverride {
if overrides > 0 {
b.WriteByte(' ')
}
b.WriteString("options")
}
b.WriteByte(']')
b.WriteByte('\n')
funcs := template.FuncMap{"join": strings.Join}
var buf bytes.Buffer
templ, err := template.New("summary").Funcs(funcs).Parse(templateText)
if err != nil {
return nil, errSystem{err}
if rc.md.NDotsFrom != "" {
b.WriteString("# Option ndots from: " + rc.md.NDotsFrom + "\n")
}
}
if err := templ.Execute(&buf, s); err != nil {
return nil, errSystem{err}
}
return buf.Bytes(), nil
return b.Bytes(), nil
}
// WriteFile generates content and writes it to path. If hashPath is non-zero, it