Skip to content

Commit 0412a1e

Browse files
committed
improve peer benchmarks
1 parent cb6befe commit 0412a1e

File tree

4 files changed

+237
-135
lines changed

4 files changed

+237
-135
lines changed

storage/database/peer.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ func (db *Database) PeerAdd(hash storage.Hash, id storage.PeerID, ip netip.Addr,
2626
torrent.mutex.Unlock()
2727
}
2828

29-
// TODO: test if this claim of performance is true
30-
// raw increment is 19x faster than atomic so we might as well just wrap it in the mutex
29+
// Benchmarks run on January 10, 2026 showed atomics are faster in isolation, but since we
30+
// already take this lock for map updates, keeping the counter update under the same lock
31+
// is slightly faster than adding separate atomic ops.
3132
torrent.mutex.Lock()
3233
if peerExists {
3334
if !peer.Complete && complete {
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package database
2+
3+
import (
4+
"math/rand"
5+
"testing"
6+
"time"
7+
8+
"github.com/crimist/trakx/stats"
9+
"github.com/crimist/trakx/storage"
10+
)
11+
12+
const (
13+
benchPeerCount = 1 << 15
14+
benchPeerMask = benchPeerCount - 1
15+
benchPeerShift = 15
16+
benchTorrentCount = 1 << 8
17+
benchTorrentMask = benchTorrentCount - 1
18+
)
19+
20+
func newBenchDB(b *testing.B) *Database {
21+
b.Helper()
22+
db, err := NewDatabase(Config{
23+
InitalSize: 1,
24+
EvictionFrequency: 1 * time.Minute,
25+
ExpirationTime: 1 * time.Minute,
26+
Collector: stats.NewCollectors(false, false, 0),
27+
})
28+
if err != nil {
29+
b.Fatal("Failed to create database")
30+
}
31+
return db
32+
}
33+
34+
func makePeerIDs(n int, seed int64) []storage.PeerID {
35+
rnd := rand.New(rand.NewSource(seed))
36+
ids := make([]storage.PeerID, n)
37+
for i := range ids {
38+
rnd.Read(ids[i][:])
39+
}
40+
return ids
41+
}
42+
43+
func makeHashes(n int, seed int64) []storage.Hash {
44+
rnd := rand.New(rand.NewSource(seed))
45+
hashes := make([]storage.Hash, n)
46+
for i := range hashes {
47+
rnd.Read(hashes[i][:])
48+
}
49+
return hashes
50+
}
51+
52+
func prefillSingleTorrent(db *Database, ids []storage.PeerID) {
53+
for i, id := range ids {
54+
complete := (i & 1) == 0
55+
db.PeerAdd(testTorrentHash1, id, testPeerIP, 1234, complete)
56+
}
57+
}
58+
59+
func prefillMultiTorrent(db *Database, ids []storage.PeerID, hashes []storage.Hash) {
60+
for i, id := range ids {
61+
hash := hashes[i&benchTorrentMask]
62+
complete := (i & 1) == 0
63+
db.PeerAdd(hash, id, testPeerIP, 1234, complete)
64+
}
65+
}
66+
67+
func BenchmarkPeerAddSingle(b *testing.B) {
68+
ids := makePeerIDs(benchPeerCount, 1)
69+
70+
b.Run("NewPeer", func(b *testing.B) {
71+
db := newBenchDB(b)
72+
idx := 0
73+
b.ReportAllocs()
74+
b.ResetTimer()
75+
for n := 0; n < b.N; n++ {
76+
if idx == len(ids) {
77+
b.StopTimer()
78+
db = newBenchDB(b)
79+
idx = 0
80+
b.StartTimer()
81+
}
82+
id := ids[idx]
83+
complete := (idx & 1) == 0
84+
db.PeerAdd(testTorrentHash1, id, testPeerIP, 1234, complete)
85+
idx++
86+
}
87+
})
88+
89+
b.Run("ExistingPeer", func(b *testing.B) {
90+
db := newBenchDB(b)
91+
prefillSingleTorrent(db, ids)
92+
idx := 0
93+
b.ReportAllocs()
94+
b.ResetTimer()
95+
for n := 0; n < b.N; n++ {
96+
id := ids[idx&benchPeerMask]
97+
cycle := idx >> benchPeerShift
98+
complete := (cycle & 1) == 0
99+
db.PeerAdd(testTorrentHash1, id, testPeerIP, 1234, complete)
100+
idx++
101+
}
102+
})
103+
}
104+
105+
func BenchmarkPeerAddSingleParallell(b *testing.B) {
106+
ids := makePeerIDs(benchPeerCount, 2)
107+
108+
b.Run("ExistingPeer", func(b *testing.B) {
109+
db := newBenchDB(b)
110+
prefillSingleTorrent(db, ids)
111+
b.ReportAllocs()
112+
b.ResetTimer()
113+
b.RunParallel(func(pb *testing.PB) {
114+
idx := 0
115+
for pb.Next() {
116+
id := ids[idx&benchPeerMask]
117+
cycle := idx >> benchPeerShift
118+
complete := (cycle & 1) == 0
119+
db.PeerAdd(testTorrentHash1, id, testPeerIP, 1234, complete)
120+
idx++
121+
}
122+
})
123+
})
124+
125+
b.Run("MixedPeerIDs", func(b *testing.B) {
126+
db := newBenchDB(b)
127+
b.ReportAllocs()
128+
b.ResetTimer()
129+
b.RunParallel(func(pb *testing.PB) {
130+
idx := 0
131+
for pb.Next() {
132+
id := ids[idx&benchPeerMask]
133+
complete := (idx & 1) == 0
134+
db.PeerAdd(testTorrentHash1, id, testPeerIP, 1234, complete)
135+
idx++
136+
}
137+
})
138+
})
139+
}
140+
141+
func BenchmarkPeerAddMulti(b *testing.B) {
142+
ids := makePeerIDs(benchPeerCount, 3)
143+
hashes := makeHashes(benchTorrentCount, 4)
144+
145+
b.Run("NewPeer", func(b *testing.B) {
146+
db := newBenchDB(b)
147+
idx := 0
148+
b.ReportAllocs()
149+
b.ResetTimer()
150+
for n := 0; n < b.N; n++ {
151+
if idx == len(ids) {
152+
b.StopTimer()
153+
db = newBenchDB(b)
154+
idx = 0
155+
b.StartTimer()
156+
}
157+
id := ids[idx]
158+
hash := hashes[idx&benchTorrentMask]
159+
complete := (idx & 1) == 0
160+
db.PeerAdd(hash, id, testPeerIP, 1234, complete)
161+
idx++
162+
}
163+
})
164+
165+
b.Run("ExistingPeer", func(b *testing.B) {
166+
db := newBenchDB(b)
167+
prefillMultiTorrent(db, ids, hashes)
168+
idx := 0
169+
b.ReportAllocs()
170+
b.ResetTimer()
171+
for n := 0; n < b.N; n++ {
172+
id := ids[idx&benchPeerMask]
173+
hash := hashes[idx&benchTorrentMask]
174+
cycle := idx >> benchPeerShift
175+
complete := (cycle & 1) == 0
176+
db.PeerAdd(hash, id, testPeerIP, 1234, complete)
177+
idx++
178+
}
179+
})
180+
}
181+
182+
func BenchmarkPeerAddMultiParallell(b *testing.B) {
183+
ids := makePeerIDs(benchPeerCount, 5)
184+
hashes := makeHashes(benchTorrentCount, 6)
185+
186+
b.Run("ExistingPeer", func(b *testing.B) {
187+
db := newBenchDB(b)
188+
prefillMultiTorrent(db, ids, hashes)
189+
b.ReportAllocs()
190+
b.ResetTimer()
191+
b.RunParallel(func(pb *testing.PB) {
192+
idx := 0
193+
for pb.Next() {
194+
id := ids[idx&benchPeerMask]
195+
hash := hashes[idx&benchTorrentMask]
196+
cycle := idx >> benchPeerShift
197+
complete := (cycle & 1) == 0
198+
db.PeerAdd(hash, id, testPeerIP, 1234, complete)
199+
idx++
200+
}
201+
})
202+
})
203+
204+
b.Run("MixedPeerIDs", func(b *testing.B) {
205+
db := newBenchDB(b)
206+
b.ReportAllocs()
207+
b.ResetTimer()
208+
b.RunParallel(func(pb *testing.PB) {
209+
idx := 0
210+
for pb.Next() {
211+
id := ids[idx&benchPeerMask]
212+
hash := hashes[idx&benchTorrentMask]
213+
complete := (idx & 1) == 0
214+
db.PeerAdd(hash, id, testPeerIP, 1234, complete)
215+
idx++
216+
}
217+
})
218+
})
219+
}

storage/database/peer_test.go

Lines changed: 0 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,13 @@
11
package database
22

33
import (
4-
"fmt"
5-
"math/rand"
6-
"net/netip"
74
"testing"
85
"time"
96

107
"github.com/crimist/trakx/stats"
118
"github.com/crimist/trakx/storage"
129
)
1310

14-
var (
15-
testTorrentHash1 = storage.Hash([20]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
16-
testTorrentHash2 = storage.Hash([20]byte{1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
17-
testPeerIP = netip.AddrFrom4([4]byte{1, 2, 3, 4})
18-
testPeerID1 = storage.PeerID([20]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
19-
testPeerID2 = storage.PeerID([20]byte{1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
20-
)
21-
2211
func TestPeerAdd(t *testing.T) {
2312
db, err := NewDatabase(Config{
2413
InitalSize: 1,
@@ -80,125 +69,3 @@ func TestPeerRemove(t *testing.T) {
8069
t.Error("peer not removed from database")
8170
}
8271
}
83-
84-
func BenchmarkPeerAddSingle(b *testing.B) {
85-
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
86-
87-
db, err := NewDatabase(Config{
88-
InitalSize: 1,
89-
EvictionFrequency: 1 * time.Minute,
90-
ExpirationTime: 1 * time.Minute,
91-
Collector: stats.NewCollectors(false, false, 0),
92-
})
93-
if err != nil {
94-
b.Fatal("Failed to create database")
95-
}
96-
benchPeer := storage.Peer{
97-
Complete: true,
98-
IP: testPeerIP,
99-
Port: 1234,
100-
}
101-
var peerid storage.PeerID
102-
103-
b.ResetTimer()
104-
for n := 0; n < b.N; n++ {
105-
rnd.Read(peerid[:])
106-
db.PeerAdd(testTorrentHash1, peerid, benchPeer.IP, benchPeer.Port, benchPeer.Complete)
107-
}
108-
}
109-
110-
func BenchmarkPeerAddSingleParallell(b *testing.B) {
111-
for routines := 1; routines < 1000; routines *= 10 {
112-
b.Run(fmt.Sprintf("%d", routines), func(b *testing.B) {
113-
db, err := NewDatabase(Config{
114-
InitalSize: 1,
115-
EvictionFrequency: 1 * time.Minute,
116-
ExpirationTime: 1 * time.Minute,
117-
Collector: stats.NewCollectors(false, false, 0),
118-
})
119-
if err != nil {
120-
b.Fatal("Failed to create database")
121-
}
122-
benchPeer := storage.Peer{
123-
Complete: true,
124-
IP: testPeerIP,
125-
Port: 1234,
126-
}
127-
128-
b.SetParallelism(routines)
129-
b.ResetTimer()
130-
b.RunParallel(func(pb *testing.PB) {
131-
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
132-
var peerid storage.PeerID
133-
134-
for pb.Next() {
135-
rnd.Read(peerid[:])
136-
db.PeerAdd(testTorrentHash1, peerid, benchPeer.IP, benchPeer.Port, benchPeer.Complete)
137-
}
138-
})
139-
})
140-
}
141-
}
142-
143-
func BenchmarkPeerAddMulti(b *testing.B) {
144-
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
145-
146-
db, err := NewDatabase(Config{
147-
InitalSize: 1,
148-
EvictionFrequency: 1 * time.Minute,
149-
ExpirationTime: 1 * time.Minute,
150-
Collector: stats.NewCollectors(false, false, 0),
151-
})
152-
if err != nil {
153-
b.Fatal("Failed to create database")
154-
}
155-
benchPeer := storage.Peer{
156-
Complete: true,
157-
IP: testPeerIP,
158-
Port: 1234,
159-
}
160-
var peerid storage.PeerID
161-
var hash storage.Hash
162-
163-
b.ResetTimer()
164-
for n := 0; n < b.N; n++ {
165-
rnd.Read(peerid[:])
166-
rnd.Read(hash[:])
167-
db.PeerAdd(hash, peerid, benchPeer.IP, benchPeer.Port, benchPeer.Complete)
168-
}
169-
}
170-
171-
func BenchmarkPeerAddMultiParallell(b *testing.B) {
172-
for routines := 1; routines < 1000; routines *= 10 {
173-
b.Run(fmt.Sprintf("%d", routines), func(b *testing.B) {
174-
db, err := NewDatabase(Config{
175-
InitalSize: 1,
176-
EvictionFrequency: 1 * time.Minute,
177-
ExpirationTime: 1 * time.Minute,
178-
Collector: stats.NewCollectors(false, false, 0),
179-
})
180-
if err != nil {
181-
b.Fatal("Failed to create database")
182-
}
183-
benchPeer := storage.Peer{
184-
Complete: true,
185-
IP: testPeerIP,
186-
Port: 1234,
187-
}
188-
189-
b.SetParallelism(routines)
190-
b.ResetTimer()
191-
b.RunParallel(func(pb *testing.PB) {
192-
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
193-
var peerid storage.PeerID
194-
var hash storage.Hash
195-
196-
for pb.Next() {
197-
rnd.Read(peerid[:])
198-
rnd.Read(hash[:])
199-
db.PeerAdd(hash, peerid, benchPeer.IP, benchPeer.Port, benchPeer.Complete)
200-
}
201-
})
202-
})
203-
}
204-
}

storage/database/testdata_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package database
2+
3+
import (
4+
"net/netip"
5+
6+
"github.com/crimist/trakx/storage"
7+
)
8+
9+
var (
10+
testTorrentHash1 = storage.Hash([20]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
11+
testTorrentHash2 = storage.Hash([20]byte{1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
12+
testPeerIP = netip.AddrFrom4([4]byte{1, 2, 3, 4})
13+
testPeerID1 = storage.PeerID([20]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
14+
testPeerID2 = storage.PeerID([20]byte{1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
15+
)

0 commit comments

Comments
 (0)