From dcb0c087b920713fbd593833b5b0a8cab300f783 Mon Sep 17 00:00:00 2001 From: danhale-git <36298392+danhale-git@users.noreply.github.com> Date: Thu, 16 Jul 2020 19:13:01 +0100 Subject: [PATCH] Vector benchmark (#594) * Reflect and ReflectSurface benchmark. * Rotate, NinetyAnti and NinetyClock benchmark. * Equals, EqualsApprox, CompareApprox and IsZero benchmark. * Benchmarks for everything else. Also removing dead functions and commented out code and correcting typos. --- d2common/d2math/d2vector/vector.go | 3 +- d2common/d2math/d2vector/vector_test.go | 386 ++++++++++++++++++------ 2 files changed, 295 insertions(+), 94 deletions(-) diff --git a/d2common/d2math/d2vector/vector.go b/d2common/d2math/d2vector/vector.go index 31ff3834..e350aac5 100644 --- a/d2common/d2math/d2vector/vector.go +++ b/d2common/d2math/d2vector/vector.go @@ -284,7 +284,8 @@ func (v *Vector) ReflectSurface(surface Vector) *Vector { return v } -// Rotate moves the vector around it's origin clockwise, by the given angle in radians. +// Rotate moves the vector around it's origin clockwise, by the given angle in radians. The result will be exact within +// d2math.Epsilon. See d2math.EqualsApprox. func (v *Vector) Rotate(angle float64) *Vector { a := -angle x := v.x*math.Cos(a) - v.y*math.Sin(a) diff --git a/d2common/d2math/d2vector/vector_test.go b/d2common/d2math/d2vector/vector_test.go index 71ed9d93..76280a19 100644 --- a/d2common/d2math/d2vector/vector_test.go +++ b/d2common/d2math/d2vector/vector_test.go @@ -7,50 +7,17 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math" ) -/*func TestMain(m *testing.M) { - setup() - os.Exit(m.Run()) -}*/ +//nolint:gochecknoglobals // These variables are assigned to in benchmark functions to avoid compiler optimisations +// lowering the runtime of the benchmark. See: https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go (A note +// on compiler optimisations) +var ( + outVector Vector + outFloat float64 + outBool bool + outInt int +) -var outVector Vector -var outFloat float64 - -/*func setup() { -}*/ - -// TODO: Remove these evaluate functions. Throwing test errors outside the relevant functions means otherwise handly links to failed tests now point here which is annoying. - -func evaluateVector(description string, want, got Vector, t *testing.T) { - if !got.Equals(want) { - t.Errorf("%s: wanted %s: got %s", description, want, got) - } -} - -func evaluateVectorApprox(description string, want, got Vector, t *testing.T) { - if !got.EqualsApprox(want) { - t.Errorf("%s: wanted %s: got %s", description, want, got) - } -} - -func evaluateScalar(description string, want, got float64, t *testing.T) { - if want != got { - t.Errorf("%s: wanted %f: got %f", description, want, got) - } -} - -func evaluateScalarApprox(description string, want, got float64, t *testing.T) { - if d2math.CompareFloat64Fuzzy(want, got) != 0 { - t.Errorf("%s: wanted %f: got %f", description, want, got) - } -} - -func evaluateChanged(description string, original, clone Vector, t *testing.T) { - if !original.Equals(clone) { - t.Errorf("%s: changed vector %s to %s unexpectedly", description, clone, original) - } -} - -func TestEquals(t *testing.T) { +func TestVector_Equals(t *testing.T) { a := NewVector(1, 2) b := NewVector(1, 2) @@ -69,7 +36,16 @@ func TestEquals(t *testing.T) { } } -func TestEqualsF(t *testing.T) { +func BenchmarkVector_Equals(b *testing.B) { + v := NewVector(1, 1) + o := NewVector(2, 2) + + for n := 0; n < b.N; n++ { + outBool = v.Equals(o) + } +} + +func TestVector_EqualsApprox(t *testing.T) { subEpsilon := d2math.Epsilon / 3 a := NewVector(1, 2) @@ -90,7 +66,16 @@ func TestEqualsF(t *testing.T) { } } -func TestCompareApprox(t *testing.T) { +func BenchmarkVector_EqualsApprox(b *testing.B) { + v := NewVector(1, 1) + o := NewVector(2, 2) + + for n := 0; n < b.N; n++ { + outBool = v.EqualsApprox(o) + } +} + +func TestVector_CompareApprox(t *testing.T) { subEpsilon := d2math.Epsilon / 3 f := NewVector(1+subEpsilon, 1+subEpsilon) @@ -121,7 +106,16 @@ func TestCompareApprox(t *testing.T) { } } -func TestIsZero(t *testing.T) { +func BenchmarkVector_CompareApprox(b *testing.B) { + v := NewVector(1, 1) + o := NewVector(2, 2) + + for n := 0; n < b.N; n++ { + outInt, outInt = v.CompareApprox(o) + } +} + +func TestVector_IsZero(t *testing.T) { testIsZero(NewVector(0, 0), true, t) testIsZero(NewVector(1, 0), false, t) testIsZero(NewVector(0, 1), false, t) @@ -135,40 +129,89 @@ func testIsZero(v Vector, want bool, t *testing.T) { } } -func TestSet(t *testing.T) { +func BenchmarkVector_IsZero(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + outBool = v.IsZero() + } +} + +func TestVector_Set(t *testing.T) { v := NewVector(1, 1) want := NewVector(2, 3) got := v.Clone() got.Set(2, 3) - evaluateVector(fmt.Sprintf("set %s to (2, 3)", v), want, got, t) + if !got.Equals(want) { + t.Errorf("set %s to (2, 3): want %s: got %s", v, want, got) + } } -func TestClone(t *testing.T) { +func BenchmarkVector_Set(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + v.Set(1, 1) + } +} + +func TestVector_Clone(t *testing.T) { want := NewVector(1, 2) got := want.Clone() - evaluateVector(fmt.Sprintf("clone %s", want), want, got, t) + if !got.Equals(want) { + t.Errorf("clone %s: want %s: got %s", want, want, got) + } } -func TestCopy(t *testing.T) { +func BenchmarkVector_Clone(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + outVector = v.Clone() + } +} + +func TestVector_Copy(t *testing.T) { want := NewVector(1, 2) got := NewVector(0, 0) got.Copy(&want) - evaluateVector(fmt.Sprintf("copy %s to %s", got, want), want, got, t) + if !got.Equals(want) { + t.Errorf("copy %s to %s: want %s: got %s", got, want, want, got) + } } -func TestFloor(t *testing.T) { +func BenchmarkVector_Copy(b *testing.B) { + v := NewVector(1, 1) + o := NewVector(2, 2) + + for n := 0; n < b.N; n++ { + v.Copy(&o) + } +} + +func TestVector_Floor(t *testing.T) { v := NewVector(1.6, 1.6) want := NewVector(1, 1) got := v.Clone() got.Floor() - evaluateVector(fmt.Sprintf("round %s down", v), want, got, t) + if !got.Equals(want) { + t.Errorf("round %s down: want %s: got %s", v, want, got) + } } -func TestClamp(t *testing.T) { +func BenchmarkVector_Floor(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + v.Floor() + } +} + +func TestVector_Clamp(t *testing.T) { v := NewVector(-10, 10) c := v.Clone() a := NewVector(2, 2) @@ -177,76 +220,162 @@ func TestClamp(t *testing.T) { want := NewVector(2, 7) got := v.Clamp(&a, &b) - evaluateVector(fmt.Sprintf("clamp %s between %s and %s", c, a, b), want, *got, t) + if !got.Equals(want) { + t.Errorf("clamp %s between %s and %s: want %s: got %s", c, a, b, want, *got) + } } -func TestAdd(t *testing.T) { +func BenchmarkVector_Clamp(b *testing.B) { + v := NewVector(1, 1) + min := NewVector(0, 0) + max := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + v.Clamp(&min, &max) + } +} + +func TestVector_Add(t *testing.T) { v := NewVector(1, 2) add := NewVector(0.5, 3) want := NewVector(1.5, 5) got := v.Clone() got.Add(&add) - evaluateVector(fmt.Sprintf("add %s to %s", add, v), want, got, t) + if !got.Equals(want) { + t.Errorf("add %s to %s: want %s: got %s", add, v, want, got) + } } -func TestAddScalar(t *testing.T) { +func BenchmarkVector_Add(b *testing.B) { + v := NewVector(1, 1) + o := NewVector(0.01, 0.01) + + for n := 0; n < b.N; n++ { + v.Add(&o) + } +} + +func TestVector_AddScalar(t *testing.T) { v := NewVector(1, -1) add := 0.5 want := NewVector(1.5, -0.5) got := v.Clone() got.AddScalar(add) - evaluateVector(fmt.Sprintf("add %.2f to %s", add, v), want, got, t) + if !got.Equals(want) { + t.Errorf("add %.2f to %s: want %s: got %s", add, v, want, got) + } } -func TestSubtract(t *testing.T) { +func BenchmarkVector_AddScalar(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + v.AddScalar(0.01) + } +} + +func TestVector_Subtract(t *testing.T) { v := NewVector(1, 1) subtract := NewVector(0.6, 0.6) want := NewVector(0.4, 0.4) got := v.Clone() got.Subtract(&subtract) - evaluateVector(fmt.Sprintf("subtract %s from %s", subtract, v), want, got, t) + if !got.Equals(want) { + t.Errorf("subtract %s from %s: want %s: got %s", subtract, v, want, got) + } } -func TestMultiply(t *testing.T) { +func BenchmarkVector_Subtract(b *testing.B) { + v := NewVector(1, 1) + o := NewVector(0.01, 0.01) + + for n := 0; n < b.N; n++ { + v.Subtract(&o) + } +} + +func TestVector_Multiply(t *testing.T) { v := NewVector(1, 1) multiply := NewVector(2, 2) want := NewVector(2, 2) got := v.Clone() got.Multiply(&multiply) - evaluateVector(fmt.Sprintf("multiply %s by %s", v, multiply), want, got, t) + if !got.Equals(want) { + t.Errorf("multiply %s by %s: want %s: got %s", v, multiply, want, got) + } } -func TestDivide(t *testing.T) { +func BenchmarkVector_Multiply(b *testing.B) { + v := NewVector(1, 1) + o := NewVector(0.01, 0.01) + + for n := 0; n < b.N; n++ { + v.Multiply(&o) + } +} + +func TestVector_Divide(t *testing.T) { v := NewVector(1, 8) divide := NewVector(2, 4) want := NewVector(0.5, 2) got := v.Clone() got.Divide(÷) - evaluateVector(fmt.Sprintf("divide %s by %s", v, divide), want, got, t) + if !got.Equals(want) { + t.Errorf("divide %s by %s: want %s: got %s", v, divide, want, got) + } } -func TestDivideScalar(t *testing.T) { +func BenchmarkVector_Divide(b *testing.B) { + v := NewVector(1, 1) + o := NewVector(3, 3) + + for n := 0; n < b.N; n++ { + v.Divide(&o) + } +} + +func TestVector_DivideScalar(t *testing.T) { v := NewVector(1, 2) divide := 2.0 want := NewVector(0.5, 1.0) got := v.Clone() got.DivideScalar(divide) - evaluateVector(fmt.Sprintf("divide %s by %.2f", v, divide), want, got, t) + if !got.Equals(want) { + t.Errorf("divide %s by %.2f: want %s: got %s", v, divide, want, got) + } } -func TestScale(t *testing.T) { +func BenchmarkVector_DivideScalar(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + v.DivideScalar(3) + } +} + +func TestVector_Scale(t *testing.T) { v := NewVector(2, 3) want := NewVector(4, 6) got := v.Clone() got.Scale(2) - evaluateVector(fmt.Sprintf("scale %s by 2", v), want, got, t) + if !got.Equals(want) { + t.Errorf("scale %s by 2: want %s: got %s", v, want, got) + } +} + +func BenchmarkVector_Scale(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + v.Scale(1.01) + } } func TestVector_Abs(t *testing.T) { @@ -561,56 +690,105 @@ func BenchmarkVector_SignedAngle(b *testing.B) { } } -func TestReflect(t *testing.T) { - rightDown := NewVector(1, -1) +func TestVector_Reflect(t *testing.T) { + v := NewVector(1, -1) + c := v.Clone() up := NewVector(0, 1) want := NewVector(1, 1) - got := rightDown.Reflect(up) - evaluateVector(fmt.Sprintf("reflect direction %s off surface with normal %s", rightDown, up), want, *got, t) + v.Reflect(up) + + if !want.Equals(v) { + t.Errorf("reflect direction %s off surface with normal %s: want %s: got %s", c, up, want, v) + } } -func TestReflectSurface(t *testing.T) { - rightDown := NewVector(1, -1) +func BenchmarkVector_Reflect(b *testing.B) { + v := NewVector(1, -1) + o := NewVector(0, 1) + + for n := 0; n < b.N; n++ { + v.Reflect(o) + } +} + +func TestVector_ReflectSurface(t *testing.T) { + v := NewVector(1, -1) + c := v.Clone() up := NewVector(0, 1) want := NewVector(-1, -1) - got := rightDown.ReflectSurface(up) - evaluateVector(fmt.Sprintf("reflect direction %s off surface with normal %s", rightDown, up), want, *got, t) + v.ReflectSurface(up) + + if !want.Equals(v) { + t.Errorf("reflect direction %s off surface with normal %s: want %s: got %s", c, up, want, v) + } } -func TestRotate(t *testing.T) { - up := NewVector(0, 1) - right := NewVector(1, 0) +func BenchmarkVector_ReflectSurface(b *testing.B) { + v := NewVector(1, -1) + o := NewVector(0, 1) + for n := 0; n < b.N; n++ { + v.ReflectSurface(o) + } +} + +func TestVector_Rotate(t *testing.T) { + right := NewVector(1, 0) c := right.Clone() + + up := NewVector(0, 1) angle := -up.SignedAngle(right) want := NewVector(0, 1) got := right.Rotate(angle) - evaluateVectorApprox(fmt.Sprintf("rotated %s by %.1f", c, angle*d2math.RadToDeg), want, *got, t) + if !want.EqualsApprox(*got) { + t.Errorf("rotated %s by %.1f: want %s: got %s", c, angle*d2math.RadToDeg, want, got) + } c = up.Clone() angle -= d2math.RadFull want = NewVector(-1, 0) got = up.Rotate(angle) - evaluateVectorApprox(fmt.Sprintf("rotated %s by %.1f", c, angle*d2math.RadToDeg), want, *got, t) + if !want.EqualsApprox(*got) { + t.Errorf("rotated %s by %.1f: want %s: got %s", c, angle*d2math.RadToDeg, want, got) + } } -func TestNinetyAnti(t *testing.T) { +func BenchmarkVector_Rotate(b *testing.B) { + v := NewVector(1, 0) + angle := 45.0 + + for n := 0; n < b.N; n++ { + v.Rotate(angle) + } +} + +func TestVector_NinetyAnti(t *testing.T) { v := NewVector(0, 1) c := v.Clone() want := NewVector(-1, 0) got := v.NinetyAnti() - evaluateVector(fmt.Sprintf("rotated %s by 90 degrees clockwise", c), want, *got, t) + if !want.Equals(*got) { + t.Errorf("rotated %s by 90 degrees clockwise: want %s: got %s", c, want, *got) + } } -func TestNinetyClock(t *testing.T) { +func BenchmarkVector_NinetyAnti(b *testing.B) { + v := NewVector(1, 0) + + for n := 0; n < b.N; n++ { + v.NinetyAnti() + } +} + +func TestVector_NinetyClock(t *testing.T) { v := NewVector(0, 1) c := v.Clone() @@ -618,47 +796,69 @@ func TestNinetyClock(t *testing.T) { v = c.Clone() got := v.NinetyClock() - evaluateVector(fmt.Sprintf("rotated %s by 90 degrees anti-clockwise", c), want, *got, t) + if !want.Equals(*got) { + t.Errorf("rotated %s by 90 degrees anti-clockwise: want %s: got %s", c, want, *got) + } +} + +func BenchmarkVector_NinetyClock(b *testing.B) { + v := NewVector(1, 0) + + for n := 0; n < b.N; n++ { + v.NinetyClock() + } } func TestVectorUp(t *testing.T) { got := VectorUp() want := NewVector(0, 1) - evaluateVector("create normalized vector with up direction", want, got, t) + if !want.Equals(got) { + t.Errorf("create normalized vector with up direction: want %s: got %s", want, got) + } } func TestVectorDown(t *testing.T) { got := VectorDown() want := NewVector(0, -1) - evaluateVector("create normalized vector with down direction", want, got, t) + if !want.Equals(got) { + t.Errorf("create normalized vector with down direction: want %s: got %s", want, got) + } } func TestVectorRight(t *testing.T) { got := VectorRight() want := NewVector(1, 0) - evaluateVector("create normalized vector with right direction", want, got, t) + if !want.Equals(got) { + t.Errorf("create normalized vector with right direction: want %s: got %s", want, got) + } } func TestVectorLeft(t *testing.T) { got := VectorLeft() want := NewVector(-1, 0) - evaluateVector("create normalized vector with left direction", want, got, t) + if !want.Equals(got) { + t.Errorf("create normalized vector with left direction: want %s: got %s", want, got) + } } func TestVectorOne(t *testing.T) { got := VectorOne() want := NewVector(1, 1) - evaluateVector("create vector with X and Y values of 1", want, got, t) + if !want.Equals(got) { + t.Errorf("create vector with X and Y values of 1: want %s: got %s", want, got) + } } func TestVectorZero(t *testing.T) { got := VectorZero() want := NewVector(0, 0) - evaluateVector("create vector with X and Y values of 0", want, got, t) + if !want.Equals(got) { + t.Errorf("create vector with X and Y values of 0: want %s: got %s", want, got) + } }