Skip to content

Commit

Permalink
#67: Implement ConcatFast that concatenates strings faster than strin…
Browse files Browse the repository at this point in the history
…gs.Builder
  • Loading branch information
arthurkushman committed May 4, 2023
1 parent 1398dff commit f89dc67
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 1 deletion.
21 changes: 21 additions & 0 deletions str.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"strings"
"unsafe"
)

type replaceParams struct {
Expand Down Expand Up @@ -129,3 +130,23 @@ func HTTPBuildQuery(pairs map[string]interface{}) string {

return q.Encode()
}

// ConcatFast concatenates strings faster than strings.Builder (see benchmarks)
func ConcatFast(s ...string) string {
l := len(s)
if l == 0 {
return ""
}

n := 0
for i := 0; i < l; i++ {
n += len(s[i])
}

b := make([]byte, 0, n)
for i := 0; i < l; i++ {
b = append(b, s[i]...)
}

return *(*string)(unsafe.Pointer(&b))
}
90 changes: 89 additions & 1 deletion str_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package pgo_test

import (
"math/rand"
"strings"
"testing"
"time"

"github.com/arthurkushman/pgo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/arthurkushman/pgo"
)

func TestStrReplace(t *testing.T) {
Expand Down Expand Up @@ -74,3 +79,86 @@ func TestHTTPBuildQuery(t *testing.T) {
queryStr2 := pgo.HTTPBuildQuery(map[string]interface{}{})
assert.Empty(t, queryStr2, "built str from an empty map must be empty")
}

func TestConcatFast(t *testing.T) {
tests := []struct {
name string
s []string
result string
}{
{
name: "concat 3 strings",
s: []string{"foo", "bar", "bazzz"},
result: "foobarbazzz",
},
{
name: "concat 0 strings",
s: []string{},
result: "",
},
{
name: "concat random strings",
s: []string{"impEdfCJyek3jn5kj3nkj35nkj35nkj3nkj3n5kjn3kjn35kjn5", "IpDtUOSwMy", "sMIaQYdeON", "TZTwRNgZfx",
"kybtlfzfJa", "UJQJXhknLe", "GKDmxroeFv",
"ifguLESWvm333334241341231242414k12m4k1m24k1m2k4m1k24n1l2n41ln41lk2n4k12"},
result: "impEdfCJyek3jn5kj3nkj35nkj35nkj3nkj3n5kjn3kjn35kjn5IpDtUOSwMysMIaQYdeONTZTwRNgZfxkybtlfzfJaUJQJXhknLeGKDmxroeFvifguLESWvm333334241341231242414k12m4k1m24k1m2k4m1k24n1l2n41ln41lk2n4k12",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
resStr := pgo.ConcatFast(tc.s...)
require.Equal(t, tc.result, resStr)
})
}
}

func BenchmarkConcatFast(b *testing.B) {
s := generateRandomSliceOfStrings()
for i := 0; i < b.N; i++ {
pgo.ConcatFast(s...)
}
}

func BenchmarkConcatFast2(b *testing.B) {
s := generateRandomSliceOfStrings()
for i := 0; i < b.N; i++ {
stringBuilder(s...)
}
}

func generateRandomSliceOfStrings() []string {
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

rand.Seed(time.Now().UnixNano())
s := make([]string, 15)
for i := range s {
bt := make([]byte, 10)
for j := range bt {
bt[j] = letterBytes[rand.Intn(len(letterBytes))]
}
s[i] = string(bt)
}

return s
}

func stringBuilder(s ...string) string {
l := len(s)
if l == 0 {
return ""
}

b := strings.Builder{}
n := 0
for i := 0; i < l; i++ {
n += len(s[i])
}

b.Grow(n)
for i := 0; i < l; i++ {
b.WriteString(s[i])
}

return b.String()
}

0 comments on commit f89dc67

Please sign in to comment.