Skip to content

Commit a6f48f4

Browse files
committed
feat: compare component codes instead of strings
Switches matcher implementation to use multiaddr components and compare the codes rather than encoding/decoding strings which leads to a 2x speed up.
1 parent 4d5197a commit a6f48f4

File tree

7 files changed

+188
-108
lines changed

7 files changed

+188
-108
lines changed

benchmarks/operations/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# multiaddr Benchmark
2+
3+
Benchmarks multiaddr performance during common operations - parsing strings,
4+
encapsulating/decapsulating addresses, turning to bytes, decoding bytes, etc.
5+
6+
## Running the benchmarks
7+
8+
```console
9+
% npm start
10+
11+
12+
> npm run build && node dist/src/index.js
13+
14+
15+
16+
> aegir build --bundle false
17+
18+
[06:10:56] tsc [started]
19+
[06:10:56] tsc [completed]
20+
┌─────────┬─────────────────────────────────────────┬──────────────┬────────┬───────┬────────┐
21+
│ (index) │ Implementation │ ops/s │ ms/op │ runs │ p99 │
22+
├─────────┼─────────────────────────────────────────┼──────────────┼────────┼───────┼────────┤
23+
│ 0 │ 'head' │ '2427997.80' │ '0.00' │ 50000 │ '0.00' │
24+
│ 1 │ '@multiformats/multiaddr-matcher-1.7.2' │ '1098132.24' │ '0.00' │ 50000 │ '0.00' │
25+
└─────────┴─────────────────────────────────────────┴──────────────┴────────┴───────┴────────┘
26+
```

benchmarks/operations/package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "benchmarks-operations",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"private": true,
6+
"type": "module",
7+
"scripts": {
8+
"clean": "aegir clean",
9+
"build": "aegir build --bundle false",
10+
"lint": "aegir lint",
11+
"dep-check": "aegir dep-check",
12+
"doc-check": "aegir doc-check",
13+
"start": "npm run build && node dist/test/index.js"
14+
},
15+
"devDependencies": {
16+
"@multiformats/multiaddr-matcher-1.7.2": "npm:@multiformats/[email protected]",
17+
"@multiformats/multiaddr-matcher": "../../",
18+
"aegir": "^47.0.7",
19+
"tinybench": "^4.0.1"
20+
}
21+
}

