Skip to content

Commit ab9387c

Browse files
authored
feat: reduce memory allocation for common slice and map types (#30)
This commit reduces memory allocations for some collection types by avoiding the slow and expensive JSON marshal->unmarshal path. It extends the list of type assertions to slices and maps of some primitive types. **Additional context** While memory profiling a production application we (@dyolcekaj) noticed that 50+% of our memory allocations were happening during `ldvalue.CopyArbitraryValue`. We use feature flags heavily, and for any HTTP request it is typical to evaluate 2-8 feature flags. We found that the reason for this high memory usage was due to a string slice with several hundred items being passed through `ldvalue.CopyArbitraryValue` on most `ldcontext.Builder` instantiations. This was occurring in a general use function like ``` func IsEnabled(ctx context.Context, flag string, attributes map[string]any) bool { client := ctx.Value("ld-client).(*ldclient.LDClient) builder := ldcontextBuilderFromCtx(ctx) // get some HTTP request type info, user info, etc. // note ldvalue.CopyArbitraryValueMap has the same issue for key, value := range attributes { builder.SetValue(key, ldvalue.CopyArbitraryValue(value)) } // ignore errors for ex. enabled, _ := client.BoolVariation(flag, builder.Build(), false) return enabled } ``` If any of the `map[string]any` values are themselves a `[]string` or similar we hit the `FromJSONMarshal` slow path. To partially solve the problem we have written a wrapper around this SDK with more or less the same code as I am submitting here. The reduction in memory allocation is especially noticeable when copying `[]string`, but in all cases there is a fairly significant improvement in latency and total memory usage. Although this is verbose, alternatives that use generic functions like ``` func copyArbitraryType[T comparable](data []T) Value { ... } ``` don't save on allocations, and using reflection to determine the slice or map element types does have some benefit especially for slices but still has a high number of allocations for large maps. You can see an implementation of this change using reflection [here](https://github.com/dyolcekaj/launchdarkly-go-sdk-common/tree/arbitrary-collection-copies) Here are benchmarks from my machine showing the improvement for these specific use cases. Command for all is `go test -benchmem '-run=^$$' -bench="CollectionCopy*" ./ldvalue` Before: ``` BenchmarkCollectionCopyMapStringSmall-16 197137 5597 ns/op 4730 B/op 47 allocs/op BenchmarkCollectionCopyMapStringLarge-16 1776 632766 ns/op 676664 B/op 4043 allocs/op BenchmarkCollectionCopySliceStringSmall-16 415045 2731 ns/op 3931 B/op 19 allocs/op BenchmarkCollectionCopySliceStringMedium-16 57158 20780 ns/op 35357 B/op 112 allocs/op BenchmarkCollectionCopySliceStringLarge-16 6002 186840 ns/op 267965 B/op 1016 allocs/op BenchmarkCollectionCopySliceIntSmall-16 428552 2633 ns/op 3835 B/op 9 allocs/op BenchmarkCollectionCopySliceIntMedium-16 58148 20332 ns/op 34397 B/op 12 allocs/op BenchmarkCollectionCopySliceIntLarge-16 6182 180290 ns/op 260010 B/op 15 allocs/op ``` After: ``` BenchmarkCollectionCopyMapStringSmall-16 1568584 769.4 ns/op 2117 B/op 2 allocs/op BenchmarkCollectionCopyMapStringLarge-16 15642 76328 ns/op 254033 B/op 3 allocs/op BenchmarkCollectionCopySliceStringSmall-16 5143736 226.0 ns/op 1048 B/op 2 allocs/op BenchmarkCollectionCopySliceStringMedium-16 825129 1439 ns/op 9752 B/op 2 allocs/op BenchmarkCollectionCopySliceStringLarge-16 78724 15250 ns/op 98328 B/op 2 allocs/op BenchmarkCollectionCopySliceIntSmall-16 5327779 224.1 ns/op 1048 B/op 2 allocs/op BenchmarkCollectionCopySliceIntMedium-16 832033 1419 ns/op 9752 B/op 2 allocs/op BenchmarkCollectionCopySliceIntLarge-16 86389 13898 ns/op 98328 B/op 2 allocs/op ``` Reflection based benchmark ``` BenchmarkReflectCopyMapStringSmall-16 516852 2229 ns/op 2678 B/op 23 allocs/op BenchmarkReflectCopyMapStringLarge-16 5341 197526 ns/op 310622 B/op 2004 allocs/op BenchmarkReflectCopySliceStringSmall-16 2635124 447.3 ns/op 1048 B/op 2 allocs/op BenchmarkReflectCopySliceStringMedium-16 346424 3233 ns/op 9752 B/op 2 allocs/op BenchmarkReflectCopySliceStringLarge-16 38649 31042 ns/op 98329 B/op 2 allocs/op BenchmarkReflectCopySliceIntSmall-16 2634298 448.7 ns/op 1048 B/op 2 allocs/op BenchmarkReflectCopySliceIntMedium-16 340862 3293 ns/op 9752 B/op 2 allocs/op BenchmarkReflectCopySliceIntLarge-16 39921 30468 ns/op 98328 B/op 2 allocs/op ``` Benchstat ``` goos: darwin goarch: amd64 pkg: github.com/launchdarkly/go-sdk-common/v3/ldvalue cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz │ original.txt │ updated.txt │ │ sec/op │ sec/op vs base │ CollectionCopyMapStringSmall-16 5.534µ ± ∞ ¹ 2.103µ ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopyMapStringLarge-16 680.4µ ± ∞ ¹ 193.5µ ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceStringSmall-16 2818.0n ± ∞ ¹ 431.1n ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceStringMedium-16 20.874µ ± ∞ ¹ 3.299µ ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceStringLarge-16 188.00µ ± ∞ ¹ 30.97µ ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceIntSmall-16 2672.0n ± ∞ ¹ 441.5n ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceIntMedium-16 20.542µ ± ∞ ¹ 3.196µ ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceIntLarge-16 181.99µ ± ∞ ¹ 30.73µ ± ∞ ¹ ~ (p=1.000 n=1) ² geomean 28.34µ 5.449µ -80.77% ¹ need >= 6 samples for confidence interval at level 0.95 ² need >= 4 samples to detect a difference at alpha level 0.05 │ original.txt │ updated.txt │ │ B/op │ B/op vs base │ CollectionCopyMapStringSmall-16 4.618Ki ± ∞ ¹ 2.615Ki ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopyMapStringLarge-16 661.2Ki ± ∞ ¹ 303.3Ki ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceStringSmall-16 3.839Ki ± ∞ ¹ 1.023Ki ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceStringMedium-16 34.526Ki ± ∞ ¹ 9.523Ki ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceStringLarge-16 262.07Ki ± ∞ ¹ 96.02Ki ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceIntSmall-16 3.745Ki ± ∞ ¹ 1.023Ki ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceIntMedium-16 33.590Ki ± ∞ ¹ 9.523Ki ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceIntLarge-16 253.86Ki ± ∞ ¹ 96.02Ki ± ∞ ¹ ~ (p=1.000 n=1) ² geomean 36.83Ki 12.74Ki -65.41% ¹ need >= 6 samples for confidence interval at level 0.95 ² need >= 4 samples to detect a difference at alpha level 0.05 │ original.txt │ updated.txt │ │ allocs/op │ allocs/op vs base │ CollectionCopyMapStringSmall-16 47.00 ± ∞ ¹ 23.00 ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopyMapStringLarge-16 4.043k ± ∞ ¹ 2.004k ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceStringSmall-16 19.000 ± ∞ ¹ 2.000 ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceStringMedium-16 112.000 ± ∞ ¹ 2.000 ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceStringLarge-16 1016.000 ± ∞ ¹ 2.000 ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceIntSmall-16 9.000 ± ∞ ¹ 2.000 ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceIntMedium-16 12.000 ± ∞ ¹ 2.000 ± ∞ ¹ ~ (p=1.000 n=1) ² CollectionCopySliceIntLarge-16 15.000 ± ∞ ¹ 2.000 ± ∞ ¹ ~ (p=1.000 n=1) ² geomean 71.27 6.438 -90.97% ¹ need >= 6 samples for confidence interval at level 0.95 ² need >= 4 samples to detect a difference at alpha level 0.05 ```
1 parent 6279f2c commit ab9387c

File tree

6 files changed

+581
-0
lines changed

6 files changed

+581
-0
lines changed

ldvalue/value_array.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,129 @@ func (a ValueArray) Transform(fn func(index int, value Value) (Value, bool)) Val
248248
func (a ValueArray) String() string {
249249
return a.JSONString()
250250
}
251+
252+
func copyArbitraryArrayString(data []string) Value {
253+
a := make([]Value, len(data))
254+
for i, v := range data {
255+
a[i] = String(v)
256+
}
257+
258+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
259+
}
260+
261+
func copyArbitraryArrayBool(data []bool) Value {
262+
a := make([]Value, len(data))
263+
for i, v := range data {
264+
a[i] = Bool(v)
265+
}
266+
267+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
268+
}
269+
270+
func copyArbitraryArrayInt(data []int) Value {
271+
a := make([]Value, len(data))
272+
for i, v := range data {
273+
a[i] = Float64(float64(v))
274+
}
275+
276+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
277+
}
278+
279+
func copyArbitraryArrayInt8(data []int8) Value {
280+
a := make([]Value, len(data))
281+
for i, v := range data {
282+
a[i] = Float64(float64(v))
283+
}
284+
285+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
286+
}
287+
288+
func copyArbitraryArrayInt16(data []int16) Value {
289+
a := make([]Value, len(data))
290+
for i, v := range data {
291+
a[i] = Float64(float64(v))
292+
}
293+
294+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
295+
}
296+
297+
func copyArbitraryArrayInt32(data []int32) Value {
298+
a := make([]Value, len(data))
299+
for i, v := range data {
300+
a[i] = Float64(float64(v))
301+
}
302+
303+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
304+
}
305+
306+
func copyArbitraryArrayInt64(data []int64) Value {
307+
a := make([]Value, len(data))
308+
for i, v := range data {
309+
a[i] = Float64(float64(v))
310+
}
311+
312+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
313+
}
314+
315+
func copyArbitraryArrayUint(data []uint) Value {
316+
a := make([]Value, len(data))
317+
for i, v := range data {
318+
a[i] = Float64(float64(v))
319+
}
320+
321+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
322+
}
323+
324+
func copyArbitraryArrayUint8(data []uint8) Value {
325+
a := make([]Value, len(data))
326+
for i, v := range data {
327+
a[i] = Float64(float64(v))
328+
}
329+
330+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
331+
}
332+
333+
func copyArbitraryArrayUint16(data []uint16) Value {
334+
a := make([]Value, len(data))
335+
for i, v := range data {
336+
a[i] = Float64(float64(v))
337+
}
338+
339+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
340+
}
341+
342+
func copyArbitraryArrayUint32(data []uint32) Value {
343+
a := make([]Value, len(data))
344+
for i, v := range data {
345+
a[i] = Float64(float64(v))
346+
}
347+
348+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
349+
}
350+
351+
func copyArbitraryArrayUint64(data []uint64) Value {
352+
a := make([]Value, len(data))
353+
for i, v := range data {
354+
a[i] = Float64(float64(v))
355+
}
356+
357+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
358+
}
359+
360+
func copyArbitraryArrayFloat32(data []float32) Value {
361+
a := make([]Value, len(data))
362+
for i, v := range data {
363+
a[i] = Float64(float64(v))
364+
}
365+
366+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
367+
}
368+
369+
func copyArbitraryArrayFloat64(data []float64) Value {
370+
a := make([]Value, len(data))
371+
for i, v := range data {
372+
a[i] = Float64(v)
373+
}
374+
375+
return Value{valueType: ArrayType, arrayValue: ValueArray{data: a}}
376+
}

ldvalue/value_array_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,87 @@ func TestValueArrayTransform(t *testing.T) {
255255
assert.Equal(t, ValueArrayOf(), ValueArrayOf().Transform(shouldNotCallThis))
256256
}
257257

258+
func TestCopyArbitrarySliceOfType(t *testing.T) {
259+
var sNil []string
260+
vm := CopyArbitraryValue(sNil)
261+
assert.Equal(t, ArrayType, vm.Type())
262+
263+
sEmpty := []string{}
264+
vm = CopyArbitraryValue(sEmpty)
265+
assert.Equal(t, ArrayType, vm.Type())
266+
assert.Equal(t, []Value{}, vm.arrayValue.data)
267+
268+
sStr := []string{"a", "b", "c"}
269+
vm = CopyArbitraryValue(sStr)
270+
assert.Equal(t, ArrayType, vm.Type())
271+
assert.Equal(t, []Value{String("a"), String("b"), String("c")}, vm.arrayValue.data)
272+
273+
sBool := []bool{true, false}
274+
vm = CopyArbitraryValue(sBool)
275+
assert.Equal(t, ArrayType, vm.Type())
276+
assert.Equal(t, []Value{Bool(true), Bool(false)}, vm.arrayValue.data)
277+
278+
sInt := []int{1, 2, 3}
279+
vm = CopyArbitraryValue(sInt)
280+
assert.Equal(t, ArrayType, vm.Type())
281+
assert.Equal(t, []Value{Int(1), Int(2), Int(3)}, vm.arrayValue.data)
282+
283+
sInt8 := []int8{1, 2, 3}
284+
vm = CopyArbitraryValue(sInt8)
285+
assert.Equal(t, ArrayType, vm.Type())
286+
assert.Equal(t, []Value{Int(1), Int(2), Int(3)}, vm.arrayValue.data)
287+
288+
sInt16 := []int16{1, 2, 3}
289+
vm = CopyArbitraryValue(sInt16)
290+
assert.Equal(t, ArrayType, vm.Type())
291+
assert.Equal(t, []Value{Int(1), Int(2), Int(3)}, vm.arrayValue.data)
292+
293+
sInt32 := []int32{1, 2, 3}
294+
vm = CopyArbitraryValue(sInt32)
295+
assert.Equal(t, ArrayType, vm.Type())
296+
assert.Equal(t, []Value{Int(1), Int(2), Int(3)}, vm.arrayValue.data)
297+
298+
sInt64 := []int64{1, 2, 3}
299+
vm = CopyArbitraryValue(sInt64)
300+
assert.Equal(t, ArrayType, vm.Type())
301+
assert.Equal(t, []Value{Int(1), Int(2), Int(3)}, vm.arrayValue.data)
302+
303+
sUint := []uint{1, 2, 3}
304+
vm = CopyArbitraryValue(sUint)
305+
assert.Equal(t, ArrayType, vm.Type())
306+
assert.Equal(t, []Value{Int(1), Int(2), Int(3)}, vm.arrayValue.data)
307+
308+
sUint8 := []uint8{1, 2, 3}
309+
vm = CopyArbitraryValue(sUint8)
310+
assert.Equal(t, ArrayType, vm.Type())
311+
assert.Equal(t, []Value{Int(1), Int(2), Int(3)}, vm.arrayValue.data)
312+
313+
sUint16 := []uint16{1, 2, 3}
314+
vm = CopyArbitraryValue(sUint16)
315+
assert.Equal(t, ArrayType, vm.Type())
316+
assert.Equal(t, []Value{Int(1), Int(2), Int(3)}, vm.arrayValue.data)
317+
318+
sUint32 := []uint32{1, 2, 3}
319+
vm = CopyArbitraryValue(sUint32)
320+
assert.Equal(t, ArrayType, vm.Type())
321+
assert.Equal(t, []Value{Int(1), Int(2), Int(3)}, vm.arrayValue.data)
322+
323+
sUint64 := []uint64{1, 2, 3}
324+
vm = CopyArbitraryValue(sUint64)
325+
assert.Equal(t, ArrayType, vm.Type())
326+
assert.Equal(t, []Value{Int(1), Int(2), Int(3)}, vm.arrayValue.data)
327+
328+
sFloat32 := []float32{1.0, 2.0, 3.0}
329+
vm = CopyArbitraryValue(sFloat32)
330+
assert.Equal(t, ArrayType, vm.Type())
331+
assert.Equal(t, []Value{Float64(1.0), Float64(2.0), Float64(3.0)}, vm.arrayValue.data)
332+
333+
sFloat64 := []float64{1.1, 2.2, 3.3}
334+
vm = CopyArbitraryValue(sFloat64)
335+
assert.Equal(t, ArrayType, vm.Type())
336+
assert.Equal(t, []Value{Float64(1.1), Float64(2.2), Float64(3.3)}, vm.arrayValue.data)
337+
}
338+
258339
func shouldBeSameSlice(t *testing.T, s0 []Value, s1 []Value) {
259340
old := s0[0]
260341
s0[0] = String("temp-value")

ldvalue/value_base.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,34 @@ func CopyArbitraryValue(anyValue any) Value { //nolint:gocyclo // yes, we know i
296296
return Null()
297297
}
298298
return ArrayOf((*o)...)
299+
case []string:
300+
return copyArbitraryArrayString(o)
301+
case []bool:
302+
return copyArbitraryArrayBool(o)
303+
case []int:
304+
return copyArbitraryArrayInt(o)
305+
case []int8:
306+
return copyArbitraryArrayInt8(o)
307+
case []int16:
308+
return copyArbitraryArrayInt16(o)
309+
case []int32:
310+
return copyArbitraryArrayInt32(o)
311+
case []int64:
312+
return copyArbitraryArrayInt64(o)
313+
case []uint:
314+
return copyArbitraryArrayUint(o)
315+
case []uint8:
316+
return copyArbitraryArrayUint8(o)
317+
case []uint16:
318+
return copyArbitraryArrayUint16(o)
319+
case []uint32:
320+
return copyArbitraryArrayUint32(o)
321+
case []uint64:
322+
return copyArbitraryArrayUint64(o)
323+
case []float32:
324+
return copyArbitraryArrayFloat32(o)
325+
case []float64:
326+
return copyArbitraryArrayFloat64(o)
299327
case map[string]any:
300328
return copyArbitraryValueMap(o)
301329
case *map[string]any:
@@ -310,6 +338,34 @@ func CopyArbitraryValue(anyValue any) Value { //nolint:gocyclo // yes, we know i
310338
return Null()
311339
}
312340
return CopyObject(*o)
341+
case map[string]string:
342+
return copyArbitraryMapString(o)
343+
case map[string]bool:
344+
return copyArbitraryMapBool(o)
345+
case map[string]int:
346+
return copyArbitraryMapInt(o)
347+
case map[string]int8:
348+
return copyArbitraryMapInt8(o)
349+
case map[string]int16:
350+
return copyArbitraryMapInt16(o)
351+
case map[string]int32:
352+
return copyArbitraryMapInt32(o)
353+
case map[string]int64:
354+
return copyArbitraryMapInt64(o)
355+
case map[string]uint:
356+
return copyArbitraryMapUint(o)
357+
case map[string]uint8:
358+
return copyArbitraryMapUint8(o)
359+
case map[string]uint16:
360+
return copyArbitraryMapUint16(o)
361+
case map[string]uint32:
362+
return copyArbitraryMapUint32(o)
363+
case map[string]uint64:
364+
return copyArbitraryMapUint64(o)
365+
case map[string]float32:
366+
return copyArbitraryMapFloat32(o)
367+
case map[string]float64:
368+
return copyArbitraryMapFloat64(o)
313369
case json.RawMessage:
314370
return Raw(o)
315371
case *json.RawMessage:
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package ldvalue
2+
3+
import (
4+
"math/rand"
5+
"testing"
6+
)
7+
8+
func BenchmarkCollectionCopyMapStringSmall(b *testing.B) {
9+
input := generateRandomMap(10)
10+
11+
for i := 0; i < b.N; i++ {
12+
_ = CopyArbitraryValue(input)
13+
}
14+
}
15+
16+
func BenchmarkCollectionCopyMapStringLarge(b *testing.B) {
17+
input := generateRandomMap(1_000)
18+
19+
for i := 0; i < b.N; i++ {
20+
_ = CopyArbitraryValue(input)
21+
}
22+
}
23+
24+
func generateRandomMap(i int) map[string]string {
25+
m := make(map[string]string, i)
26+
27+
for j := 0; j < i; j++ {
28+
m[generateRandomString()] = generateRandomString()
29+
}
30+
31+
return m
32+
}
33+
34+
func BenchmarkCollectionCopySliceStringSmall(b *testing.B) {
35+
input := generateStringSlice(10)
36+
37+
for i := 0; i < b.N; i++ {
38+
_ = CopyArbitraryValue(input)
39+
}
40+
}
41+
42+
func BenchmarkCollectionCopySliceStringMedium(b *testing.B) {
43+
input := generateStringSlice(100)
44+
45+
for i := 0; i < b.N; i++ {
46+
_ = CopyArbitraryValue(input)
47+
}
48+
}
49+
50+
func BenchmarkCollectionCopySliceStringLarge(b *testing.B) {
51+
input := generateStringSlice(1_000)
52+
53+
for i := 0; i < b.N; i++ {
54+
_ = CopyArbitraryValue(input)
55+
}
56+
}
57+
58+
func BenchmarkCollectionCopySliceIntSmall(b *testing.B) {
59+
input := generateIntSlice(10)
60+
61+
for i := 0; i < b.N; i++ {
62+
_ = CopyArbitraryValue(input)
63+
}
64+
}
65+
66+
func BenchmarkCollectionCopySliceIntMedium(b *testing.B) {
67+
input := generateIntSlice(100)
68+
69+
for i := 0; i < b.N; i++ {
70+
_ = CopyArbitraryValue(input)
71+
}
72+
}
73+
74+
func BenchmarkCollectionCopySliceIntLarge(b *testing.B) {
75+
input := generateIntSlice(1_000)
76+
77+
for i := 0; i < b.N; i++ {
78+
_ = CopyArbitraryValue(input)
79+
}
80+
}
81+
82+
func generateIntSlice(n int) []int {
83+
s := make([]int, n)
84+
85+
for i := 0; i < n; i++ {
86+
s[i] = rand.Int()
87+
}
88+
89+
return s
90+
}
91+
92+
func generateStringSlice(n int) []string {
93+
s := make([]string, n)
94+
95+
for i := 0; i < n; i++ {
96+
s[i] = generateRandomString()
97+
}
98+
99+
return s
100+
}
101+
102+
func generateRandomString() string {
103+
alpha := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
104+
105+
b := make([]byte, 10)
106+
for i := range b {
107+
b[i] = alpha[rand.Intn(len(alpha))]
108+
}
109+
110+
return string(b)
111+
}

0 commit comments

Comments
 (0)