From f8b92b50c6f3991351f43d1fce92c37a0a7d3893 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 31 Dec 2024 14:07:45 -0600 Subject: [PATCH 1/9] guid: use bytes.Equal for equality testing Instead of looping over each byte and checking, bytes.Equal uses string conversion and equality testing, which should use compiler intrinsics. --- sttp/guid/Guid.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/sttp/guid/Guid.go b/sttp/guid/Guid.go index 9d8a3ad..69154f6 100644 --- a/sttp/guid/Guid.go +++ b/sttp/guid/Guid.go @@ -24,6 +24,7 @@ package guid import ( + "bytes" "errors" "strconv" @@ -53,13 +54,7 @@ func (g Guid) Equal(other Guid) bool { // Equal returns true if the a and b Guid values are equal. func Equal(a, b Guid) bool { - for i := 0; i < 16; i++ { - if a[i] != b[i] { - return false - } - } - - return true + return bytes.Equal(a[:], b[:]) } // Compare returns an integer comparing this Guid (g) to other Guid. The result will be 0 if g==other, -1 if this g < other, and +1 if g > other. From aa2e455af8919007239c3b590b3a21cca214c627 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 9 Jan 2025 03:35:19 -0600 Subject: [PATCH 2/9] guid: optimize Components() call, clean up code --- sttp/guid/Guid.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/sttp/guid/Guid.go b/sttp/guid/Guid.go index 69154f6..163bd45 100644 --- a/sttp/guid/Guid.go +++ b/sttp/guid/Guid.go @@ -25,6 +25,7 @@ package guid import ( "bytes" + "encoding/binary" "errors" "strconv" @@ -98,18 +99,10 @@ func result(left, right uint32) int { // Components gets the Guid value as its constituent components. func (g Guid) Components() (a uint32, b, c uint16, d [8]byte) { - a = (uint32(g[0]) << 24) | (uint32(g[1]) << 16) | (uint32(g[2]) << 8) | uint32(g[3]) - b = uint16((uint32(g[4]) << 8) | uint32(g[5])) - c = uint16((uint32(g[6]) << 8) | uint32(g[7])) - d[0] = g[8] - d[1] = g[9] - d[2] = g[10] - d[3] = g[11] - d[4] = g[12] - d[5] = g[13] - d[6] = g[14] - d[7] = g[15] - + a = binary.BigEndian.Uint32(g[:4]) + b = binary.BigEndian.Uint16(g[4:6]) + c = binary.BigEndian.Uint16(g[6:8]) + copy(d[:], g[8:16]) return } From 0e2c3b3795e6f8cc78d34f1f10e0f81e70bee062 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 9 Jan 2025 03:48:09 -0600 Subject: [PATCH 3/9] guid: minor simplification --- sttp/guid/Guid.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sttp/guid/Guid.go b/sttp/guid/Guid.go index 163bd45..d16a782 100644 --- a/sttp/guid/Guid.go +++ b/sttp/guid/Guid.go @@ -131,9 +131,7 @@ func FromBytes(data []byte, swapEndianness bool) (Guid, error) { swapGuidEndianness(&data) } - var g Guid - copy(g[:], data[:16]) - return g, nil + return Guid(data), nil } // ToBytes creates a byte slice from a Guid. From 6d428dc53db2fe578fc19de1a9d657d3ab893cb1 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 9 Jan 2025 04:50:15 -0600 Subject: [PATCH 4/9] guid: swap Equals() to integer comparisons instead of strings --- sttp/guid/Guid.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sttp/guid/Guid.go b/sttp/guid/Guid.go index d16a782..350a9c7 100644 --- a/sttp/guid/Guid.go +++ b/sttp/guid/Guid.go @@ -24,10 +24,10 @@ package guid import ( - "bytes" "encoding/binary" "errors" "strconv" + "unsafe" "github.com/google/uuid" ) @@ -55,7 +55,11 @@ func (g Guid) Equal(other Guid) bool { // Equal returns true if the a and b Guid values are equal. func Equal(a, b Guid) bool { - return bytes.Equal(a[:], b[:]) + a1 := (*uint64)(unsafe.Pointer(&a[0])) + a2 := (*uint64)(unsafe.Pointer(&a[8])) + b1 := (*uint64)(unsafe.Pointer(&b[0])) + b2 := (*uint64)(unsafe.Pointer(&b[8])) + return *a1 == *b1 && *a2 == *b2 } // Compare returns an integer comparing this Guid (g) to other Guid. The result will be 0 if g==other, -1 if this g < other, and +1 if g > other. From c78f16dc9235fb577402f88193c5b1a25e6f8f1a Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 9 Jan 2025 05:42:15 -0600 Subject: [PATCH 5/9] guid: clean up test --- sttp/guid/Guid_test.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/sttp/guid/Guid_test.go b/sttp/guid/Guid_test.go index 775c061..16c04bf 100644 --- a/sttp/guid/Guid_test.go +++ b/sttp/guid/Guid_test.go @@ -25,7 +25,6 @@ package guid import ( "bytes" - "strconv" "testing" ) @@ -39,7 +38,7 @@ const ( gsz string = "{00000000-0000-0000-0000-000000000000}" ) -//gocyclo: ignore +// gocyclo: ignore func TestGuidParsing(t *testing.T) { var g1, g2, g3, g4 Guid var err error @@ -113,12 +112,12 @@ func TestGuidParsing(t *testing.T) { func TestNewGuidRandomness(t *testing.T) { for i := 0; i < 10000; i++ { if New().Equal(New()) || Equal(New(), New()) { - t.Fatalf("TestNewGuidRandomness: encountered non-unique Guid after " + strconv.Itoa(i*4) + "generations") + t.Fatalf("TestNewGuidRandomness: encountered non-unique Guid after %d generations", i*4) } } } -//gocyclo: ignore +// gocyclo: ignore func TestZeroGuid(t *testing.T) { var gz, zero Guid var err error @@ -166,7 +165,7 @@ func TestZeroGuid(t *testing.T) { } } -//gocyclo: ignore +// gocyclo: ignore func TestGuidCompare(t *testing.T) { var g1, g2, g3, g4, g5, g6 Guid var err error @@ -296,16 +295,16 @@ func testGuidToFromBytes(g Guid, gs string, swapEndianness bool, t *testing.T) { } if !bytes.Equal(gbf, gbs) { - t.Fatalf("TestGuidToFromBytes: ToBytes test compare failed for guid " + gs) + t.Fatal("TestGuidToFromBytes: ToBytes test compare failed for guid " + gs) } g1fb, err := FromBytes(gbf, swapEndianness) if err != nil { - t.Fatalf("TestGuidToFromBytes: FromBytes failed for guid " + gs) + t.Fatal("TestGuidToFromBytes: FromBytes failed for guid " + gs) } if !g1fb.Equal(g) { - t.Fatalf("TestGuidToFromBytes: FromBytes test compare failed for guid " + gs) + t.Fatal("TestGuidToFromBytes: FromBytes test compare failed for guid " + gs) } } From 0104ec7277b1bea3d224e8e71ade50f7b83ddcfb Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 9 Jan 2025 07:42:26 -0600 Subject: [PATCH 6/9] guid: add benchmark for Equals() --- sttp/guid/Guid_test.go | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/sttp/guid/Guid_test.go b/sttp/guid/Guid_test.go index 16c04bf..54271f0 100644 --- a/sttp/guid/Guid_test.go +++ b/sttp/guid/Guid_test.go @@ -308,3 +308,50 @@ func testGuidToFromBytes(g Guid, gs string, swapEndianness bool, t *testing.T) { t.Fatal("TestGuidToFromBytes: FromBytes test compare failed for guid " + gs) } } + +func BenchmarkEqualityBaseline(b *testing.B) { + list := []string{gs1,gs2,gs3,gs4,gs5,gs6,gsz} + glist := [7]Guid{} + for i := range list { + glist[i], _ = Parse(list[i]) + } + b.ResetTimer() + for range b.N { + for i := range list { + for j := range list { + if i == j { + continue + } + equal := true + a, b := glist[i], glist[j] + for k := range 16 { + if a[k] != b[k] { + equal = false + break + } + } + _ = equal + } + } + } +} + +func BenchmarkEqualityCurrent(b *testing.B) { + list := []string{gs1,gs2,gs3,gs4,gs5,gs6,gsz} + glist := [7]Guid{} + for i := range list { + glist[i], _ = Parse(list[i]) + } + b.ResetTimer() + for range b.N { + for i := range list { + for j := range list { + if i == j { + continue + } + _ = glist[i].Equal(glist[j]) + } + } + } +} + From ee3aef3a63e88441a40620bd8bce277e57e9f2e6 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 9 Jan 2025 08:40:32 -0600 Subject: [PATCH 7/9] update benchmark, further improve performance --- sttp/guid/Guid.go | 6 +++++- sttp/guid/Guid_test.go | 41 ++++++++++++++++------------------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/sttp/guid/Guid.go b/sttp/guid/Guid.go index 350a9c7..ce718dc 100644 --- a/sttp/guid/Guid.go +++ b/sttp/guid/Guid.go @@ -50,7 +50,11 @@ func (g Guid) IsZero() bool { // Equal returns true if this Guid and other Guid values are equal. func (g Guid) Equal(other Guid) bool { - return Equal(g, other) + a1 := (*uint64)(unsafe.Pointer(&g[0])) + a2 := (*uint64)(unsafe.Pointer(&g[8])) + b1 := (*uint64)(unsafe.Pointer(&other[0])) + b2 := (*uint64)(unsafe.Pointer(&other[8])) + return *a1 == *b1 && *a2 == *b2 } // Equal returns true if the a and b Guid values are equal. diff --git a/sttp/guid/Guid_test.go b/sttp/guid/Guid_test.go index 54271f0..d13c368 100644 --- a/sttp/guid/Guid_test.go +++ b/sttp/guid/Guid_test.go @@ -26,6 +26,7 @@ package guid import ( "bytes" "testing" + "unsafe" ) const ( @@ -310,48 +311,38 @@ func testGuidToFromBytes(g Guid, gs string, swapEndianness bool, t *testing.T) { } func BenchmarkEqualityBaseline(b *testing.B) { - list := []string{gs1,gs2,gs3,gs4,gs5,gs6,gsz} + list := []string{gs1, gs2, gs3, gs4, gs5, gs6, gsz} glist := [7]Guid{} for i := range list { glist[i], _ = Parse(list[i]) } b.ResetTimer() for range b.N { - for i := range list { - for j := range list { - if i == j { - continue - } - equal := true - a, b := glist[i], glist[j] - for k := range 16 { - if a[k] != b[k] { - equal = false - break - } - } - _ = equal + equal := true + a, b := glist[0], glist[1] + for k := range 16 { + if a[k] != b[k] { + equal = false + break } } + _ = equal } } func BenchmarkEqualityCurrent(b *testing.B) { - list := []string{gs1,gs2,gs3,gs4,gs5,gs6,gsz} + list := []string{gs1, gs2, gs3, gs4, gs5, gs6, gsz} glist := [7]Guid{} for i := range list { glist[i], _ = Parse(list[i]) } b.ResetTimer() for range b.N { - for i := range list { - for j := range list { - if i == j { - continue - } - _ = glist[i].Equal(glist[j]) - } - } + a1 := (*uint64)(unsafe.Pointer(&glist[0][0])) + a2 := (*uint64)(unsafe.Pointer(&glist[0][8])) + b1 := (*uint64)(unsafe.Pointer(&glist[1][0])) + b2 := (*uint64)(unsafe.Pointer(&glist[1][8])) + equal := *a1 == *b1 && *a2 == *b2 + _ = equal } } - From 1cc843eeaa9825fad7abed8f2adf616ca4e5a778 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 9 Jan 2025 16:47:51 -0600 Subject: [PATCH 8/9] guid: add benchmark of direct == (Thanks Jorge!) --- sttp/guid/Guid_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sttp/guid/Guid_test.go b/sttp/guid/Guid_test.go index d13c368..5915598 100644 --- a/sttp/guid/Guid_test.go +++ b/sttp/guid/Guid_test.go @@ -346,3 +346,16 @@ func BenchmarkEqualityCurrent(b *testing.B) { _ = equal } } + +func BenchmarkEqualityDirect(b *testing.B) { + list := []string{gs1, gs2, gs3, gs4, gs5, gs6, gsz} + glist := [7]Guid{} + for i := range list { + glist[i], _ = Parse(list[i]) + } + b.ResetTimer() + for range b.N { + equal := glist[0] == glist[1] + _ = equal + } +} From 8fa6ed86b645e2bb0cd20c6e08ef8848907347cb Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 9 Jan 2025 16:58:07 -0600 Subject: [PATCH 9/9] guid: kill indirection, use == operator for Equal() This just... works. And is slightly faster than unsafe conversion, too. --- sttp/guid/Guid.go | 15 +++----------- sttp/guid/Guid_test.go | 47 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/sttp/guid/Guid.go b/sttp/guid/Guid.go index ce718dc..04138f0 100644 --- a/sttp/guid/Guid.go +++ b/sttp/guid/Guid.go @@ -27,7 +27,6 @@ import ( "encoding/binary" "errors" "strconv" - "unsafe" "github.com/google/uuid" ) @@ -45,25 +44,17 @@ func New() Guid { // IsZero determines if the Guid value is its zero value, i.e., empty. func (g Guid) IsZero() bool { - return Equal(g, Empty) + return g == Empty } // Equal returns true if this Guid and other Guid values are equal. func (g Guid) Equal(other Guid) bool { - a1 := (*uint64)(unsafe.Pointer(&g[0])) - a2 := (*uint64)(unsafe.Pointer(&g[8])) - b1 := (*uint64)(unsafe.Pointer(&other[0])) - b2 := (*uint64)(unsafe.Pointer(&other[8])) - return *a1 == *b1 && *a2 == *b2 + return g == other } // Equal returns true if the a and b Guid values are equal. func Equal(a, b Guid) bool { - a1 := (*uint64)(unsafe.Pointer(&a[0])) - a2 := (*uint64)(unsafe.Pointer(&a[8])) - b1 := (*uint64)(unsafe.Pointer(&b[0])) - b2 := (*uint64)(unsafe.Pointer(&b[8])) - return *a1 == *b1 && *a2 == *b2 + return a == b } // Compare returns an integer comparing this Guid (g) to other Guid. The result will be 0 if g==other, -1 if this g < other, and +1 if g > other. diff --git a/sttp/guid/Guid_test.go b/sttp/guid/Guid_test.go index 5915598..71bbea4 100644 --- a/sttp/guid/Guid_test.go +++ b/sttp/guid/Guid_test.go @@ -310,6 +310,53 @@ func testGuidToFromBytes(g Guid, gs string, swapEndianness bool, t *testing.T) { } } +func (g Guid) EqualIndirectBaseline(other Guid) bool { + return EqualBaseline(g, other) +} + +func (g Guid) EqualIndirect(other Guid) bool { + return Equal(g, other) +} + +func EqualBaseline(a, b Guid) bool { + for k := range 16 { + if a[k] != b[k] { + return false + break + } + } + return true + +} + +func BenchmarkEqualityIndirect(b *testing.B) { + list := []string{gs1, gs2, gs3, gs4, gs5, gs6, gsz} + glist := [7]Guid{} + for i := range list { + glist[i], _ = Parse(list[i]) + } + b.ResetTimer() + for range b.N { + a, b := glist[0], glist[1] + equal := a.EqualIndirect(b) + _ = equal + } +} + +func BenchmarkEqualityIndirectBaseline(b *testing.B) { + list := []string{gs1, gs2, gs3, gs4, gs5, gs6, gsz} + glist := [7]Guid{} + for i := range list { + glist[i], _ = Parse(list[i]) + } + b.ResetTimer() + for range b.N { + a, b := glist[0], glist[1] + equal := a.EqualIndirectBaseline(b) + _ = equal + } +} + func BenchmarkEqualityBaseline(b *testing.B) { list := []string{gs1, gs2, gs3, gs4, gs5, gs6, gsz} glist := [7]Guid{}