Skip to content

Commit 8efb763

Browse files
committed
feat: improve perf for convert,add CopyUnsafe and change Equal function,make Swap extern
1 parent 881ab79 commit 8efb763

File tree

2 files changed

+166
-15
lines changed

2 files changed

+166
-15
lines changed

bench_test.go

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"testing"
88
)
99

10+
const x64 = 64
11+
1012
func ConvertSliceManual(from []int64) []int32 {
1113
to := make([]int32, len(from))
1214
for i, v := range from {
@@ -15,6 +17,91 @@ func ConvertSliceManual(from []int64) []int32 {
1517
return to
1618
}
1719

20+
func TestCopyUnsafe(t *testing.T) {
21+
// Test 1: Standard case
22+
source := []byte("Hello, World!")
23+
destination := make([]byte, len(source))
24+
n := CopyUnsafe(destination, source)
25+
if n != len(source) {
26+
t.Errorf("Expected %d bytes copied, got %d", len(source), n)
27+
}
28+
if string(destination) != string(source) {
29+
t.Errorf("Expected destination %q, got %q", string(source), string(destination))
30+
}
31+
32+
// Test 2: Different lengths (source longer than destination)
33+
source = []byte("Hello, Go!")
34+
destination = make([]byte, len(source)-2) // Smaller destination
35+
defer func() {
36+
if r := recover(); r == nil {
37+
t.Errorf("Expected panic for size mismatch, but none occurred")
38+
}
39+
}()
40+
_ = CopyUnsafe(destination, source)
41+
42+
// Test 3: Empty slices
43+
source = []byte{}
44+
destination = []byte{}
45+
n = CopyUnsafe(destination, source)
46+
if n != 0 {
47+
t.Errorf("Expected 0 bytes copied, got %d", n)
48+
}
49+
}
50+
51+
func TestStrings(t *testing.T) {
52+
// Test 1: Standard case
53+
source := []byte("Hello, World!")
54+
55+
n := String(source)
56+
if n != string(source) {
57+
t.Errorf("Expected %s string copied, got %s", string(source), n)
58+
}
59+
60+
source1 := "Hello,world!"
61+
62+
n1 := StringToBytes(source1)
63+
if string(n1) != source1 {
64+
t.Errorf("Expected %s string copied, got %s", source, n1)
65+
}
66+
67+
}
68+
69+
func BenchmarkCopyUnsafe(b *testing.B) {
70+
source := []byte("Benchmarking Unsafe Copy!")
71+
destination := make([]byte, len(source))
72+
b.ResetTimer()
73+
for i := 0; i < b.N; i++ {
74+
CopyUnsafe(destination, source)
75+
}
76+
}
77+
78+
func BenchmarkCopyStandart(b *testing.B) {
79+
source := []byte("Benchmarking Standart Copy!")
80+
destination := make([]byte, len(source))
81+
b.ResetTimer()
82+
for i := 0; i < b.N; i++ {
83+
copy(destination, source)
84+
}
85+
}
86+
87+
func BenchmarkCopy_currentBytes(b *testing.B) {
88+
source := []byte("Benchmarking Current Copy!")
89+
destination := make([]byte, len(source))
90+
b.ResetTimer()
91+
for i := 0; i < b.N; i++ {
92+
copy(destination[:26], source)
93+
}
94+
}
95+
96+
func BenchmarkCopy_currentBytes_UNSAFE(b *testing.B) {
97+
source := []byte("Benchmarking Current Copy!")
98+
destination := make([]byte, len(source))
99+
b.ResetTimer()
100+
for i := 0; i < b.N; i++ {
101+
CopyUnsafe(destination[:26], source)
102+
}
103+
}
104+
18105
func generateTestStrings(count, minLength, maxLength int) []string {
19106
var data []string
20107
for i := 0; i < count; i++ {
@@ -58,13 +145,27 @@ func BenchmarkStringsBuilder(b *testing.B) {
58145

59146
func BenchmarkString(b *testing.B) {
60147
data := []byte("This is a benchmark test for String conversion.")
148+
length := len(data)
61149

62150
b.Run("Custom String", func(b *testing.B) {
63151
for i := 0; i < b.N; i++ {
64152
_ = String(data)
65153
}
66154
})
67155

156+
b.Run("Custom string 2", func(b *testing.B) {
157+
for i := 0; i < b.N; i++ {
158+
_ = string2(data, length)
159+
}
160+
161+
})
162+
163+
b.Run("Custom string 3", func(b *testing.B) {
164+
for i := 0; i < b.N; i++ {
165+
_ = string3(data)
166+
}
167+
168+
})
68169
b.Run("Standard String", func(b *testing.B) {
69170
for i := 0; i < b.N; i++ {
70171
_ = string(data)
@@ -211,15 +312,38 @@ func BenchmarkStringToBytes(b *testing.B) {
211312
})
212313
}
213314

315+
func TestEquals(t *testing.T) {
316+
t.Run("TestEqualsTrue", func(t *testing.T) {
317+
a := []byte("This is a benchmark test for Equal.....")
318+
bb := []byte("This is a benchmark test for Equal.....")
319+
lengthA := uintptr(len(a))
320+
boole := Equal(a, bb, lengthA)
321+
if boole == false {
322+
t.Log("IsEqual: ", Equal(a, bb, lengthA))
323+
}
324+
})
325+
t.Run("TestEqualsFalse", func(t *testing.T) {
326+
a := []byte("This is a benchmark test for Equal.....")
327+
bb := []byte("This is a benchmark test for Equal.....1")
328+
lengthA := uintptr(len(a))
329+
boole := Equal(a, bb, lengthA)
330+
if boole == true {
331+
t.Log("IsEqual: ", Equal(a, bb, lengthA))
332+
}
333+
334+
})
335+
}
336+
214337
func BenchmarkEqualTrue(b *testing.B) { // true
215338
a := []byte("This is a benchmark test for Equal.....")
216339
bb := []byte("This is a benchmark test for Equal.....")
340+
lengthA := uintptr(len(a))
217341

218342
b.Run("Custom Equal", func(b *testing.B) {
219343
for i := 0; i < b.N; i++ {
220-
boole := Equal(a, bb)
344+
boole := Equal(a, bb, lengthA)
221345
if boole == false {
222-
b.Log("IsEqual: ", Equal(a, bb))
346+
b.Log("IsEqual: ", Equal(a, bb, lengthA))
223347
}
224348
}
225349
})
@@ -233,6 +357,15 @@ func BenchmarkEqualTrue(b *testing.B) { // true
233357

234358
}
235359
})
360+
361+
b.Run("TEST GENERIC EQUAL", func(b *testing.B) {
362+
for i := 0; i < b.N; i++ {
363+
boole := String(a) == String(bb)
364+
if boole == false {
365+
b.Fatal("GenericEqual: ", Equal(a, bb, lengthA))
366+
}
367+
}
368+
})
236369
}
237370

238371
func BenchmarkConvertSlice(b *testing.B) {

utils.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ import (
1010
"github.com/NikoMalik/low-level-functions/constants"
1111
)
1212

13+
//go:linkname memmove runtime.memmove
14+
func memmove(dst, src unsafe.Pointer, n uintptr)
15+
16+
// constants in make for standart copy is more faster,but if no constant we can use CopyUnsafe()
17+
// make([]byte, len(src)) not constant make([]byte, 0) constant
18+
//
19+
//go:nocheckptr
20+
func CopyUnsafe(dst []byte, src []byte) int {
21+
memmove(unsafe.Pointer(&dst[0]), unsafe.Pointer(&src[0]), uintptr(len(src)))
22+
return len(src)
23+
}
24+
1325
//go:nosplit
1426
//go:nocheckptr
1527
func Noescape(up unsafe.Pointer) unsafe.Pointer {
@@ -32,16 +44,24 @@ func (err *ErrorSizeUnmatch) Error() string {
3244
}
3345

3446
func String(b []byte) string {
47+
return *(*string)(unsafe.Pointer(&b))
48+
}
49+
50+
func string2(b []byte, length int) string {
51+
return *(*string)(unsafe.Pointer(&struct {
52+
*byte
53+
int
54+
}{(*byte)(unsafe.Pointer(&b[0])), length}))
55+
}
56+
57+
// too slow
58+
func string3(b []byte) string {
3559

3660
return unsafe.String(unsafe.SliceData(b), len(b))
3761
}
3862

3963
func StringToBytes(s string) []byte {
40-
return *(*[]byte)(unsafe.Pointer(&struct {
41-
string
42-
Cap int
43-
}{s, len(s)},
44-
))
64+
return *(*[]byte)(unsafe.Pointer(&s))
4565
}
4666

4767
func CopyString(s string) string {
@@ -60,7 +80,7 @@ func ConvertSlice[TFrom, TTo any](from []TFrom) ([]TTo, error) {
6080
minSize := unsafe.Sizeof(zeroValTo)
6181

6282
if minSize > maxSize {
63-
swap(&minSize, &maxSize)
83+
Swap(&minSize, &maxSize)
6484
}
6585

6686
if unsafe.Sizeof(zeroValFrom) == minSize {
@@ -97,7 +117,7 @@ func ConvertSlice[TFrom, TTo any](from []TFrom) ([]TTo, error) {
97117
}
98118

99119
//go:noinline
100-
func swap[T any](a, b *T) {
120+
func Swap[T any](a, b *T) {
101121
tmp := *a
102122
*a = *b
103123
*b = tmp
@@ -235,11 +255,8 @@ func MakeNoZeroCapString(l int, c int) []string {
235255
//go:linkname memequal runtime.memequal
236256
func memequal(a, b unsafe.Pointer, size uintptr) bool
237257

238-
func Equal(a, b []byte) bool {
239-
if len(a) != len(b) {
240-
return false
241-
}
242-
return memequal(unsafe.Pointer(&a[0]), unsafe.Pointer(&b[0]), uintptr(len(a)))
258+
func Equal(a, b []byte, length uintptr) bool {
259+
return memequal(unsafe.Pointer(&a[0]), unsafe.Pointer(&b[0]), length)
243260

244261
}
245262

@@ -269,7 +286,7 @@ func IsNil(v any) bool {
269286
//
270287
// Returns:
271288
// - bool: True if the variables point to the same memory location, false otherwise.
272-
func IsEqual(v1, v2 any) bool {
289+
func IsEqual[T any](v1, v2 T) bool {
273290
// Get the memory address of the variables using unsafe.Pointer.
274291
// The & operator returns the memory address of a variable.
275292
// The unsafe.Pointer type is used to store and manipulate untyped memory.
@@ -315,6 +332,7 @@ func GetItem[T any](slice []T, idx int) T { // experimental same performance as
315332
return *(*T)(ptr)
316333
}
317334

335+
//go:nocheckptr
318336
func GetItemWithoutCheck[T any](slice []T, idx int) T { // clears the checks for idx and make it faster but not safe
319337

320338
ptr := unsafe.Pointer(uintptr(unsafe.Pointer(&slice[0])) + uintptr(idx)*unsafe.Sizeof(slice[0]))

0 commit comments

Comments
 (0)