benchmarks/operations/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* eslint-disable no-console */
2+
3+
import { multiaddr } from '@multiformats/multiaddr'
4+
import { TCP } from '@multiformats/multiaddr-matcher'
5+
import { TCP as TCP_172 } from '@multiformats/multiaddr-matcher-1.7.2'
6+
import { Bench } from 'tinybench'
7+
8+
const ITERATIONS = parseInt(process.env.ITERATIONS ?? '50000')
9+
const MIN_TIME = parseInt(process.env.MIN_TIME ?? '1')
10+
const RESULT_PRECISION = 2
11+
12+
function bench (m: typeof TCP | typeof TCP_172): void {
13+
const ma = multiaddr('/ip4/127.0.0.1/tcp/1234')
14+
15+
m.exactMatch(ma)
16+
}
17+
18+
async function main (): Promise<void> {
19+
const suite = new Bench({
20+
iterations: ITERATIONS,
21+
time: MIN_TIME
22+
})
23+
suite.add('head', () => {
24+
bench(TCP)
25+
})
26+
suite.add('@multiformats/multiaddr-matcher-1.7.2', () => {
27+
bench(TCP_172)
28+
})
29+
30+
await suite.run()
31+
32+
console.table(suite.tasks.map(({ name, result }) => {
33+
if (result?.error != null) {
34+
console.error(result.error)
35+
36+
return {
37+
Implementation: name,
38+
'ops/s': 'error',
39+
'ms/op': 'error',
40+
runs: 'error',
41+
p99: 'error'
42+
}
43+
}
44+
45+
return {
46+
Implementation: name,
47+
'ops/s': result?.hz.toFixed(RESULT_PRECISION),
48+
'ms/op': result?.period.toFixed(RESULT_PRECISION),
49+
runs: result?.samples.length,
50+
p99: result?.p99.toFixed(RESULT_PRECISION)
51+
}
52+
}))
53+
}
54+
55+
main().catch(err => {
56+
console.error(err)
57+
process.exit(1)
58+
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"extends": "aegir/src/config/tsconfig.aegir.json",
3+
"compilerOptions": {
4+
"outDir": "dist"
5+
},
6+
"include": [
7+
"src",
8+
"test"
9+
],
10+
"references": [
11+
{
12+
"path": "../../"
13+
}
14+
]
15+
}

src/index.ts

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@
3333
*/
3434

3535
import { isIPv4, isIPv6 } from '@chainsafe/is-ip'
36-
import { and, or, literal, string, peerId, optional, fmt, func, number, certhash } from './utils.js'
37-
import type { Multiaddr } from '@multiformats/multiaddr'
36+
import { and, or, optional, fmt, func, code, value } from './utils.js'
37+
import { type Multiaddr, type Component, CODE_P2P, CODE_DNS4, CODE_DNS6, CODE_DNSADDR, CODE_DNS, CODE_IP4, CODE_IP6, CODE_TCP, CODE_UDP, CODE_QUIC, CODE_QUIC_V1, CODE_WS, CODE_WSS, CODE_TLS, CODE_SNI, CODE_WEBRTC_DIRECT, CODE_CERTHASH, CODE_WEBTRANSPORT, CODE_P2P_CIRCUIT, CODE_WEBRTC, CODE_HTTP, CODE_UNIX, CODE_HTTPS, CODE_MEMORY } from '@multiformats/multiaddr'
3838

3939
/**
4040
* A matcher accepts multiaddr components and either fails to match and returns
4141
* false or returns a sublist of unmatched components
4242
*/
4343
export interface Matcher {
44-
match(parts: string[]): string[] | false
44+
match(parts: Component[]): Component[] | false
4545
pattern: string
4646
}
4747

@@ -82,17 +82,17 @@ export interface MultiaddrMatcher {
8282
* PEER_ID.matches(multiaddr('/ipfs/Qmfoo')) // true
8383
* ```
8484
*/
85-
const _PEER_ID = peerId()
85+
const _PEER_ID = value(CODE_P2P)
8686

8787
export const PEER_ID = fmt(_PEER_ID)
8888

8989
/**
9090
* DNS matchers
9191
*/
92-
const _DNS4 = and(literal('dns4'), string())
93-
const _DNS6 = and(literal('dns6'), string())
94-
const _DNSADDR = and(literal('dnsaddr'), string())
95-
const _DNS = and(literal('dns'), string())
92+
const _DNS4 = value(CODE_DNS4)
93+
const _DNS6 = value(CODE_DNS6)
94+
const _DNSADDR = value(CODE_DNSADDR)
95+
const _DNS = value(CODE_DNS)
9696

9797
/**
9898
* Matches dns4 addresses.
@@ -108,7 +108,7 @@ const _DNS = and(literal('dns'), string())
108108
* DNS4.matches(multiaddr('/dns4/example.org')) // true
109109
* ```
110110
*/
111-
export const DNS4 = fmt(_DNS4, optional(peerId()))
111+
export const DNS4 = fmt(_DNS4, optional(value(CODE_P2P)))
112112

113113
/**
114114
* Matches dns6 addresses.
@@ -124,7 +124,7 @@ export const DNS4 = fmt(_DNS4, optional(peerId()))
124124
* DNS6.matches(multiaddr('/dns6/example.org')) // true
125125
* ```
126126
*/
127-
export const DNS6 = fmt(_DNS6, optional(peerId()))
127+
export const DNS6 = fmt(_DNS6, optional(value(CODE_P2P)))
128128

129129
/**
130130
* Matches dnsaddr addresses.
@@ -141,7 +141,7 @@ export const DNS6 = fmt(_DNS6, optional(peerId()))
141141
* DNSADDR.matches(multiaddr('/dnsaddr/example.org/p2p/Qmfoo')) // true
142142
* ```
143143
*/
144-
export const DNSADDR = fmt(_DNSADDR, optional(peerId()))
144+
export const DNSADDR = fmt(_DNSADDR, optional(value(CODE_P2P)))
145145

146146
/**
147147
* Matches any dns address.
@@ -158,10 +158,10 @@ export const DNSADDR = fmt(_DNSADDR, optional(peerId()))
158158
* DNS.matches(multiaddr('/dns6/example.org/p2p/Qmfoo')) // true
159159
* ```
160160
*/
161-
export const DNS = fmt(or(_DNS, _DNSADDR, _DNS4, _DNS6), optional(peerId()))
161+
export const DNS = fmt(or(_DNS, _DNSADDR, _DNS4, _DNS6), optional(value(CODE_P2P)))
162162

163-
const _IP4 = and(literal('ip4'), func(isIPv4))
164-
const _IP6 = and(literal('ip6'), func(isIPv6))
163+
const _IP4 = value(CODE_IP4)
164+
const _IP6 = value(CODE_IP6)
165165
const _IP = or(_IP4, _IP6)
166166

167167
const _IP_OR_DOMAIN = or(_IP, _DNS, _DNS4, _DNS6, _DNSADDR)
@@ -181,7 +181,7 @@ const _IP_OR_DOMAIN = or(_IP, _DNS, _DNS4, _DNS6, _DNSADDR)
181181
* IP_OR_DOMAIN.matches(multiaddr('/p2p/QmFoo')) // false
182182
* ```
183183
*/
184-
export const IP_OR_DOMAIN = fmt(or(_IP, and(or(_DNS, _DNSADDR, _DNS4, _DNS6), optional(peerId()))))
184+
export const IP_OR_DOMAIN = fmt(or(_IP, and(or(_DNS, _DNSADDR, _DNS4, _DNS6), optional(value(CODE_P2P)))))
185185

186186
/**
187187
* Matches ip4 addresses.
@@ -234,8 +234,8 @@ export const IP6 = fmt(_IP6)
234234
*/
235235
export const IP = fmt(_IP)
236236

237-
const _TCP = and(_IP_OR_DOMAIN, literal('tcp'), number())
238-
const _UDP = and(_IP_OR_DOMAIN, literal('udp'), number())
237+
const _TCP = and(_IP_OR_DOMAIN, value(CODE_TCP))
238+
const _UDP = and(_IP_OR_DOMAIN, value(CODE_UDP))
239239

240240
/**
241241
* Matches TCP addresses.
@@ -249,7 +249,7 @@ const _UDP = and(_IP_OR_DOMAIN, literal('udp'), number())
249249
* TCP.matches(multiaddr('/ip4/123.123.123.123/tcp/1234')) // true
250250
* ```
251251
*/
252-
export const TCP = fmt(and(_TCP, optional(peerId())))
252+
export const TCP = fmt(and(_TCP, optional(value(CODE_P2P))))
253253

254254
/**
255255
* Matches UDP addresses.
@@ -265,10 +265,10 @@ export const TCP = fmt(and(_TCP, optional(peerId())))
265265
*/
266266
export const UDP = fmt(_UDP)
267267

268-
const _QUIC = and(_UDP, literal('quic'), optional(peerId()))
269-
const _QUICV1 = and(_UDP, literal('quic-v1'), optional(peerId()))
268+
const _QUIC = and(_UDP, code(CODE_QUIC), optional(value(CODE_P2P)))
269+
const _QUIC_V1 = and(_UDP, code(CODE_QUIC_V1), optional(value(CODE_P2P)))
270270

271-
const QUIC_V0_OR_V1 = or(_QUIC, _QUICV1)
271+
const QUIC_V0_OR_V1 = or(_QUIC, _QUIC_V1)
272272

273273
/**
274274
* Matches QUIC addresses.
@@ -296,18 +296,19 @@ export const QUIC = fmt(_QUIC)
296296
* QUICV1.matches(multiaddr('/ip4/123.123.123.123/udp/1234/quic-v1')) // true
297297
* ```
298298
*/
299-
export const QUICV1 = fmt(_QUICV1)
299+
export const QUIC_V1 = fmt(_QUIC_V1)
300+
export const QUICV1 = QUIC_V1
300301

301302
const _WEB = or(
302303
_IP_OR_DOMAIN,
303304
_TCP,
304305
_UDP,
305306
_QUIC,
306-
_QUICV1
307+
_QUIC_V1
307308
)
308309

309310
const _WebSockets = or(
310-
and(_WEB, literal('ws'), optional(peerId()))
311+
and(_WEB, code(CODE_WS), optional(value(CODE_P2P)))
311312
)
312313

313314
/**
@@ -325,8 +326,8 @@ const _WebSockets = or(
325326
export const WebSockets = fmt(_WebSockets)
326327

327328
const _WebSocketsSecure = or(
328-
and(_WEB, literal('wss'), optional(peerId())),
329-
and(_WEB, literal('tls'), optional(and(literal('sni'), string())), literal('ws'), optional(peerId()))
329+
and(_WEB, code(CODE_WSS), optional(value(CODE_P2P))),
330+
and(_WEB, code(CODE_TLS), optional(value(CODE_SNI)), code(CODE_WS), optional(value(CODE_P2P)))
330331
)
331332

332333
/**
@@ -343,7 +344,7 @@ const _WebSocketsSecure = or(
343344
*/
344345
export const WebSocketsSecure = fmt(_WebSocketsSecure)
345346

346-
const _WebRTCDirect = and(_UDP, literal('webrtc-direct'), optional(certhash()), optional(certhash()), optional(peerId()))
347+
const _WebRTCDirect = and(_UDP, code(CODE_WEBRTC_DIRECT), optional(value(CODE_CERTHASH)), optional(value(CODE_CERTHASH)), optional(value(CODE_P2P)))
347348

348349
/**
349350
* Matches WebRTC-direct addresses.
@@ -359,7 +360,7 @@ const _WebRTCDirect = and(_UDP, literal('webrtc-direct'), optional(certhash()),
359360
*/
360361
export const WebRTCDirect = fmt(_WebRTCDirect)
361362

362-
const _WebTransport = and(_QUICV1, literal('webtransport'), optional(certhash()), optional(certhash()), optional(peerId()))
363+
const _WebTransport = and(_QUIC_V1, code(CODE_WEBTRANSPORT), optional(value(CODE_CERTHASH)), optional(value(CODE_CERTHASH)), optional(value(CODE_P2P)))
363364

364365
/**
365366
* Matches WebTransport addresses.
@@ -378,12 +379,12 @@ export const WebTransport = fmt(_WebTransport)
378379
const _P2P = or(
379380
_WebSockets,
380381
_WebSocketsSecure,
381-
and(_TCP, optional(peerId())),
382-
and(QUIC_V0_OR_V1, optional(peerId())),
383-
and(_IP_OR_DOMAIN, optional(peerId())),
382+
and(_TCP, optional(value(CODE_P2P))),
383+
and(QUIC_V0_OR_V1, optional(value(CODE_P2P))),
384+
and(_IP_OR_DOMAIN, optional(value(CODE_P2P))),
384385
_WebRTCDirect,
385386
_WebTransport,
386-
peerId()
387+
value(CODE_P2P)
387388
)
388389

389390
/**
@@ -400,7 +401,7 @@ const _P2P = or(
400401
*/
401402
export const P2P = fmt(_P2P)
402403

403-
const _Circuit = and(_P2P, literal('p2p-circuit'), peerId())
404+
const _Circuit = and(_P2P, code(CODE_P2P_CIRCUIT), value(CODE_P2P))
404405

405406
/**
406407
* Matches circuit relay addresses
@@ -417,9 +418,9 @@ const _Circuit = and(_P2P, literal('p2p-circuit'), peerId())
417418
export const Circuit = fmt(_Circuit)
418419

419420
const _WebRTC = or(
420-
and(_P2P, literal('p2p-circuit'), literal('webrtc'), optional(peerId())),
421-
and(_P2P, literal('webrtc'), optional(peerId())),
422-
and(literal('webrtc'), optional(peerId()))
421+
and(_P2P, code(CODE_P2P_CIRCUIT), code(CODE_WEBRTC), optional(value(CODE_P2P))),
422+
and(_P2P, code(CODE_WEBRTC), optional(value(CODE_P2P))),
423+
and(code(CODE_WEBRTC), optional(value(CODE_P2P)))
423424
)
424425

425426
/**
@@ -437,8 +438,8 @@ const _WebRTC = or(
437438
export const WebRTC = fmt(_WebRTC)
438439

439440
const _HTTP = or(
440-
and(_IP_OR_DOMAIN, literal('tcp'), number(), literal('http'), optional(peerId())),
441-
and(_IP_OR_DOMAIN, literal('http'), optional(peerId()))
441+
and(_IP_OR_DOMAIN, value(CODE_TCP), code(CODE_HTTP), optional(value(CODE_P2P))),
442+
and(_IP_OR_DOMAIN, code(CODE_HTTP), optional(value(CODE_P2P)))
442443
)
443444

444445
/**
@@ -455,14 +456,15 @@ const _HTTP = or(
455456
*/
456457
export const HTTP = fmt(_HTTP)
457458

458-
const _HTTPS = or(
459-
and(_IP_OR_DOMAIN, literal('tcp'), or(
460-
and(literal('443'), literal('http')),
461-
and(number(), literal('https')),
462-
and(number(), literal('tls'), literal('http'))
463-
), optional(peerId())),
464-
and(_IP_OR_DOMAIN, literal('tls'), literal('http'), optional(peerId())),
465-
and(_IP_OR_DOMAIN, literal('https'), optional(peerId()))
459+
const _HTTPS = and(_IP_OR_DOMAIN, or(
460+
and(value(CODE_TCP, '443'), code(CODE_HTTP)),
461+
and(value(CODE_TCP), code(CODE_HTTPS)),
462+
and(value(CODE_TCP), code(CODE_TLS), code(CODE_HTTP)),
463+
and(code(CODE_TLS), code(CODE_HTTP)),
464+
code(CODE_TLS),
465+
code(CODE_HTTPS)
466+
),
467+
optional(value(CODE_P2P))
466468
)
467469

468470
/**
@@ -480,7 +482,7 @@ const _HTTPS = or(
480482
export const HTTPS = fmt(_HTTPS)
481483

482484
const _Memory = or(
483-
and(literal('memory'), string(), optional(peerId()))
485+
and(value(CODE_MEMORY), optional(value(CODE_P2P)))
484486
)
485487

486488
/**
@@ -498,7 +500,7 @@ const _Memory = or(
498500
export const Memory = fmt(_Memory)
499501

500502
const _Unix = or(
501-
and(literal('unix'), string(), optional(peerId()))
503+
and(value(CODE_UNIX), optional(value(CODE_P2P)))
502504
)
503505

504506
/**

0 commit comments

Comments
 (0)