go strings,bytes: tune inliner for Contains or add ContainsByte

8iwquhpp  于 5个月前  发布在  Go
关注(0)|答案(3)|浏览(42)

对于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
}
7xllpg7q

7xllpg7q1#

/cc @bradfitz@ianlancetaylor

dnph8jn4

dnph8jn42#

在阅读了@rsc在#46336中的评论后:
我最近搜索了strings.Index, strings.IndexByte, 或strings.IndexRune在主仓库中的用法[...] 20应该是Contains [...] 2应该是1次ContainsAny调用
,我也做了同样的操作。我发现的一个用法是:

func (e *Endpoint) String() string {
	if strings.IndexByte(e.Host, ':') != -1 {
		return fmt.Sprintf("[%s]:%d", e.Host, e.Port)
	}
	return fmt.Sprintf("%s:%d", e.Host, e.Port)
}

似乎将这个IndexByte转换为Contains, ContainsAny, 或ContainsRune会带来性能损失。我去寻找一个ContainsByte函数,并找到了这个问题。

gtlvzcf8

gtlvzcf83#

对于这个特定情况,看起来你可以使用net.JoinHostPort。

相关问题