Skip to content

Commit 4939ef7

Browse files
authored
fix: improve kad distance list performance (#3009)
Refactors `addWithKadId` to splice the new entry into the list rather than adding it and then sorting.
1 parent ab1bb86 commit 4939ef7

File tree

4 files changed

+141
-5
lines changed

4 files changed

+141
-5
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/* eslint-disable no-console */
2+
import { randomBytes } from 'node:crypto'
3+
import { generateKeyPair } from '@libp2p/crypto/keys'
4+
import { peerIdFromPrivateKey } from '@libp2p/peer-id'
5+
import Benchmark from 'benchmark'
6+
import { sha256 } from 'multiformats/hashes/sha2'
7+
import { xor as uint8ArrayXor } from 'uint8arrays/xor'
8+
import { xorCompare as uint8ArrayXorCompare } from 'uint8arrays/xor-compare'
9+
import { convertPeerId } from '../dist/src/utils.js'
10+
11+
// Results - splicing is faster:
12+
// % node ./benchmarks/add-with-kad-id.js
13+
// addWithKadId sort x 285 ops/sec ±1.45% (88 runs sampled)
14+
// addWithKadId splice x 498 ops/sec ±1.36% (91 runs sampled)
15+
16+
// simulate roughly full routing table
17+
const peers = 6_000
18+
const capacity = 20
19+
const originDhtKey = (await sha256.digest(randomBytes(32))).digest
20+
const toAdd = await Promise.all(
21+
new Array(peers).fill(0).map(async () => {
22+
const privateKey = await generateKeyPair('Ed25519')
23+
const peerId = peerIdFromPrivateKey(privateKey)
24+
const kadId = await convertPeerId(peerId)
25+
26+
return {
27+
peerId,
28+
kadId
29+
}
30+
})
31+
)
32+
33+
const main = function () {
34+
let peerDistancesOrig
35+
let peerDistancesNew
36+
37+
const bench1 = new Benchmark('addWithKadId sort', {
38+
fn: function () {
39+
let peerDistances = []
40+
41+
for (let i = 0; i < toAdd.length; i++) {
42+
const { peerId, kadId } = toAdd[i]
43+
44+
if (peerDistances.find(pd => pd.peerId.equals(peerId)) != null) {
45+
continue
46+
}
47+
48+
const el = {
49+
peerId,
50+
distance: uint8ArrayXor(originDhtKey, kadId)
51+
}
52+
53+
peerDistances.push(el)
54+
peerDistances.sort((a, b) => uint8ArrayXorCompare(a.distance, b.distance))
55+
peerDistances = peerDistances.slice(0, capacity)
56+
}
57+
58+
peerDistancesOrig = peerDistances
59+
}
60+
})
61+
.on('complete', function (stats) {
62+
console.log(String(stats.currentTarget))
63+
})
64+
65+
bench1.run()
66+
67+
const bench2 = new Benchmark('addWithKadId splice', {
68+
fn: function () {
69+
let peerDistances = []
70+
71+
for (let i = 0; i < toAdd.length; i++) {
72+
const { peerId, kadId } = toAdd[i]
73+
74+
if (peerDistances.find(pd => pd.peerId.equals(peerId)) != null) {
75+
continue
76+
}
77+
78+
const el = {
79+
peerId,
80+
distance: uint8ArrayXor(originDhtKey, kadId)
81+
}
82+
83+
let added = false
84+
85+
for (let j = 0; j < peerDistances.length; j++) {
86+
const distance = uint8ArrayXorCompare(peerDistances[j].distance, el.distance)
87+
if (distance === 0 || distance === 1) {
88+
added = true
89+
peerDistances.splice(j, 0, el)
90+
break
91+
}
92+
}
93+
94+
if (!added) {
95+
peerDistances.push(el)
96+
}
97+
98+
peerDistances = peerDistances.slice(0, capacity)
99+
}
100+
101+
peerDistancesNew = peerDistances
102+
}
103+
})
104+
.on('complete', function (stats) {
105+
console.log(String(stats.currentTarget))
106+
107+
// make sure we have the same distance list
108+
if (peerDistancesOrig.length !== peerDistancesNew.length) {
109+
throw new Error(`Peer distances not equal (${peerDistancesOrig.length} vs ${peerDistancesNew.length})`)
110+
}
111+
112+
for (let i = 0; i < peerDistancesOrig.length; i++) {
113+
if (!peerDistancesOrig[i].peerId.equals(peerDistancesNew[i].peerId)) {
114+
throw new Error(`Peer distance ${i} not equal`)
115+
}
116+
}
117+
})
118+
119+
bench2.run()
120+
}
121+
122+
main()

packages/kad-dht/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
"@types/sinon": "^17.0.3",
9898
"@types/which": "^3.0.4",
9999
"aegir": "^45.1.1",
100+
"benchmark": "^2.1.4",
100101
"datastore-core": "^10.0.2",
101102
"delay": "^6.0.0",
102103
"execa": "^9.5.1",

packages/kad-dht/src/peer-distance-list.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,13 @@ export class PeerDistanceList {
5050
async add (peer: PeerInfo): Promise<void> {
5151
const dhtKey = await convertPeerId(peer.id)
5252

53-
this.addWitKadId(peer, dhtKey)
53+
this.addWithKadId(peer, dhtKey)
5454
}
5555

5656
/**
5757
* Add a peerId to the list.
5858
*/
59-
addWitKadId (peer: PeerInfo, kadId: Uint8Array): void {
59+
addWithKadId (peer: PeerInfo, kadId: Uint8Array): void {
6060
if (this.peerDistances.find(pd => pd.peer.id.equals(peer.id)) != null) {
6161
return
6262
}
@@ -66,8 +66,21 @@ export class PeerDistanceList {
6666
distance: uint8ArrayXor(this.originDhtKey, kadId)
6767
}
6868

69-
this.peerDistances.push(el)
70-
this.peerDistances.sort((a, b) => uint8ArrayXorCompare(a.distance, b.distance))
69+
let added = false
70+
71+
for (let j = 0; j < this.peerDistances.length; j++) {
72+
const distance = uint8ArrayXorCompare(this.peerDistances[j].distance, el.distance)
73+
if (distance === 0 || distance === 1) {
74+
added = true
75+
this.peerDistances.splice(j, 0, el)
76+
break
77+
}
78+
}
79+
80+
if (!added) {
81+
this.peerDistances.push(el)
82+
}
83+
7184
this.peerDistances = this.peerDistances.slice(0, this.capacity)
7285
}
7386

packages/kad-dht/src/routing-table/k-bucket.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ export class KBucket {
298298
const list = new PeerDistanceList(id, n)
299299

300300
for (const peer of this.toIterable()) {
301-
list.addWitKadId({ id: peer.peerId, multiaddrs: [] }, peer.kadId)
301+
list.addWithKadId({ id: peer.peerId, multiaddrs: [] }, peer.kadId)
302302
}
303303

304304
yield * map(list.peers, info => info.id)

0 commit comments

Comments
 (0)