-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve perf on label key validation #52579
base: master
Are you sure you want to change the base?
Conversation
marcoandredinis
commented
Feb 27, 2025
func isValidLabelKeyByte(b byte) bool { | ||
switch b { | ||
case | ||
// Digits | ||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', | ||
|
||
// Lowercase letters | ||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', | ||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', | ||
|
||
// Uppercase letters | ||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', | ||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', | ||
|
||
// Allowed symbols | ||
'/', '.', '_', ':', '*', '-': | ||
return true | ||
default: | ||
return false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: don't have strong opinion:
func isValidLabelKeyByte(b byte) bool { | |
switch b { | |
case | |
// Digits | |
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', | |
// Lowercase letters | |
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', | |
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', | |
// Uppercase letters | |
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', | |
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', | |
// Allowed symbols | |
'/', '.', '_', ':', '*', '-': | |
return true | |
default: | |
return false | |
} | |
func isValidLabelKeyByte(b byte) bool { | |
return ('0' <= b && b <= '9') || // Digits | |
('a' <= b && b <= 'z') || // Lowercase letters | |
('A' <= b && b <= 'Z') || // Uppercase letters | |
b == '/' || b == '.' || b == '_' || b == ':' || b == '*' || b == '-' // Allowed symbols | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On my cpu this is actually a tiny bit faster (82ns/op vs 89ns/op) but it depends on the data (it becomes worse after adding another test case that has a lot of symbols), I think that the big switch table gives the compiler the best chance to optimize the code now and in the future with better bitwise and numerical tricks (that being said, the chain of conditions and the switch are equivalent so a Sufficiently Smart Compiler should just generate the exact same code for both, but that's basically a pipe dream for Go unfortunately).
I also prefer the aesthetic of the table of letters and numbers. 😌
"label^/.:-LABEL12__34*", | ||
} | ||
|
||
for i := 0; i < b.N; i++ { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for i := 0; i < b.N; i++ { | |
for range b.N { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of constraining this test file (or a secondary file for the benchmark) to go 1.24 instead of using b.N?
@@ -63,3 +63,19 @@ func TestIsValidLabelKey(t *testing.T) { | |||
}) | |||
} | |||
} | |||
|
|||
func BenchmarkIsValidLabelKey(b *testing.B) { | |||
var labelsForBenchmark = []string{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var labelsForBenchmark = []string{ | |
labelsForBenchmark := []string{ |
func isValidLabelKeyByte(b byte) bool { | ||
switch b { | ||
case | ||
// Digits | ||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', | ||
|
||
// Lowercase letters | ||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', | ||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', | ||
|
||
// Uppercase letters | ||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', | ||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', | ||
|
||
// Allowed symbols | ||
'/', '.', '_', ':', '*', '-': | ||
return true | ||
default: | ||
return false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On my cpu this is actually a tiny bit faster (82ns/op vs 89ns/op) but it depends on the data (it becomes worse after adding another test case that has a lot of symbols), I think that the big switch table gives the compiler the best chance to optimize the code now and in the future with better bitwise and numerical tricks (that being said, the chain of conditions and the switch are equivalent so a Sufficiently Smart Compiler should just generate the exact same code for both, but that's basically a pipe dream for Go unfortunately).
I also prefer the aesthetic of the table of letters and numbers. 😌