Skip to content

Commit

Permalink
Refactor axis/benchmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
Marc M. Adkins committed Aug 20, 2024
1 parent 01ab17e commit 8112f4d
Show file tree
Hide file tree
Showing 9 changed files with 426 additions and 358 deletions.
26 changes: 26 additions & 0 deletions internal/scoring/axis/bench/average.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package bench

import "github.com/madkins23/go-slog/internal/scoring/score"

// -----------------------------------------------------------------------------

type Average struct {
Value score.Value
Count uint
}

func (ba *Average) Add(v score.Value) *Average {
ba.Value += v
ba.Count++
return ba
}

func (ba *Average) AddMultiple(v score.Value, multiple uint) *Average {
ba.Value += v * score.Value(multiple)
ba.Count += multiple
return ba
}

func (ba *Average) Average() score.Value {
return ba.Value.Round() / score.Value(ba.Count)
}
65 changes: 65 additions & 0 deletions internal/scoring/axis/bench/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package bench

import (
"github.com/madkins23/go-slog/internal/data"
"github.com/madkins23/go-slog/internal/scoring/score"
)

// -----------------------------------------------------------------------------

type RollOver uint8

const (
OverData = iota
OverTests
)

type HandlerData struct {
byTest map[data.TestTag]*Average
originalScore score.Value
scores map[score.Type]score.Value
subScore map[Weight]*Average
rollup map[RollOver]*Average
}

func NewHandlerData() *HandlerData {
hd := &HandlerData{
byTest: make(map[data.TestTag]*Average),
scores: make(map[score.Type]score.Value),
subScore: make(map[Weight]*Average),
rollup: make(map[RollOver]*Average),
}
for _, weight := range WeightOrder {
hd.subScore[weight] = &Average{}
}
return hd
}

func (hd *HandlerData) ByTest(test data.TestTag) *Average {
if hd.byTest[test] == nil {
hd.byTest[test] = &Average{}
}
return hd.byTest[test]
}

func (hd *HandlerData) Rollup(over RollOver) *Average {
if hd.rollup[over] == nil {
hd.rollup[over] = &Average{}
}
return hd.rollup[over]
}

func (hd *HandlerData) Score(scoreType score.Type) score.Value {
return hd.scores[scoreType]
}

func (hd *HandlerData) SetScore(scoreType score.Type, value score.Value) {
hd.scores[scoreType] = value
}

func (hd *HandlerData) SubScore(weight Weight) *Average {
if hd.subScore[weight] == nil {
hd.subScore[weight] = &Average{}
}
return hd.subScore[weight]
}
98 changes: 98 additions & 0 deletions internal/scoring/axis/bench/irange.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package bench

import (
"fmt"
"math"

"github.com/madkins23/go-slog/internal/scoring/score"
)

// -----------------------------------------------------------------------------

type Range interface {
AddValueUint64(val uint64)
AddValueFloat64(val float64)
Length() float64
RangedValue(from float64) score.Value
String() string
}

// -----------------------------------------------------------------------------

var _ Range = &RangeFloat64{}

type RangeFloat64 struct {
low, high float64
}

func NewRangeFloat64() *RangeFloat64 {
return &RangeFloat64{
low: math.MaxFloat64,
high: 0.0,
}
}

func (r *RangeFloat64) AddValueUint64(val uint64) {
r.AddValueFloat64(float64(val))
}

func (r *RangeFloat64) AddValueFloat64(val float64) {
if val < r.low {
r.low = val
}
if val > r.high {
r.high = val
}
}

func (r *RangeFloat64) Length() float64 {
return r.high - r.low
}

func (r *RangeFloat64) RangedValue(from float64) score.Value {
return score.Value(100.0 * (r.high - from) / r.Length())
}

func (r *RangeFloat64) String() string {
return fmt.Sprintf("%0.2f -> %0.2f", r.low, r.high)
}

// -----------------------------------------------------------------------------

var _ Range = &RangeUint64{}

type RangeUint64 struct {
low, high uint64
}

func NewRangeUint64() *RangeUint64 {
return &RangeUint64{
low: math.MaxUint64,
high: 0,
}
}

func (r *RangeUint64) AddValueFloat64(val float64) {
r.AddValueUint64(uint64(val))
}

func (r *RangeUint64) AddValueUint64(val uint64) {
if val < r.low {
r.low = val
}
if val > r.high {
r.high = val
}
}

func (r *RangeUint64) Length() float64 {
return float64(r.high - r.low)
}

func (r *RangeUint64) RangedValue(from float64) score.Value {
return score.Value(100.0 * (float64(r.high) - from) / r.Length())
}

func (r *RangeUint64) String() string {
return fmt.Sprintf("%0d -> %0d", r.low, r.high)
}
156 changes: 156 additions & 0 deletions internal/scoring/axis/bench/original.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package bench

import (
"fmt"
"log/slog"
"math"

"github.com/madkins23/go-slog/internal/data"
"github.com/madkins23/go-slog/internal/scoring/score"
)

type testRange struct {
allocLow, allocHigh uint64
bytesLow, bytesHigh uint64
nanosLow, nanosHigh float64
}

func (tr *testRange) String(bv Weight) string {
switch bv {
case Allocations:
return fmt.Sprintf("%0d -> %0d", tr.allocLow, tr.allocHigh)
case AllocBytes:
return fmt.Sprintf("%0d -> %0d", tr.bytesLow, tr.bytesHigh)
case Nanoseconds:
return fmt.Sprintf("%0.2f -> %0.2f", tr.nanosLow, tr.nanosHigh)
default:
return "<unknown:" + string(bv) + ">"
}
}

