go ``` cmd/compile: 调整分支消除启发式算法在amd64上的参数 ```

w8biq8rn  于 6个月前  发布在  Go
关注(0)|答案(5)|浏览(53)

你正在使用的Go版本是什么( go version )?

master:
go version devel +cd1976d Tue May 8 19:57:49 2018 +0000 linux/amd64

这个问题在最新版本中是否会重现?

是的

你正在使用什么操作系统和处理器架构( go env )?

GOARCH="amd64"
GOBIN=""
GOCACHE="/localdisk/itocar/gocache/"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/localdisk/itocar/gopath"
GORACE=""
GOROOT="/localdisk/itocar/golang"
GOTMPDIR=""
GOTOOLDIR="/localdisk/itocar/golang/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build948974604=/tmp/go-build -gno-record-gcc-switches"

你做了什么?

运行strconv/Atof基准测试

你期望看到什么?

性能与1.10相同或更好

你看到了什么?

Atof64Decimal-8       20.9ns ± 1%  20.3ns ± 2%   -3.11%  (p=0.000 n=10+10)
Atof64Float-8         22.9ns ± 1%  23.1ns ± 2%   +0.83%  (p=0.042 n=10+10)
Atof64FloatExp-8      56.3ns ± 3%  68.8ns ± 2%  +22.11%  (p=0.000 n=10+10)
Atof64Big-8           84.0ns ± 1%  94.0ns ± 1%  +11.91%  (p=0.000 n=10+10)
Atof64RandomBits-8    72.9ns ±15%  80.5ns ±24%  +10.47%  (p=0.022 n=10+9)
Atof64RandomFloats-8  80.2ns ±25%  91.6ns ±19%  +14.27%  (p=0.029 n=10+10)
Atof32Decimal-8       20.1ns ± 2%  20.5ns ± 2%   +1.59%  (p=0.008 n=10+10)
Atof32Float-8         21.5ns ± 3%  21.9ns ± 3%   +2.09%  (p=0.012 n=10+10)
Atof32FloatExp-8      58.6ns ± 3%  68.2ns ± 2%  +16.49%  (p=0.000 n=10+10)
Atof32Random-8        93.8ns ± 0%  86.4ns ± 0%   -7.83%  (p=0.000 n=10+8)

二分查找指向a35ec9a
查看代码后,我发现(*extFloat).Normalize变慢了,可能是因为分支预测很好,而且大多数指令都依赖于分支的结果。
看起来生成CMOV的启发式方法应该进行调整,但我不确定如何操作。当前的阈值很低,进一步降低可能会对其他情况产生性能影响。也许我们应该避免生成长链的依赖CMOV?

1zmg4dgp

1zmg4dgp2#

比较软件包strings在Go tip和Go 1.10.2上的差异:

SingleMaxSkipping-4     1.36µs ± 0%     3.21µs ± 0%   +135.09%  (p=0.008 n=5+5)

主要回归发生在a35ec9a(1675ns/op -> 3201ns/op)。

jum4pzuy

jum4pzuy3#

通过粗略的观察,似乎回归基准总是使用固定输入执行代码。例如,如果你查看 Atof64FloatExp(+22% 的低方差),它是:

func BenchmarkAtof64FloatExp(b *testing.B) {
	for i := 0; i < b.N; i++ {
		ParseFloat("-5.09e75", 64)
	}
}

所以它总是解析相同的数字,因此CPU能够很好地预测接下来会发生什么。同样适用于 SingleMaxSkipping:

func BenchmarkSingleMaxSkipping(b *testing.B) {
	benchmarkSingleString(b, Repeat("b", 25), Repeat("a", 10000))
}

所以,是的,如果分支预测得很好,CMOV 可以变慢。@TocarIP 你用于基准测试的确切CPU型号是什么?
另一方面,在随机输入的情况下,它确实更快:

Atof32Random-8        93.8ns ± 0%  86.4ns ± 0%   -7.83%  (p=0.000 n=10+8)

我不确定该怎么办。
PS:@TocarIP FWIW,(*extFloat).Normalize 应该使用 bits.LeadingZeros64 并避免一开始的所有分支:

func (f *extFloat) Normalize() uint {
    shift := bits.LeadingZeros64(f.mant)
    f.mant <<= uint(shift)
    f.exp -= shift
    return uint(shift)
}
fd3cxomn

fd3cxomn4#

https://golang.org/cl/113256提到了这个问题:strconv: simplify (*extFloat).Normalize

vc9ivgsu

vc9ivgsu5#

我使用了i7-6700。同意关于(*extFloat).Normalize的看法。
顺便说一下,在singleMaxSkipping cmov中,只生成了内联的

func max(a, b int) int {
        if a > b {
                return a
        }
        return b
}

指令。这是我们想要生成CMOV的情况,同时gcc/clang也生成了CMOV。

相关问题