go-nostr/envelopes_benchmark_test.go

267 lines
5.8 KiB
Go

//go:build sonic
package nostr
import (
stdlibjson "encoding/json"
"fmt"
"math/rand/v2"
"testing"
"time"
"unsafe"
)
func BenchmarkParseMessage(b *testing.B) {
for _, name := range []string{"relay", "client"} {
b.Run(name, func(b *testing.B) {
messages := generateTestMessages(name)
b.Run("jsonstdlib", func(b *testing.B) {
for b.Loop() {
for _, msg := range messages {
var v any
stdlibjson.Unmarshal(unsafe.Slice(unsafe.StringData(msg), len(msg)), &v)
}
}
})
b.Run("easyjson", func(b *testing.B) {
for b.Loop() {
for _, msg := range messages {
_ = ParseMessage(msg)
}
}
})
b.Run("sonic", func(b *testing.B) {
smp := NewSonicMessageParser()
for b.Loop() {
for _, msg := range messages {
_, _ = smp.ParseMessage(msg)
}
}
})
})
}
}
func generateTestMessages(typ string) []string {
messages := make([]string, 0, 600)
setup := map[string]map[int]func() []byte{
"client": {
600: generateEventMessage,
5: generateEOSEMessage,
9: generateNoticeMessage,
14: generateCountMessage,
20: generateOKMessage,
},
"relay": {
500: generateReqMessage,
50: generateEventMessage,
10: generateCountMessage,
},
}[typ]
for count, generator := range setup {
for range count {
messages = append(messages, string(generator()))
}
}
return messages
}
func generateEventMessage() []byte {
event := generateRandomEvent()
eventJSON, _ := json.Marshal(event)
if rand.IntN(2) == 0 {
subID := fmt.Sprintf("sub_%d", rand.IntN(1000))
return []byte(fmt.Sprintf(`["EVENT","%s",%s]`, subID, string(eventJSON)))
}
return []byte(fmt.Sprintf(`["EVENT",%s]`, string(eventJSON)))
}
func generateRandomEvent() Event {
tagCount := rand.IntN(10)
tags := make(Tags, tagCount)
for i := 0; i < tagCount; i++ {
tagType := string([]byte{byte('a' + rand.IntN(26))})
tagValues := make([]string, rand.IntN(3)+1)
for j := range tagValues {
tagValues[j] = fmt.Sprintf("%d", j)
}
tags[i] = append([]string{tagType}, tagValues...)
}
contentLength := rand.IntN(200) + 10
content := make([]byte, contentLength)
for i := range content {
content[i] = byte('a' + rand.IntN(26))
}
event := Event{
ID: generateRandomHex(64),
PubKey: generateRandomHex(64),
CreatedAt: Timestamp(time.Now().Unix() - int64(rand.IntN(10000000))),
Kind: rand.IntN(10000),
Tags: tags,
Content: string(content),
Sig: generateRandomHex(128),
}
return event
}
func generateAuthMessage() []byte {
if rand.IntN(2) == 0 {
challenge := fmt.Sprintf("challenge_%d", rand.IntN(1000000))
return []byte(fmt.Sprintf(`["AUTH","%s"]`, challenge))
} else {
event := generateRandomEvent()
eventJSON, _ := json.Marshal(event)
return []byte(fmt.Sprintf(`["AUTH",%s]`, string(eventJSON)))
}
}
func generateNoticeMessage() []byte {
noticeLength := rand.IntN(100) + 5
notice := make([]byte, noticeLength)
for i := range notice {
notice[i] = byte('a' + rand.IntN(26))
}
return []byte(fmt.Sprintf(`["NOTICE","%s"]`, string(notice)))
}
func generateEOSEMessage() []byte {
subID := fmt.Sprintf("sub_%d", rand.IntN(1000))
return []byte(fmt.Sprintf(`["EOSE","%s"]`, subID))
}
func generateOKMessage() []byte {
eventID := generateRandomHex(64)
success := rand.IntN(2) == 0
var reason string
if !success {
reasons := []string{
"blocked",
"rate-limited",
"invalid: signature verification failed",
"error: could not connect to the database",
"pow: difficulty too low",
}
reason = reasons[rand.IntN(len(reasons))]
}
return []byte(fmt.Sprintf(`["OK","%s",%t,"%s"]`, eventID, success, reason))
}
func generateCountMessage() []byte {
subID := fmt.Sprintf("sub_%d", rand.IntN(1000))
count := rand.IntN(10000)
if rand.IntN(5) == 0 {
hll := generateRandomHex(512)
return []byte(fmt.Sprintf(`["COUNT","%s",{"count":%d,"hll":"%s"}]`, subID, count, hll))
}
return []byte(fmt.Sprintf(`["COUNT","%s",{"count":%d}]`, subID, count))
}
func generateReqMessage() []byte {
subID := fmt.Sprintf("sub_%d", rand.IntN(1000))
filterCount := rand.IntN(3) + 1
filters := make([]string, filterCount)
for i := range filters {
filter := generateRandomFilter()
filterJSON, _ := json.Marshal(filter)
filters[i] = string(filterJSON)
}
result := fmt.Sprintf(`["REQ","%s"`, subID)
for _, f := range filters {
result += "," + f
}
result += "]"
return []byte(result)
}
func generateRandomFilter() Filter {
filter := Filter{}
if rand.IntN(2) == 0 {
count := rand.IntN(5) + 1
filter.IDs = make([]string, count)
for i := range filter.IDs {
filter.IDs[i] = generateRandomHex(64)
}
}
if rand.IntN(2) == 0 {
count := rand.IntN(5) + 1
filter.Kinds = make([]int, count)
for i := range filter.Kinds {
filter.Kinds[i] = rand.IntN(10000)
}
}
if rand.IntN(2) == 0 {
count := rand.IntN(5) + 1
filter.Authors = make([]string, count)
for i := range filter.Authors {
filter.Authors[i] = generateRandomHex(64)
}
}
if rand.IntN(2) == 0 {
tagCount := rand.IntN(3) + 1
filter.Tags = make(TagMap)
for i := 0; i < tagCount; i++ {
tagName := string([]byte{byte('a' + rand.IntN(26))})
valueCount := rand.IntN(3) + 1
values := make([]string, valueCount)
for j := range values {
values[j] = fmt.Sprintf("tag_value_%d", rand.IntN(100))
}
filter.Tags[tagName] = values
}
}
if rand.IntN(2) == 0 {
ts := Timestamp(time.Now().Unix() - int64(rand.IntN(10000000)))
filter.Since = &ts
}
if rand.IntN(2) == 0 {
ts := Timestamp(time.Now().Unix() - int64(rand.IntN(1000000)))
filter.Until = &ts
}
if rand.IntN(2) == 0 {
filter.Limit = rand.IntN(100) + 1
}
return filter
}
func generateRandomHex(length int) string {
const hexChars = "0123456789abcdef"
result := make([]byte, length)
for i := range result {
result[i] = hexChars[rand.IntN(len(hexChars))]
}
return string(result)
}