Closed
Description
Go version
go version go1.22.2 linux/amd64
Output of go env
in your module/workspace:
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/ys/.cache/go-build'
GOENV='/home/ys/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/ys/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/ys/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/snap/go/10585'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/snap/go/10585/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.22.2'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/home/ys/dos/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2137305734=/tmp/go-build -gno-record-gcc-switches'
What did you do?
Consider these examples:
package main
import (
"encoding/binary"
"fmt"
"hash/maphash"
"math/rand"
)
func main() {
// Note: only works on -tags purego.
seed := maphash.MakeSeed()
for n := 0; n < 10; n++ {
var buf [16]byte
binary.LittleEndian.PutUint32(buf[:], 0xe7037ed1) // Upper bits of constant m2.
binary.LittleEndian.PutUint32(buf[4:], rand.Uint32()) // Arbitrary bits.
binary.LittleEndian.PutUint32(buf[8:], 0xa0b428db) // Lower bits of constant m2.
binary.LittleEndian.PutUint32(buf[12:], rand.Uint32()) // Arbitrary bits.
fmt.Printf("%x\n", buf[:])
fmt.Printf("%016x\n", maphash.Bytes(seed, buf[:]))
}
}
package main
import (
"encoding/binary"
"math/rand"
"testing"
_ "unsafe" // Only for the linkname.
)
const (
width = 1 << 10
)
var (
sink bool
)
var (
//go:linkname useAeshash runtime.useAeshash
useAeshash bool
)
func BenchmarkMap(b *testing.B) {
useAeshash = false
tab := make(map[string]bool, width)
var keys [width]string
for n := range width {
var buf [16]byte
binary.LittleEndian.PutUint64(buf[:], 0xe7037ed1a0b428db)
binary.LittleEndian.PutUint64(buf[8:], rand.Uint64())
key := string(buf[:])
keys[n] = key
tab[key] = true
}
b.ResetTimer()
b.ReportAllocs()
for n := range b.N {
sink = sink != tab[keys[n%width]]
}
}
What did you see happen?
If you run the first program with go run -tags purego .
, you will see all the values hashed into 0.
If you run the second program like go test -bench . -count 10
, it will report ~2000 ns/op, while it will report ~18 ns/op on other cases.
What did you expect to see?
While machines without AES or hash/maphash
with purego tag are not so common, I think it's important to prevent issues like this.
The cause is that it relies on simple multiplications, which means we can use the XORed constant as inputs to cancel the effect, essentially making one of the operand 0, which in turn makes the whole product 0. I don't really have good fixes to make without losing much speed.