Skip to content

Commit 22cda6d

Browse files
jpichtmiekg
authored andcommitted
Speed up NextLabel and PrevLabel (#1039)
* change NextLabel and PrevLabel to be faster This reduces readability, but they are in the hot path of coredns. * @redyeti pointed out, that my implementation disregarded triple backslashes * add synthetic benchmark-tests for PrevLabel and NextLabel * rename ii -> j * invert compare * PrevLabel: add empty string check + test case * NextLabel: fix and add testcase for NextLabel("", offset>0)
1 parent 9b7437f commit 22cda6d

File tree

2 files changed

+133
-28
lines changed

2 files changed

+133
-28
lines changed

labels.go

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -126,20 +126,23 @@ func Split(s string) []int {
126126
// The bool end is true when the end of the string has been reached.
127127
// Also see PrevLabel.
128128
func NextLabel(s string, offset int) (i int, end bool) {
129-
quote := false
129+
if s == "" {
130+
return 0, true
131+
}
130132
for i = offset; i < len(s)-1; i++ {
131-
switch s[i] {
132-
case '\\':
133-
quote = !quote
134-
default:
135-
quote = false
136-
case '.':
137-
if quote {
138-
quote = !quote
139-
continue
140-
}
141-
return i + 1, false
133+
if s[i] != '.' {
134+
continue
135+
}
136+
j := i - 1
137+
for j >= 0 && s[j] == '\\' {
138+
j--
139+
}
140+
141+
if (j-i)%2 == 0 {
142+
continue
142143
}
144+
145+
return i + 1, false
143146
}
144147
return i + 1, true
145148
}
@@ -149,17 +152,38 @@ func NextLabel(s string, offset int) (i int, end bool) {
149152
// The bool start is true when the start of the string has been overshot.
150153
// Also see NextLabel.
151154
func PrevLabel(s string, n int) (i int, start bool) {
155+
if s == "" {
156+
return 0, true
157+
}
152158
if n == 0 {
153159
return len(s), false
154160
}
155-
lab := Split(s)
156-
if lab == nil {
157-
return 0, true
161+
162+
l := len(s) - 1
163+
if s[l] == '.' {
164+
l--
158165
}
159-
if n > len(lab) {
160-
return 0, true
166+
167+
for ; l >= 0 && n > 0; l-- {
168+
if s[l] != '.' {
169+
continue
170+
}
171+
j := l - 1
172+
for j >= 0 && s[j] == '\\' {
173+
j--
174+
}
175+
176+
if (j-l)%2 == 0 {
177+
continue
178+
}
179+
180+
n--
181+
if n == 0 {
182+
return l + 1, false
183+
}
161184
}
162-
return lab[len(lab)-n], false
185+
186+
return 0, n > 1
163187
}
164188

165189
// equal compares a and b while ignoring case. It returns true when equal otherwise false.

labels_test.go

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,17 @@ func TestCompareDomainName(t *testing.T) {
4040

4141
func TestSplit(t *testing.T) {
4242
splitter := map[string]int{
43-
"www.miek.nl.": 3,
44-
"www.miek.nl": 3,
45-
"www..miek.nl": 4,
46-
`www\.miek.nl.`: 2,
47-
`www\\.miek.nl.`: 3,
48-
".": 0,
49-
"nl.": 1,
50-
"nl": 1,
51-
"com.": 1,
52-
".com.": 2,
43+
"www.miek.nl.": 3,
44+
"www.miek.nl": 3,
45+
"www..miek.nl": 4,
46+
`www\.miek.nl.`: 2,
47+
`www\\.miek.nl.`: 3,
48+
`www\\\.miek.nl.`: 2,
49+
".": 0,
50+
"nl.": 1,
51+
"nl": 1,
52+
"com.": 1,
53+
".com.": 2,
5354
}
5455
for s, i := range splitter {
5556
if x := len(Split(s)); x != i {
@@ -79,12 +80,32 @@ func TestSplit2(t *testing.T) {
7980
}
8081
}
8182

83+
func TestNextLabel(t *testing.T) {
84+
type next struct {
85+
string
86+
int
87+
}
88+
nexts := map[next]int{
89+
{"", 1}: 0,
90+
{"www.miek.nl.", 0}: 4,
91+
{"www.miek.nl.", 4}: 9,
92+
{"www.miek.nl.", 9}: 12,
93+
}
94+
for s, i := range nexts {
95+
x, ok := NextLabel(s.string, s.int)
96+
if i != x {
97+
t.Errorf("label should be %d, got %d, %t: nexting %d, %s", i, x, ok, s.int, s.string)
98+
}
99+
}
100+
}
101+
82102
func TestPrevLabel(t *testing.T) {
83103
type prev struct {
84104
string
85105
int
86106
}
87107
prever := map[prev]int{
108+
{"", 1}: 0,
88109
{"www.miek.nl.", 0}: 12,
89110
{"www.miek.nl.", 1}: 9,
90111
{"www.miek.nl.", 2}: 4,
@@ -237,3 +258,63 @@ func BenchmarkIsSubDomain(b *testing.B) {
237258
IsSubDomain("miek.nl.", "aa.example.com.")
238259
}
239260
}
261+
262+
func BenchmarkNextLabelSimple(b *testing.B) {
263+
b.ReportAllocs()
264+
for i := 0; i < b.N; i++ {
265+
NextLabel("www.example.com", 0)
266+
NextLabel("www.example.com", 5)
267+
NextLabel("www.example.com", 12)
268+
}
269+
}
270+
271+
func BenchmarkPrevLabelSimple(b *testing.B) {
272+
b.ReportAllocs()
273+
for i := 0; i < b.N; i++ {
274+
PrevLabel("www.example.com", 0)
275+
PrevLabel("www.example.com", 5)
276+
PrevLabel("www.example.com", 12)
277+
}
278+
}
279+
280+
func BenchmarkNextLabelComplex(b *testing.B) {
281+
b.ReportAllocs()
282+
for i := 0; i < b.N; i++ {
283+
NextLabel(`www\.example.com`, 0)
284+
NextLabel(`www\\.example.com`, 0)
285+
NextLabel(`www\\\.example.com`, 0)
286+
}
287+
}
288+
289+
func BenchmarkPrevLabelComplex(b *testing.B) {
290+
b.ReportAllocs()
291+
for i := 0; i < b.N; i++ {
292+
PrevLabel(`www\.example.com`, 10)
293+
PrevLabel(`www\\.example.com`, 10)
294+
PrevLabel(`www\\\.example.com`, 10)
295+
}
296+
}
297+
298+
func BenchmarkNextLabelMixed(b *testing.B) {
299+
b.ReportAllocs()
300+
for i := 0; i < b.N; i++ {
301+
NextLabel("www.example.com", 0)
302+
NextLabel(`www\.example.com`, 0)
303+
NextLabel("www.example.com", 5)
304+
NextLabel(`www\\.example.com`, 0)
305+
NextLabel("www.example.com", 12)
306+
NextLabel(`www\\\.example.com`, 0)
307+
}
308+
}
309+
310+
func BenchmarkPrevLabelMixed(b *testing.B) {
311+
b.ReportAllocs()
312+
for i := 0; i < b.N; i++ {
313+
PrevLabel("www.example.com", 0)
314+
PrevLabel(`www\.example.com`, 10)
315+
PrevLabel("www.example.com", 5)
316+
PrevLabel(`www\\.example.com`, 10)
317+
PrevLabel("www.example.com", 12)
318+
PrevLabel(`www\\\.example.com`, 10)
319+
}
320+
}

0 commit comments

Comments
 (0)