plugin/cache: remove superfluous allocations in item.toMsg (#7700)

This commit removes superfluous allocations of the Answer, Ns, and Extra
slices when copying a cached a dns.Msg. The allocations are superfluous
because we immediately overwrite the newly copied slices with
filterRRSlice. It also updates filterRRSlice to pre-calculate the size
of the slice being copied into.

Benchmark results:

goos: darwin
goarch: arm64
pkg: github.com/coredns/coredns/plugin/cache
cpu: Apple M4 Pro
                 │ base.10.txt │             new.10.txt             │
                 │   sec/op    │   sec/op     vs base               │
CacheResponse-14   471.1n ± 0%   462.9n ± 2%  -1.74% (p=0.009 n=10)

                 │ base.10.txt │            new.10.txt             │
                 │    B/op     │    B/op     vs base               │
CacheResponse-14    672.0 ± 0%   656.0 ± 0%  -2.38% (p=0.000 n=10)

                 │ base.10.txt │            new.10.txt             │
                 │  allocs/op  │ allocs/op   vs base               │
CacheResponse-14    13.00 ± 0%   12.00 ± 0%  -7.69% (p=0.000 n=10)

Signed-off-by: Charlie Vieth <charlie.vieth@gmail.com>
This commit is contained in:
Charlie Vieth
2025-11-21 18:07:59 -05:00
committed by GitHub
parent 854048b0ab
commit de010910e2
3 changed files with 18 additions and 7 deletions

View File

@@ -594,16 +594,25 @@ func BenchmarkCacheResponse(b *testing.B) {
ctx := context.TODO() ctx := context.TODO()
// Add some answers since these need to be duplicated when
// serving a cached response.
answer := []dns.RR{
test.MX("miek.nl. 3601 IN MX 1 aspmx.l.google.com."),
test.MX("miek.nl. 3601 IN MX 10 aspmx2.googlemail.com."),
}
reqs := make([]*dns.Msg, 5) reqs := make([]*dns.Msg, 5)
for i, q := range []string{"example1", "example2", "a", "b", "ddd"} { for i, q := range []string{"example1", "example2", "a", "b", "ddd"} {
reqs[i] = new(dns.Msg) reqs[i] = new(dns.Msg)
reqs[i].SetQuestion(q+".example.org.", dns.TypeA) reqs[i].SetQuestion(q+".example.org.", dns.TypeA)
reqs[i].Answer = answer
} }
b.ResetTimer()
rw := &test.ResponseWriter{}
j := 0 j := 0
for b.Loop() { for b.Loop() {
req := reqs[j] req := reqs[j]
c.ServeDNS(ctx, &test.ResponseWriter{}, req) c.ServeDNS(ctx, rw, req)
j = (j + 1) % 5 j = (j + 1) % 5
} }
} }

View File

@@ -6,8 +6,14 @@ import "github.com/miekg/dns"
// If dup is true the RRs in rrs are _copied_ before adjusting their // If dup is true the RRs in rrs are _copied_ before adjusting their
// TTL and the slice of copied RRs is returned. // TTL and the slice of copied RRs is returned.
func filterRRSlice(rrs []dns.RR, ttl uint32, dup bool) []dns.RR { func filterRRSlice(rrs []dns.RR, ttl uint32, dup bool) []dns.RR {
n := 0
for _, r := range rrs {
if r.Header().Rrtype != dns.TypeOPT {
n++
}
}
rs := make([]dns.RR, n)
j := 0 j := 0
rs := make([]dns.RR, len(rrs))
for _, r := range rrs { for _, r := range rrs {
if r.Header().Rrtype == dns.TypeOPT { if r.Header().Rrtype == dns.TypeOPT {
continue continue
@@ -20,5 +26,5 @@ func filterRRSlice(rrs []dns.RR, ttl uint32, dup bool) []dns.RR {
rs[j].Header().Ttl = ttl rs[j].Header().Ttl = ttl
j++ j++
} }
return rs[:j] return rs
} }

View File

@@ -82,10 +82,6 @@ func (i *item) toMsg(m *dns.Msg, now time.Time, do bool, ad bool) *dns.Msg {
m1.RecursionAvailable = i.RecursionAvailable m1.RecursionAvailable = i.RecursionAvailable
m1.Rcode = i.Rcode m1.Rcode = i.Rcode
m1.Answer = make([]dns.RR, len(i.Answer))
m1.Ns = make([]dns.RR, len(i.Ns))
m1.Extra = make([]dns.RR, len(i.Extra))
ttl := uint32(i.ttl(now)) ttl := uint32(i.ttl(now))
m1.Answer = filterRRSlice(i.Answer, ttl, true) m1.Answer = filterRRSlice(i.Answer, ttl, true)
m1.Ns = filterRRSlice(i.Ns, ttl, true) m1.Ns = filterRRSlice(i.Ns, ttl, true)