From 4bdf30257457cd291de58a569e56920d51763492 Mon Sep 17 00:00:00 2001 From: Tom Thorogood Date: Thu, 27 Apr 2023 21:06:03 +0930 Subject: [PATCH] Improve IsFQDN performance (#1453) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve IsFQDN performance While this code may be slightly less clear, it's significantly faster and this function seems to be a hot path for certain workloads. name old time/op new time/op delta IsFQDN/no_dot-12 5.86ns ± 2% 1.48ns ± 3% -74.71% (p=0.000 n=10+10) IsFQDN/unescaped-12 8.73ns ± 2% 1.57ns ± 1% -81.98% (p=0.000 n=9+8) IsFQDN/escaped-12 27.4ns ± 2% 23.8ns ± 2% -13.19% (p=0.000 n=10+10) FQDN/is_fqdn-12 8.36ns ± 1% 1.80ns ± 2% -78.50% (p=0.000 n=9+10) FQDN/not_fqdn-12 36.8ns ±15% 33.4ns ±12% -9.25% (p=0.035 n=10+10) * Fixup IsFQDN comment --- defaults.go | 20 +++++++++++++------- dns_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/defaults.go b/defaults.go index 75b17f0c1..c1558b79c 100644 --- a/defaults.go +++ b/defaults.go @@ -272,18 +272,24 @@ func IsMsg(buf []byte) error { // IsFqdn checks if a domain name is fully qualified. func IsFqdn(s string) bool { - s2 := strings.TrimSuffix(s, ".") - if s == s2 { + // Check for (and remove) a trailing dot, returning if there isn't one. + if s == "" || s[len(s)-1] != '.' { return false } + s = s[:len(s)-1] - i := strings.LastIndexFunc(s2, func(r rune) bool { + // If we don't have an escape sequence before the final dot, we know it's + // fully qualified and can return here. + if s == "" || s[len(s)-1] != '\\' { + return true + } + + // Otherwise we have to check if the dot is escaped or not by checking if + // there are an odd or even number of escape sequences before the dot. + i := strings.LastIndexFunc(s, func(r rune) bool { return r != '\\' }) - - // Test whether we have an even number of escape sequences before - // the dot or none. - return (len(s2)-i)%2 != 0 + return (len(s)-i)%2 != 0 } // IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181. diff --git a/dns_test.go b/dns_test.go index f1dbf23d4..cba5386af 100644 --- a/dns_test.go +++ b/dns_test.go @@ -314,3 +314,49 @@ func TestTKEY(t *testing.T) { t.Fatalf("unable to parse TKEY string: %s", newError) } } + +var ( + sinkBool bool + sinkString string +) + +func BenchmarkIsFQDN(b *testing.B) { + b.Run("no_dot", func(b *testing.B) { + var r bool + for n := 0; n < b.N; n++ { + r = IsFqdn("www.google.com") + } + sinkBool = r + }) + b.Run("unescaped", func(b *testing.B) { + var r bool + for n := 0; n < b.N; n++ { + r = IsFqdn("www.google.com.") + } + sinkBool = r + }) + b.Run("escaped", func(b *testing.B) { + var r bool + for n := 0; n < b.N; n++ { + r = IsFqdn(`www.google.com\\\\\\\\.`) + } + sinkBool = r + }) +} + +func BenchmarkFQDN(b *testing.B) { + b.Run("is_fqdn", func(b *testing.B) { + var r string + for n := 0; n < b.N; n++ { + r = Fqdn("www.google.com.") + } + sinkString = r + }) + b.Run("not_fqdn", func(b *testing.B) { + var r string + for n := 0; n < b.N; n++ { + r = Fqdn("www.google.com") + } + sinkString = r + }) +}