Skip to content

Instantly share code, notes, and snippets.

@jpeletier
Created September 4, 2019 10:18
Show Gist options
  • Save jpeletier/a7e36762f9b4f89e10247f8f8c0151c6 to your computer and use it in GitHub Desktop.
Save jpeletier/a7e36762f9b4f89e10247f8f8c0151c6 to your computer and use it in GitHub Desktop.
Hasher pooling benchmark
package experiments
import (
"encoding/binary"
"fmt"
"math/rand"
"strconv"
"strings"
"sync"
"testing"
"github.com/ethereum/go-ethereum/log"
"github.com/ethersphere/swarm/bmt"
"github.com/ethersphere/swarm/storage"
)
func init() {
log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StderrHandler))
}
type hasher interface {
hash([]byte) []byte
}
type simple struct {
mu sync.Mutex
hasher storage.SwarmHash
}
func newSimple(hasherFactory storage.SwarmHasher) *simple {
return &simple{
hasher: hasherFactory(),
}
}
func (s *simple) hash(data []byte) []byte {
s.mu.Lock()
defer s.mu.Unlock()
return doHash(s.hasher, data)
}
type instancer struct {
hashFunc storage.SwarmHasher
}
func newInstancer(hasherFactory storage.SwarmHasher) *instancer {
return &instancer{
hashFunc: hasherFactory,
}
}
func (i *instancer) hash(data []byte) []byte {
hasher := i.hashFunc()
resetLength := make([]byte, 8)
binary.LittleEndian.PutUint64(resetLength, uint64(len(data)))
_, ok := hasher.(*bmt.Hasher)
if ok {
hasher.ResetWithLength(resetLength)
} else {
hasher.Write(resetLength)
}
hasher.Write(data)
return hasher.Sum(nil)
}
type pool struct {
workers sync.Pool
inc int
}
func newPool(hasherFactory storage.SwarmHasher, workerCount int64) *pool {
p := &pool{}
p.workers = sync.Pool{
New: func() interface{} {
p.inc++
return hasherFactory()
},
}
for i := int64(0); i < workerCount; i++ {
p.workers.Put(hasherFactory())
}
return p
}
func (p *pool) hash(data []byte) []byte {
h := p.workers.Get().(storage.SwarmHash)
defer p.workers.Put(h)
return doHash(h, data)
}
func doHash(h storage.SwarmHash, data []byte) []byte {
resetLength := make([]byte, 8)
binary.LittleEndian.PutUint64(resetLength, uint64(len(data)))
h.ResetWithLength(resetLength)
h.Write(data)
return h.Sum(nil)
}
func BenchmarkPool(b *testing.B) {
methods := []string{"simple", "pool", "instance"}
algos := []string{"BMT", "SHA3", "SHA256"}
runBenchmark := func(workers, passes, size int) {
for _, algo := range algos {
for _, method := range methods {
b.Run(fmt.Sprintf("%s/%d/%d/%d/%s", method, workers, passes, size, algo), benchmarkPool)
}
}
}
runBenchmark(100, 100, 256)
runBenchmark(100, 1000, 256)
runBenchmark(100, 100, 4096)
runBenchmark(100, 1000, 4096)
runBenchmark(1000, 1000, 256)
runBenchmark(1000, 10000, 256)
runBenchmark(1000, 1000, 4096)
runBenchmark(1000, 10000, 4096)
runBenchmark(100, 10000, 256)
runBenchmark(100, 10000, 4096)
}
func benchmarkPool(b *testing.B) {
params := strings.Split(b.Name(), "/")
mode := params[1]
workers, _ := strconv.ParseInt(params[2], 10, 0)
passes, _ := strconv.ParseInt(params[3], 10, 0)
size, _ := strconv.ParseInt(params[4], 10, 0)
algo := params[5]
data := make([]byte, passes+size)
c, err := rand.Read(data)
if int64(c) != passes+size {
b.Fatal("short read")
}
if err != nil {
b.Fatal(err)
}
swarmHasher := storage.MakeHashFunc(algo)
var hasherUsed hasher
switch mode {
case "simple":
hasherUsed = newSimple(swarmHasher)
case "pool":
hasherUsed = newPool(swarmHasher, workers)
default:
hasherUsed = newInstancer(swarmHasher)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
wg := sync.WaitGroup{}
for j := int64(0); j < passes; j++ {
wg.Add(1)
go func(j int64) {
hasherUsed.hash(data[j : j+size])
wg.Done()
}(j)
}
wg.Wait()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment