- Introduced benchmark tests for JSON and binary marshaling/unmarshaling, canonical encoding, and ID generation to assess performance. - Implemented optimizations to reduce memory allocations and CPU processing time across various encoding methods. - Enhanced `Marshal`, `ToCanonical`, and `MarshalBinary` methods with pre-allocation strategies to minimize reallocations. - Added handling for nil tags to avoid unnecessary allocations during binary encoding. - Documented performance improvements in the new PERFORMANCE_REPORT.md file, highlighting significant reductions in execution time and memory usage.
280 lines
5.6 KiB
Go
280 lines
5.6 KiB
Go
package event
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
"time"
|
|
|
|
"next.orly.dev/pkg/crypto/p256k"
|
|
"next.orly.dev/pkg/encoders/hex"
|
|
"next.orly.dev/pkg/encoders/kind"
|
|
"next.orly.dev/pkg/encoders/tag"
|
|
"lukechampine.com/frand"
|
|
)
|
|
|
|
// createTestEvent creates a realistic test event with proper signing
|
|
func createTestEvent() *E {
|
|
signer := &p256k.Signer{}
|
|
if err := signer.Generate(); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
ev := New()
|
|
ev.Pubkey = signer.Pub()
|
|
ev.CreatedAt = time.Now().Unix()
|
|
ev.Kind = kind.TextNote.K
|
|
|
|
// Create realistic tags
|
|
ev.Tags = tag.NewS(
|
|
tag.NewFromBytesSlice([]byte("t"), []byte("hashtag")),
|
|
tag.NewFromBytesSlice([]byte("e"), hex.EncAppend(nil, frand.Bytes(32))),
|
|
tag.NewFromBytesSlice([]byte("p"), hex.EncAppend(nil, frand.Bytes(32))),
|
|
)
|
|
|
|
// Create realistic content
|
|
ev.Content = []byte(`This is a test event with some content that includes special characters like < > & and "quotes" and various other things that might need escaping.`)
|
|
|
|
// Sign the event
|
|
if err := ev.Sign(signer); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return ev
|
|
}
|
|
|
|
// createLargeTestEvent creates a larger event with more tags and content
|
|
func createLargeTestEvent() *E {
|
|
signer := &p256k.Signer{}
|
|
if err := signer.Generate(); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
ev := New()
|
|
ev.Pubkey = signer.Pub()
|
|
ev.CreatedAt = time.Now().Unix()
|
|
ev.Kind = kind.TextNote.K
|
|
|
|
// Create many tags
|
|
tags := tag.NewS()
|
|
for i := 0; i < 20; i++ {
|
|
tags.Append(tag.NewFromBytesSlice(
|
|
[]byte("t"),
|
|
[]byte("hashtag" + string(rune('0'+i))),
|
|
))
|
|
if i%3 == 0 {
|
|
tags.Append(tag.NewFromBytesSlice(
|
|
[]byte("e"),
|
|
hex.EncAppend(nil, frand.Bytes(32)),
|
|
))
|
|
}
|
|
}
|
|
ev.Tags = tags
|
|
|
|
// Large content
|
|
content := make([]byte, 0, 4096)
|
|
for i := 0; i < 50; i++ {
|
|
content = append(content, []byte("This is a longer piece of content that simulates real-world event content. ")...)
|
|
if i%10 == 0 {
|
|
content = append(content, []byte("With special chars: < > & \" ' ")...)
|
|
}
|
|
}
|
|
ev.Content = content
|
|
|
|
// Sign the event
|
|
if err := ev.Sign(signer); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return ev
|
|
}
|
|
|
|
// BenchmarkJSONMarshal benchmarks the JSON marshaling
|
|
func BenchmarkJSONMarshal(b *testing.B) {
|
|
ev := createTestEvent()
|
|
defer ev.Free()
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_ = ev.Marshal(nil)
|
|
}
|
|
}
|
|
|
|
// BenchmarkJSONMarshalLarge benchmarks JSON marshaling with large events
|
|
func BenchmarkJSONMarshalLarge(b *testing.B) {
|
|
ev := createLargeTestEvent()
|
|
defer ev.Free()
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_ = ev.Marshal(nil)
|
|
}
|
|
}
|
|
|
|
// BenchmarkJSONUnmarshal benchmarks JSON unmarshaling
|
|
func BenchmarkJSONUnmarshal(b *testing.B) {
|
|
ev := createTestEvent()
|
|
jsonData := ev.Marshal(nil)
|
|
defer ev.Free()
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
ev2 := New()
|
|
_, err := ev2.Unmarshal(jsonData)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
ev2.Free()
|
|
}
|
|
}
|
|
|
|
// BenchmarkBinaryMarshal benchmarks binary marshaling
|
|
func BenchmarkBinaryMarshal(b *testing.B) {
|
|
ev := createTestEvent()
|
|
defer ev.Free()
|
|
|
|
buf := &bytes.Buffer{}
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
buf.Reset()
|
|
ev.MarshalBinary(buf)
|
|
}
|
|
}
|
|
|
|
// BenchmarkBinaryMarshalLarge benchmarks binary marshaling with large events
|
|
func BenchmarkBinaryMarshalLarge(b *testing.B) {
|
|
ev := createLargeTestEvent()
|
|
defer ev.Free()
|
|
|
|
buf := &bytes.Buffer{}
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
buf.Reset()
|
|
ev.MarshalBinary(buf)
|
|
}
|
|
}
|
|
|
|
// BenchmarkBinaryUnmarshal benchmarks binary unmarshaling
|
|
func BenchmarkBinaryUnmarshal(b *testing.B) {
|
|
ev := createTestEvent()
|
|
buf := &bytes.Buffer{}
|
|
ev.MarshalBinary(buf)
|
|
binaryData := buf.Bytes()
|
|
defer ev.Free()
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
ev2 := New()
|
|
reader := bytes.NewReader(binaryData)
|
|
if err := ev2.UnmarshalBinary(reader); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
ev2.Free()
|
|
}
|
|
}
|
|
|
|
// BenchmarkCanonical benchmarks canonical encoding
|
|
func BenchmarkCanonical(b *testing.B) {
|
|
ev := createTestEvent()
|
|
defer ev.Free()
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_ = ev.ToCanonical(nil)
|
|
}
|
|
}
|
|
|
|
// BenchmarkCanonicalLarge benchmarks canonical encoding with large events
|
|
func BenchmarkCanonicalLarge(b *testing.B) {
|
|
ev := createLargeTestEvent()
|
|
defer ev.Free()
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_ = ev.ToCanonical(nil)
|
|
}
|
|
}
|
|
|
|
// BenchmarkGetIDBytes benchmarks ID generation (canonical + hash)
|
|
func BenchmarkGetIDBytes(b *testing.B) {
|
|
ev := createTestEvent()
|
|
defer ev.Free()
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_ = ev.GetIDBytes()
|
|
}
|
|
}
|
|
|
|
// BenchmarkRoundTripJSON benchmarks JSON marshal/unmarshal round trip
|
|
func BenchmarkRoundTripJSON(b *testing.B) {
|
|
ev := createTestEvent()
|
|
defer ev.Free()
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
jsonData := ev.Marshal(nil)
|
|
ev2 := New()
|
|
_, err := ev2.Unmarshal(jsonData)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
ev2.Free()
|
|
}
|
|
}
|
|
|
|
// BenchmarkRoundTripBinary benchmarks binary marshal/unmarshal round trip
|
|
func BenchmarkRoundTripBinary(b *testing.B) {
|
|
ev := createTestEvent()
|
|
defer ev.Free()
|
|
|
|
buf := &bytes.Buffer{}
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
buf.Reset()
|
|
ev.MarshalBinary(buf)
|
|
|
|
ev2 := New()
|
|
reader := bytes.NewReader(buf.Bytes())
|
|
if err := ev2.UnmarshalBinary(reader); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
ev2.Free()
|
|
}
|
|
}
|
|
|
|
// BenchmarkEstimateSize benchmarks size estimation
|
|
func BenchmarkEstimateSize(b *testing.B) {
|
|
ev := createTestEvent()
|
|
defer ev.Free()
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_ = ev.EstimateSize()
|
|
}
|
|
}
|
|
|