Skip to content
Open
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
10 changes: 10 additions & 0 deletions src/crypto/internal/fips140/sha512/sha512.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ const (
blockSize = 128
)

// The maximum number of bytes that can be passed to block(). The limit exists
// because implementations that rely on assembly routines are not preemptible.
const maxAsmIters = 512
const maxAsmSize = blockSize * maxAsmIters // 64KiB

const (
chunk = 128
init0 = 0x6a09e667f3bcc908
Expand Down Expand Up @@ -248,6 +253,11 @@ func (d *Digest) Write(p []byte) (nn int, err error) {
}
if len(p) >= chunk {
n := len(p) &^ (chunk - 1)
for n > maxAsmSize {
block(d, p[:maxAsmSize])
p = p[maxAsmSize:]
n -= maxAsmSize
}
block(d, p[:n])
p = p[n:]
}
Expand Down
10 changes: 10 additions & 0 deletions src/crypto/sha1/sha1.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ const Size = 20
// The blocksize of SHA-1 in bytes.
const BlockSize = 64

// The maximum number of bytes that can be passed to block(). The limit exists
// because implementations that rely on assembly routines are not preemptible.
const maxAsmIters = 1024
const maxAsmSize = BlockSize * maxAsmIters // 64KiB

const (
chunk = 64
init0 = 0x67452301
Expand Down Expand Up @@ -143,6 +148,11 @@ func (d *digest) Write(p []byte) (nn int, err error) {
}
if len(p) >= chunk {
n := len(p) &^ (chunk - 1)
for n > maxAsmSize {
block(d, p[:maxAsmSize])
p = p[maxAsmSize:]
n -= maxAsmSize
}
block(d, p[:n])
p = p[n:]
}
Expand Down
58 changes: 55 additions & 3 deletions src/crypto/sha1/sha1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import (
"fmt"
"hash"
"io"
"runtime"
"sync/atomic"
"testing"
"time"
)

type sha1Test struct {
Expand Down Expand Up @@ -267,24 +270,24 @@ func TestOutOfBoundsRead(t *testing.T) {
}

var bench = New()
var buf = make([]byte, 8192)

func benchmarkSize(b *testing.B, size int) {
buf := make([]byte, size)
sum := make([]byte, bench.Size())
b.Run("New", func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(size))
for i := 0; i < b.N; i++ {
bench.Reset()
bench.Write(buf[:size])
bench.Write(buf)
bench.Sum(sum[:0])
}
})
b.Run("Sum", func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(size))
for i := 0; i < b.N; i++ {
Sum(buf[:size])
Sum(buf)
}
})
}
Expand All @@ -304,3 +307,52 @@ func BenchmarkHash1K(b *testing.B) {
func BenchmarkHash8K(b *testing.B) {
benchmarkSize(b, 8192)
}

func BenchmarkHash256K(b *testing.B) {
benchmarkSize(b, 256*1024)
}

func BenchmarkHash1M(b *testing.B) {
benchmarkSize(b, 1024*1024)
}

var sinkSTW []byte

// BenchmarkSTW reports how long a garbage collection had to wait while a hash
// ran alongside it, as gcwait-ns/op. Assembly is not preemptible, so a call
// that covers the whole input blocks every goroutine in the process for as
// long as it runs; bounding the call gives the collector a way in between
// chunks. Run with GOMAXPROCS>=2 so the two actually overlap.
func BenchmarkSTW(b *testing.B) {
buf := make([]byte, 64<<20)
var total time.Duration
var iters int
b.SetBytes(int64(len(buf)))
for b.Loop() {
done := make(chan struct{})
var began atomic.Int64
go func() {
defer close(done)
began.Store(time.Now().UnixNano())
h := New()
h.Write(buf)
sinkSTW = h.Sum(nil)
}()
start := time.Now()
runtime.GC() // one per iteration, so the mean is well defined
end := time.Now()
<-done

// Count the iteration only if the hash had started before the
// collection finished. Otherwise there was nothing to overlap and the
// sample says nothing about preemptibility.
if t := began.Load(); t != 0 && t < end.UnixNano() {
total += end.Sub(start)
iters++
}
}
if iters == 0 {
b.Skip("no iteration overlapped a collection")
}
b.ReportMetric(float64(total.Nanoseconds())/float64(iters), "gcwait-ns/op")
}
60 changes: 56 additions & 4 deletions src/crypto/sha512/sha512_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import (
"fmt"
"hash"
"io"
"runtime"
"sync/atomic"
"testing"
"time"
)

type sha512Test struct {
Expand Down Expand Up @@ -998,31 +1001,31 @@ func maybeCloner(h hash.Hash) any {
}

var bench = New()
var buf = make([]byte, 8192)

func benchmarkSize(b *testing.B, size int) {
buf := make([]byte, size)
sum := make([]byte, bench.Size())
b.Run("New", func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(size))
for i := 0; i < b.N; i++ {
bench.Reset()
bench.Write(buf[:size])
bench.Write(buf)
bench.Sum(sum[:0])
}
})
b.Run("Sum384", func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(size))
for i := 0; i < b.N; i++ {
Sum384(buf[:size])
Sum384(buf)
}
})
b.Run("Sum512", func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(size))
for i := 0; i < b.N; i++ {
Sum512(buf[:size])
Sum512(buf)
}
})
}
Expand All @@ -1038,3 +1041,52 @@ func BenchmarkHash1K(b *testing.B) {
func BenchmarkHash8K(b *testing.B) {
benchmarkSize(b, 8192)
}

func BenchmarkHash256K(b *testing.B) {
benchmarkSize(b, 256*1024)
}

func BenchmarkHash1M(b *testing.B) {
benchmarkSize(b, 1024*1024)
}

var sinkSTW []byte

// BenchmarkSTW reports how long a garbage collection had to wait while a hash
// ran alongside it, as gcwait-ns/op. Assembly is not preemptible, so a call
// that covers the whole input blocks every goroutine in the process for as
// long as it runs; bounding the call gives the collector a way in between
// chunks. Run with GOMAXPROCS>=2 so the two actually overlap.
func BenchmarkSTW(b *testing.B) {
buf := make([]byte, 64<<20)
var total time.Duration
var iters int
b.SetBytes(int64(len(buf)))
for b.Loop() {
done := make(chan struct{})
var began atomic.Int64
go func() {
defer close(done)
began.Store(time.Now().UnixNano())
h := New()
h.Write(buf)
sinkSTW = h.Sum(nil)
}()
start := time.Now()
runtime.GC() // one per iteration, so the mean is well defined
end := time.Now()
<-done

// Count the iteration only if the hash had started before the
// collection finished. Otherwise there was nothing to overlap and the
// sample says nothing about preemptibility.
if t := began.Load(); t != 0 && t < end.UnixNano() {
total += end.Sub(start)
iters++
}
}
if iters == 0 {
b.Skip("no iteration overlapped a collection")
}
b.ReportMetric(float64(total.Nanoseconds())/float64(iters), "gcwait-ns/op")
}