-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix these simdjson benchmarks so others can run them.
- Loading branch information
Showing
2 changed files
with
223 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,114 +1,257 @@ | ||
package nostr | ||
|
||
import ( | ||
"bufio" | ||
"os" | ||
"fmt" | ||
"math/rand/v2" | ||
"testing" | ||
"time" | ||
|
||
"github.com/minio/simdjson-go" | ||
) | ||
|
||
// benchmarkParseMessage benchmarks the ParseMessage function | ||
func BenchmarkParseMessage(b *testing.B) { | ||
messages := getTestMessages() | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
for _, msg := range messages { | ||
_ = ParseMessage(msg) | ||
messages := generateTestMessages(2000) | ||
|
||
for i := range 15 { | ||
fmt.Println(string(messages[i])) | ||
} | ||
|
||
b.Run("golang", func(b *testing.B) { | ||
for i := 0; i < b.N; i++ { | ||
for _, msg := range messages { | ||
_ = ParseMessage(msg) | ||
} | ||
} | ||
}) | ||
|
||
b.Run("simdjson", func(b *testing.B) { | ||
pj := &simdjson.ParsedJson{} | ||
for i := 0; i < b.N; i++ { | ||
for _, msg := range messages { | ||
_, _ = ParseMessageSIMD(msg, pj) | ||
} | ||
} | ||
}) | ||
} | ||
|
||
func generateTestMessages(count int) [][]byte { | ||
messages := make([][]byte, 0, count) | ||
|
||
for i := 0; i < count; i++ { | ||
var msg []byte | ||
|
||
switch rand.IntN(12) { | ||
case 1: | ||
msg = generateAuthMessage() | ||
case 2: | ||
msg = generateNoticeMessage() | ||
case 3: | ||
msg = generateEOSEMessage() | ||
case 4: | ||
msg = generateOKMessage() | ||
case 5: | ||
msg = generateCountMessage() | ||
case 6: | ||
msg = generateReqMessage() | ||
default: | ||
msg = generateEventMessage() | ||
} | ||
|
||
messages = append(messages, msg) | ||
} | ||
|
||
return messages | ||
} | ||
|
||
// benchmarkParseMessageSIMD benchmarks the ParseMessageSIMD function | ||
func BenchmarkParseMessageSIMD(b *testing.B) { | ||
messages := getTestMessages() | ||
var pj *simdjson.ParsedJson | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
for _, msg := range messages { | ||
_, _ = ParseMessageSIMD(msg, pj) | ||
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(5)+1) | ||
for j := range tagValues { | ||
tagValues[j] = fmt.Sprintf("value_%d_%d", i, 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 | ||
} | ||
|
||
// benchmarkParseMessageSIMDWithReuse benchmarks the ParseMessageSIMD function with reusing the ParsedJson object | ||
func BenchmarkParseMessageSIMDWithReuse(b *testing.B) { | ||
messages := getTestMessages() | ||
pj, _ := simdjson.Parse(make([]byte, 1024), nil) // initialize with some capacity | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
for _, msg := range messages { | ||
_, _ = ParseMessageSIMD(msg, pj) | ||
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)) | ||
} | ||
|
||
// getTestMessages returns a slice of test messages for benchmarking | ||
func getTestMessages() [][]byte { | ||
// these are sample messages from the test cases | ||
return [][]byte{ | ||
[]byte(`["EVENT","_",{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`), | ||
[]byte(`["EVENT",{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`), | ||
[]byte(`["AUTH","challenge-string"]`), | ||
[]byte(`["AUTH",{"kind":22242,"id":"9b86ca5d2a9b4aa09870e710438c2fd2fcdeca12a18b6f17ab9ebcdbc43f1d4a","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1740505646,"tags":[["relay","ws://localhost:7777","2"],["challenge","3027526784722639360"]],"content":"","sig":"eceb827c4bba1de0ab8ee43f3e98df71194f5bdde0af27b5cda38e5c4338b5f63d31961acb5e3c119fd00ecef8b469867d060b697dbaa6ecee1906b483bc307d"}]`), | ||
[]byte(`["NOTICE","test notice message"]`), | ||
[]byte(`["EOSE","subscription123"]`), | ||
[]byte(`["CLOSE","subscription123"]`), | ||
[]byte(`["CLOSED","subscription123","reason: test closed"]`), | ||
[]byte(`["OK","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa",true,""]`), | ||
[]byte(`["COUNT","sub1",{"count":42}]`), | ||
[]byte(`["REQ","sub1",{"until":999999,"kinds":[1]}]`), | ||
[]byte(`["REQ","sub1z\\\"zzz",{"authors":["9b86ca5d2a9b4aa09870e710438c2fd2fcdeca12a18b6f17ab9ebcdbc43f1d4a","8eee10b2ce1162b040fdcfdadb4f888c64aaf87531dab28cc0c09fbdea1b663e","0deadebefb3c1a760f036952abf675076343dd8424efdeaa0f1d9803a359ed46"],"since":1740425099,"limit":2,"#x":["<","as"]},{"kinds":[2345,112],"#plic":["a"],"#ploc":["blblb","wuwuw"]}]`), | ||
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)) | ||
} | ||
|
||
// benchmarkParseMessagesFromFile benchmarks parsing messages from a file | ||
// this function can be used if you have a file with messages | ||
func BenchmarkParseMessagesFromFile(b *testing.B) { | ||
// read all messages into memory | ||
file, err := os.Open("testdata/messages.json") | ||
if err != nil { | ||
b.Fatal(err) | ||
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) | ||
} | ||
var messages [][]byte | ||
scanner := bufio.NewScanner(file) | ||
for scanner.Scan() { | ||
messages = append(messages, []byte(scanner.Text())) | ||
|
||
result := fmt.Sprintf(`["REQ","%s"`, subID) | ||
for _, f := range filters { | ||
result += "," + f | ||
} | ||
if err := scanner.Err(); err != nil { | ||
b.Fatal(err) | ||
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) | ||
} | ||
} | ||
file.Close() | ||
|
||
// benchmark ParseMessage | ||
b.Run("ParseMessage", func(b *testing.B) { | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
for _, msg := range messages { | ||
_ = ParseMessage(msg) | ||
} | ||
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) | ||
} | ||
}) | ||
} | ||
|
||
// benchmark ParseMessageSIMD | ||
b.Run("ParseMessageSIMD", func(b *testing.B) { | ||
var pj *simdjson.ParsedJson | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
for _, msg := range messages { | ||
_, _ = ParseMessageSIMD(msg, pj) | ||
} | ||
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) | ||
} | ||
}) | ||
} | ||
|
||
// benchmark ParseMessageSIMD with reuse | ||
b.Run("ParseMessageSIMDWithReuse", func(b *testing.B) { | ||
pj, _ := simdjson.Parse(make([]byte, 1024), nil) // initialize with some capacity | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
for _, msg := range messages { | ||
_, _ = ParseMessageSIMD(msg, pj) | ||
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters