Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion keccak_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ package keccak

import "golang.org/x/sys/cpu"

func init() { useASM = cpu.X86.HasBMI1 && cpu.X86.HasBMI2 }
// hasASM is the CPU probe, recorded once. useASM is the dispatch flag and
// benchmarks flip it, so anything asking "can this CPU run the assembly?"
// must read hasASM.
var hasASM = cpu.X86.HasBMI1 && cpu.X86.HasBMI2

func init() { useASM = hasASM }

// keccakF1600BMI2 permutes state. When buf != nil, it first XORs rate bytes
// of buf into state, saving one full memory pass.
Expand Down
7 changes: 6 additions & 1 deletion keccak_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ import (
// likewise leaves iOS without CPU feature detection (its darwin sysctl
// probing in internal/cpu is build-tagged `darwin && !ios`), so its SHA3
// assembly never runs on iOS either.
// hasASM is the CPU probe, recorded once. useASM is the dispatch flag and
// benchmarks flip it, so anything asking "can this CPU run the assembly?"
// must read hasASM.
var hasASM = runtime.GOOS == "darwin" || cpu.ARM64.HasSHA3

func init() {
useASM = runtime.GOOS == "darwin" || cpu.ARM64.HasSHA3
useASM = hasASM
}

// keccakF1600Sha3 permutes state. When buf != nil, it first XORs rate bytes
Expand Down
53 changes: 53 additions & 0 deletions keccak_asm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package keccak
import (
"math/rand"
"testing"

"golang.org/x/crypto/sha3"
)

// TestXorAndPermute verifies the fused XOR+permute assembly entry point
Expand Down Expand Up @@ -34,3 +36,54 @@ func TestXorAndPermute(t *testing.T) {
}
}
}

// BenchmarkBackends compares the assembly backend against the x/crypto
// fallback on the same CPU, to answer whether the assembly is actually
// faster on this core. It flips useASM, so it must not run in parallel with
// anything else in the package.
func BenchmarkBackends(b *testing.B) {
restore := useASM
defer func() { useASM = restore }()

if hasASM {
b.Run("ASM", func(b *testing.B) { benchmarkBackend(b, true) })
}
b.Run("XCrypto", func(b *testing.B) { benchmarkBackend(b, false) })
}

func benchmarkBackend(b *testing.B, native bool) {
for _, size := range benchSizes {
data := make([]byte, size)
for i := range data {
data[i] = byte(i)
}

ref := sha3.NewLegacyKeccak256().(KeccakState)
ref.Write(data)
var want [32]byte
ref.Read(want[:])

b.Run(benchName(size), func(b *testing.B) {
useASM = native
var h Hasher
// Warm up outside the timer: with useASM false the first Reset
// lazily builds the x/crypto hasher, and that one allocation
// would otherwise be reported as a per-op cost.
h.Reset()
var out [32]byte
b.SetBytes(int64(size))
b.ReportAllocs()
for b.Loop() {
h.Reset()
h.Write(data)
h.Read(out[:])
}
// Against x/crypto, not against the other arm: a -bench filter
// that runs only one arm would leave a cross-arm check with
// nothing to compare, and report success either way.
if out != want {
b.Fatalf("backend digest for %d bytes = %x, want %x", size, out, want)
}
})
}
}