// -----------------------------------------------------------------------------

type Original struct {
bench *data.Benchmarks
count, tests uint
collect, total score.Value
ranges map[data.TestTag]*testRange
testTags map[data.TestTag]bool
weight map[Weight]uint
}

func NewOriginal(bench *data.Benchmarks, tagMap map[data.TestTag]bool, weights map[Weight]uint) *Original {
return &Original{
bench: bench,
ranges: make(map[data.TestTag]*testRange),
testTags: tagMap,
weight: weights,
}
}

func (o *Original) HandlerTest(test data.TestTag, record data.TestRecord) {
o.collect = 0
o.count = 0
rngTest := o.ranges[test]
if scoreRange := float64(rngTest.allocHigh - rngTest.allocLow); scoreRange > 0 {
o.collect += score.Value(float64(o.weight[Allocations]) * 100.0 * float64(rngTest.allocHigh-record.MemAllocsPerOp) / scoreRange)
o.count += o.weight[Allocations]
}
if scoreRange := float64(rngTest.bytesHigh - rngTest.bytesLow); scoreRange > 0 {
o.collect += score.Value(float64(o.weight[AllocBytes]) * 100.0 * float64(rngTest.bytesHigh-record.MemBytesPerOp) / scoreRange)
o.count += o.weight[AllocBytes]
}
if scoreRange := rngTest.nanosHigh - rngTest.nanosLow; scoreRange > 0 {
o.collect += score.Value(float64(o.weight[Nanoseconds]) * 100.0 * (rngTest.nanosHigh - record.NanosPerOp) / scoreRange)
o.count += o.weight[Nanoseconds]
}
o.total += o.collect / score.Value(o.count)
o.tests++
}

func (o *Original) CheckRanges(ranges map[data.TestTag]map[Weight]Range) {
for test := range ranges {
if o.testTags[test] {
for _, weight := range WeightOrder {
original := o.ranges[test].String(weight)
byOthers := ranges[test][weight].String()
if byOthers != original {
slog.Error("range comparison", "weight", weight,
"Original", original,
"ByOthers", byOthers)
}
}
}
}
}

func (o *Original) CheckTest(handlerData *HandlerData, test data.TestTag) {
if !fuzzyEqual(o.collect, handlerData.byTest[test].Value) {
slog.Error("collect comparison", "Original", o.collect, "by Test", handlerData.byTest[test].Value)
}
if o.count != handlerData.byTest[test].Count {
slog.Error("count comparison", "Original", o.count, "by Test", handlerData.byTest[test].Count)
}
}

func (o *Original) CheckTotal(handlerData *HandlerData) {
if !fuzzyEqual(o.total.Round(), handlerData.Rollup(OverTests).Value) {
slog.Error("total comparison",
"Original", o.total.Round(),
"by Test", handlerData.Rollup(OverTests).Value)
}
if o.tests != handlerData.Rollup(OverTests).Count {
slog.Warn("count comparison",
"Original", o.tests,
"by Test", handlerData.Rollup(OverTests).Count)
}
}

func (o *Original) MakeRanges() {
for _, test := range o.bench.TestTags() {
if o.testTags[test] {
aRange := &testRange{
allocLow: math.MaxUint64,
bytesLow: math.MaxUint64,
nanosLow: math.MaxFloat64,
}
for _, records := range o.bench.HandlerRecordsFor(test) {
if records.MemAllocsPerOp > aRange.allocHigh {
aRange.allocHigh = records.MemAllocsPerOp
}
if records.MemAllocsPerOp < aRange.allocLow {
aRange.allocLow = records.MemAllocsPerOp
}
if records.MemBytesPerOp > aRange.bytesHigh {
aRange.bytesHigh = records.MemBytesPerOp
}
if records.MemBytesPerOp < aRange.bytesLow {
aRange.bytesLow = records.MemBytesPerOp
}
if records.NanosPerOp > aRange.nanosHigh {
aRange.nanosHigh = records.NanosPerOp
}
if records.NanosPerOp < aRange.nanosLow {
aRange.nanosLow = records.NanosPerOp
}
}
o.ranges[test] = aRange
}
}
}

func (o *Original) ResetForHandler() {
o.tests = 0
o.total = 0
}

func (o *Original) Score() score.Value {
return o.total.Round() / score.Value(o.tests)
}

// -----------------------------------------------------------------------------

func fuzzyEqual(a, b score.Value) bool {
const epsilon = 0.000000001
return math.Abs(float64(a-b)) < epsilon
}
32 changes: 32 additions & 0 deletions internal/scoring/axis/bench/weight.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package bench

import "github.com/madkins23/go-slog/internal/data"

// -----------------------------------------------------------------------------

type Weight string

const (
Allocations Weight = "Allocations"
AllocBytes Weight = "Alloc Bytes"
Nanoseconds Weight = "Nanoseconds"
)

var WeightOrder = []Weight{
Nanoseconds,
AllocBytes,
Allocations,
}

func (bw Weight) Item() data.BenchItems {
switch bw {
case Allocations:
return data.MemAllocs
case AllocBytes:
return data.MemBytes
case Nanoseconds:
return data.Nanos
default:
return 0.0
}
}
Loading

0 comments on commit 8112f4d

Please sign in to comment.