-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathnanoid_benchmark_test.go
387 lines (333 loc) · 11.5 KB
/
nanoid_benchmark_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
// Copyright (c) 2024 Six After, Inc.
//
// This source code is licensed under the Apache 2.0 License found in the
// LICENSE file in the root directory of this source tree.
package nanoid
import (
"fmt"
"strings"
"sync"
"testing"
"golang.org/x/exp/constraints"
)
// Helper function to create an ASCII-based alphabet of a specified length without duplicates
func makeASCIIBasedAlphabet(length int) string {
const (
start = 33 // (!)
end = 126 // (~)
)
// Calculate the number of unique printable ASCII characters in the range
rangeSize := end - start + 1
// Ensure the length does not exceed the number of unique characters
if length > rangeSize {
length = rangeSize
}
alphabet := make([]byte, length)
for i := 0; i < length; i++ {
alphabet[i] = byte(start + i)
}
return string(alphabet)
}
// Helper function to create a Unicode alphabet of a specified length without duplicates
// The printable Unicode range is extensive and varies widely across different scripts and symbol sets, as Unicode was designed to represent characters from numerous languages, symbols, and emojis. Unlike ASCII, Unicode doesn’t have a simple, contiguous range for all printable characters. However, there are several primary ranges in Unicode where printable characters are defined:
// 1. Basic Multilingual Plane (BMP): The majority of commonly used printable characters are in the BMP, which spans 0x0020 to 0xFFFF (decimal 32 to 65,535). This plane includes:
// - Latin characters (including ASCII, starting from 0x0020 for space).
// - Greek, Cyrillic, Hebrew, Arabic, and other alphabets.
// - Mathematical symbols, punctuation, and various technical symbols.
// - Chinese, Japanese, and Korean (CJK) characters.
// - Emojis and other miscellaneous symbols.
//
// 2. Supplementary Multilingual Plane (SMP): Includes additional printable characters, such as:
// - Historic scripts.
// - Musical notation.
// - Extended emoji sets.
// - This plane spans 0x10000 to 0x1FFFF.
//
// 3. Supplementary Ideographic Plane (SIP): Contains additional Chinese, Japanese, and Korean ideographs from 0x20000 to 0x2FFFF.
// 4. Other Supplementary Planes: These include various specialized characters, symbols, and private-use areas.
func makeUnicodeAlphabet(length int) string {
// Greek and Coptic Block
const (
start = 0x0370 // (ἰ)
end = 0x047F // (ѫ)
)
// Calculate the number of unique runes in the range
rangeSize := end - start + 1
// Ensure the length does not exceed the number of unique characters
if length > rangeSize {
length = rangeSize
}
var builder strings.Builder
for i := 0; i < length; i++ {
builder.WriteRune(rune(start + i))
}
return builder.String()
}
type Number interface {
constraints.Float | constraints.Integer
}
func mean[T Number](data []T) float64 {
if len(data) == 0 {
return 0
}
var sum float64
for _, d := range data {
sum += float64(d)
}
return sum / float64(len(data))
}
const asciiAlphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// BenchmarkNanoIDAllocations benchmarks the memory allocations and performance of generating a Nano ID
// with a length of 21 and an alphabet consisting of uppercase letters, lowercase letters, and numbers.
func BenchmarkNanoIDAllocations(b *testing.B) {
b.ReportAllocs() // Report memory allocations
const idLength = 21
// Initialize the generator with the specified length and alphabet
gen, err := NewGenerator(
WithAlphabet(asciiAlphabet),
WithLengthHint(idLength))
if err != nil {
b.Fatalf("failed to create generator: %v", err)
}
// Reset the timer to ignore setup time and track only the ID generation
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err = gen.New(idLength)
}
}
// BenchmarkNanoIDAllocationsConcurrent benchmarks the memory allocations and performance of generating
// a Nano ID concurrently with a length of 21 and an alphabet consisting of uppercase letters,
// lowercase letters, and numbers.
func BenchmarkNanoIDAllocationsConcurrent(b *testing.B) {
b.ReportAllocs() // Report memory allocations
// Alphabet and ID length for the test
const idLength = 21
// Initialize the generator with the specified length and alphabet
gen, err := NewGenerator(
WithAlphabet(asciiAlphabet),
WithLengthHint(idLength))
if err != nil {
b.Fatalf("failed to create generator: %v", err)
}
// Reset the timer to ignore setup time and track only the ID generation
b.ResetTimer()
// Run the benchmark in parallel
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err := gen.New(idLength)
if err != nil {
b.Errorf("failed to generate ID: %v", err)
}
}
})
}
// BenchmarkGenerator_Read_DefaultLength benchmarks reading into a buffer equal to DefaultLength.
func BenchmarkGenerator_Read_DefaultLength(b *testing.B) {
buffer := make([]byte, DefaultLength)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := Generator.Read(buffer)
if err != nil {
b.Fatalf("Read returned an unexpected error: %v", err)
}
}
}
// BenchmarkGenerator_Read_VaryingBufferSizes benchmarks reading into buffers of varying sizes.
func BenchmarkGenerator_Read_VaryingBufferSizes(b *testing.B) {
bufferSizes := []int{2, 3, 5, 13, 21, 34}
m := mean(bufferSizes)
gen, err := NewGenerator(WithLengthHint(uint16(m)))
if err != nil {
b.Fatalf("Failed to create generator: %v", err)
}
for _, size := range bufferSizes {
b.Run(fmt.Sprintf("BufferSize_%d", size), func(b *testing.B) {
buffer := make([]byte, size)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := gen.Read(buffer)
if err != nil {
b.Fatalf("Read returned an unexpected error: %v", err)
}
}
})
}
}
// BenchmarkGenerator_Read_ZeroLengthBuffer benchmarks reading into a zero-length buffer.
func BenchmarkGenerator_Read_ZeroLengthBuffer(b *testing.B) {
buffer := make([]byte, 0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := Generator.Read(buffer)
if err != nil {
b.Fatalf("Read returned an unexpected error: %v", err)
}
}
}
// BenchmarkGenerator_Read_Concurrent benchmarks concurrent reads to assess thread safety and performance.
func BenchmarkGenerator_Read_Concurrent(b *testing.B) {
bufferSize := DefaultLength
concurrencyLevels := []int{1, 2, 4, 8, 16}
for _, concurrency := range concurrencyLevels {
b.Run(fmt.Sprintf("Concurrency_%d", concurrency), func(b *testing.B) {
var wg sync.WaitGroup
b.SetParallelism(concurrency)
b.ResetTimer()
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func() {
defer wg.Done()
buffer := make([]byte, bufferSize)
for j := 0; j < b.N/concurrency; j++ {
_, err := Generator.Read(buffer)
if err != nil {
b.Errorf("Read returned an unexpected error: %v", err)
return
}
}
}()
}
wg.Wait()
})
}
}
// BenchmarkNanoIDGeneration benchmarks Nano ID generation for varying alphabet types, alphabet lengths, and ID lengths
func BenchmarkNanoIDGeneration(b *testing.B) {
b.ReportAllocs() // Report memory allocations
// Define the Nano ID lengths to test
idLengths := []int{8, 16, 21, 32, 64, 128}
mean := mean(idLengths)
// Define the alphabet lengths to test
alphabetLengths := []int{2, 16, 32, 64}
// Define the alphabet types to test
alphabetTypes := []string{"ASCII", "Unicode"}
for _, alphabetType := range alphabetTypes {
for _, alphaLen := range alphabetLengths {
// New the appropriate alphabet
var alphabet string
if alphabetType == "ASCII" {
alphabet = makeASCIIBasedAlphabet(alphaLen)
} else {
alphabet = makeUnicodeAlphabet(alphaLen)
}
// Initialize the generator without passing 'nil'
gen, err := NewGenerator(
WithAlphabet(alphabet),
WithLengthHint(uint16(mean)),
)
if err != nil {
b.Fatalf("Failed to create generator with %s alphabet of length %d: %v", alphabetType, alphaLen, err)
}
// Create a sub-benchmark for each alphabet configuration
b.Run(fmt.Sprintf("%s_AlphabetLen%d", alphabetType, alphaLen), func(b *testing.B) {
for _, idLen := range idLengths {
// Create a nested sub-benchmark for each Nano ID length
b.Run(fmt.Sprintf("IDLen%d", idLen), func(b *testing.B) {
// Reset the timer to exclude setup time
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := gen.New(idLen)
if err != nil {
b.Fatalf("Failed to generate Nano ID: %v", err)
}
}
})
}
})
}
}
}
// BenchmarkNanoIDGenerationParallel benchmarks Nano ID generation in parallel for varying configurations
func BenchmarkNanoIDGenerationParallel(b *testing.B) {
b.ReportAllocs() // Report memory allocations
// Define the Nano ID lengths to test
idLengths := []int{8, 16, 21, 32, 64, 128}
mean := mean(idLengths)
// Define the alphabet lengths to test
alphabetLengths := []int{2, 16, 32, 64}
// Define the alphabet types to test
alphabetTypes := []string{"ASCII", "Unicode"}
for _, alphabetType := range alphabetTypes {
for _, alphaLen := range alphabetLengths {
// New the appropriate alphabet
var alphabet string
if alphabetType == "ASCII" {
alphabet = makeASCIIBasedAlphabet(alphaLen)
} else {
alphabet = makeUnicodeAlphabet(alphaLen)
}
// Initialize the generator without passing 'nil'
gen, err := NewGenerator(
WithAlphabet(alphabet),
WithLengthHint(uint16(mean)),
)
if err != nil {
b.Fatalf("Failed to create generator with %s alphabet of length %d: %v", alphabetType, alphaLen, err)
}
// Create a sub-benchmark for each alphabet configuration
b.Run(fmt.Sprintf("%s_AlphabetLen%d", alphabetType, alphaLen), func(b *testing.B) {
for _, idLen := range idLengths {
// Create a nested sub-benchmark for each Nano ID length
b.Run(fmt.Sprintf("IDLen%d", idLen), func(b *testing.B) {
// Reset the timer to exclude setup time
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err := gen.New(idLen)
if err != nil {
b.Fatalf("Failed to generate Nano ID: %v", err)
}
}
})
})
}
})
}
}
}
// BenchmarkNanoIDWithVaryingAlphabetLengths benchmarks how different alphabet lengths affect Nano ID generation
func BenchmarkNanoIDWithVaryingAlphabetLengths(b *testing.B) {
b.ReportAllocs() // Report memory allocations
// Define the alphabet types to test
alphabetTypes := []string{"ASCII", "Unicode"}
// Define the alphabet lengths to test
alphabetLengths := []int{2, 16, 32, 64}
// Define the Nano ID lengths to test
idLengths := []int{8, 16, 21, 32, 64, 128}
mean := mean(idLengths)
for _, alphabetType := range alphabetTypes {
for _, alphaLen := range alphabetLengths {
// New the appropriate alphabet
var alphabet string
if alphabetType == "ASCII" {
alphabet = makeASCIIBasedAlphabet(alphaLen)
} else {
alphabet = makeUnicodeAlphabet(alphaLen)
}
// Initialize the generator without passing 'nil'
gen, err := NewGenerator(
WithAlphabet(alphabet),
WithLengthHint(uint16(mean)),
)
if err != nil {
b.Fatalf("Failed to create generator with %s alphabet of length %d: %v", alphabetType, alphaLen, err)
}
// Create a sub-benchmark for each alphabet configuration
b.Run(fmt.Sprintf("%s_AlphabetLen%d", alphabetType, alphaLen), func(b *testing.B) {
for _, idLen := range idLengths {
// Create a nested sub-benchmark for each Nano ID length
b.Run(fmt.Sprintf("IDLen%d", idLen), func(b *testing.B) {
// Reset the timer to exclude setup time
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := gen.New(idLen)
if err != nil {
b.Fatalf("Failed to generate Nano ID: %v", err)
}
}
})
}
})
}
}
}