对于strings.Index,已经存在一个专门的版本strings.IndexByte,除了strings.IndexRune。目前没有strings.Contains的Byte变体,但存在strings.ContainsRune。
从大型Go代码语料库中抽样:
- ~1/3的strings.Contains调用使用长度为1的ASCII字符字符串
- strings.Contains出现的频率比strings.ContainsRune高约100倍
- strings.Contains出现的频率比strings.Index和strings.IndexByte加起来高约6倍
当使用Contains或ContainsRune代替ContainsByte(使用IndexByte)时,会有一定的性能损失(二进制代码大小、从内存加载字符串的开销等)。
让内联器充分了解Contains和ContainsRune(可能需要代码布局更改),以便直接内联IndexByte调用。
替代方案
在strings和bytes包中添加一个专门的ContainsByte函数。
缺点:当前代码不会立即获得好处,除非进行更改。
数据
name time/op
Contains 5.88ns ± 1%
ContainsByte 3.30ns ± 0%
ContainsRune 5.40ns ± 1%
ContainsRuneFastPath 5.33ns ± 1%
基准测试(我看到由于分支地址取决于基准测试循环的位置而有所不同,这里的结果倾向于我的工作站上的Contains)
var global bool
var data = "golang:contains"
func ContainsByte(s string, b byte) bool {
return strings.IndexByte(s, b) >= 0
}
func ContainsRuneFastPath(s string, r rune) bool {
if r <= utf8.RuneSelf {
return strings.IndexByte(s, byte(r)) >= 0
}
return strings.IndexRune(s, r) >= 0
}
func BenchmarkContains(b *testing.B) {
var sink bool
for i := 0; i < b.N; i++ {
// ...
// LEAQ 0x4c655(IP), AX // = 7 bytes
// MOVQ AX, 0x10(SP) // = 5 bytes
// MOVQ $0x1, 0x18(SP) // = 9 bytes
sink = strings.Contains(data, ":")
}
global = sink
}
func BenchmarkContainsByte(b *testing.B) {
var sink bool
for i := 0; i < b.N; i++ {
// ...
// MOVB $0x3a, 0x10(SP) // = 5 Bytes
// CALL internal/bytealg.IndexByteString(SB)
sink = ContainsByte(data, ':')
}
global = sink
}
func BenchmarkContainsRune(b *testing.B) {
var sink bool
for i := 0; i < b.N; i++ {
// ...
// MOVL $0x3a, 0x10(SP) // = 8 bytes
sink = strings.ContainsRune(data, ':')
}
global = sink
}
func BenchmarkContainsRuneFastPath(b *testing.B) {
var sink bool
for i := 0; i < b.N; i++ {
sink = ContainsRuneFastPath(data, ':')
}
global = sink
}
3条答案
按热度按时间7xllpg7q1#
/cc @bradfitz@ianlancetaylor
dnph8jn42#
在阅读了@rsc在#46336中的评论后:
我最近搜索了strings.Index, strings.IndexByte, 或strings.IndexRune在主仓库中的用法[...] 20应该是Contains [...] 2应该是1次ContainsAny调用
,我也做了同样的操作。我发现的一个用法是:
似乎将这个
IndexByte
转换为Contains
,ContainsAny
, 或ContainsRune
会带来性能损失。我去寻找一个ContainsByte
函数,并找到了这个问题。gtlvzcf83#
对于这个特定情况,看起来你可以使用net.JoinHostPort。