1// Run from the repository root on each runtime:
2//
3// node packages/effect/benchmark/net/NetAddress.ts
4// bun packages/effect/benchmark/net/NetAddress.ts
5// deno run -A packages/effect/benchmark/net/NetAddress.ts
6import { Equal, Hash } from "effect"
7import { NetAddress } from "effect/net"
8import { Bench } from "tinybench"
9
10const runtime = (globalThis as any).Deno !== undefined
11 ? `deno ${(globalThis as any).Deno.version.deno}`
12 : (globalThis as any).Bun !== undefined
13 ? `bun ${(globalThis as any).Bun.version}`
14 : `node ${process.versions.node}`
15
16// Results are accumulated so the engine cannot drop the measured work.
17let sink = 0
18const consume = (value: unknown) => {
19 if (value === undefined) sink++
20}
21
22const ipv4String = "192.168.1.42"
23const ipv4Octets = [192, 168, 1, 42] as const
24const ipv4Bytes = new Uint8Array(ipv4Octets)
25
26const ipv6FullString = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
27const ipv6CompressedString = "2001:db8::8a2e:370:7334"
28const ipv6MappedString = "::ffff:192.168.1.42"
29const ipv6Segments = [0x2001, 0x0db8, 0x85a3, 0, 0, 0x8a2e, 0x0370, 0x7334] as const
30const ipv6Bytes = new Uint8Array(ipv6Segments.flatMap((segment) => [segment >> 8, segment & 0xff]))
31
32// Distinct objects with the same value, plus one differing only in the last
33// byte so the comparison has to check the whole address.
34const ipv4 = NetAddress.ipv4FromBytesUnsafe(ipv4Bytes)
35const ipv4Same = NetAddress.ipv4FromBytesUnsafe(ipv4Bytes)
36const ipv4Other = NetAddress.ipv4FromBytesUnsafe(new Uint8Array([192, 168, 1, 43]))
37const ipv6 = NetAddress.ipv6FromBytesUnsafe(ipv6Bytes)
38const ipv6Same = NetAddress.ipv6FromBytesUnsafe(ipv6Bytes)
39const ipv6OtherBytes = new Uint8Array(ipv6Bytes)
40ipv6OtherBytes[15] ^= 1
41const ipv6Other = NetAddress.ipv6FromBytesUnsafe(ipv6OtherBytes)
42
43// Equal.equals and Hash.hash cache results per object in WeakMaps, so calling
44// them repeatedly on the same addresses only measures the cache. The "method"
45// cases call the address implementations directly, which is the code the
46// storage change affects. The "public" cases build a fresh address each
47// iteration, so the first (uncached) call is measured; subtract the matching
48// "ipvN fromBytesUnsafe" case to isolate the call itself.
49const equalsMethod = (self: Equal.Equal, that: Equal.Equal) => self[Equal.symbol](that)
50const hashMethod = (self: Hash.Hash) => self[Hash.symbol]()
51
52const bench = new Bench({ name: `NetAddress (${runtime})` })
53
54bench
55 // Typed-array allocation cost, for comparison with Uint8Array-backed storage.
56 .add("baseline new Uint8Array(4)", () => consume(new Uint8Array(4)))
57 .add("baseline new Uint8Array(16)", () => consume(new Uint8Array(16)))
58 // IPv4 construction
59 .add("ipv4 fromString", () => consume(NetAddress.ipv4FromString(ipv4String)))
60 .add("ipv4 fromOctets", () => consume(NetAddress.ipv4FromOctets(ipv4Octets)))
61 .add("ipv4 fromBytesUnsafe", () => consume(NetAddress.ipv4FromBytesUnsafe(ipv4Bytes)))
62 .add("ip fromString (ipv4)", () => consume(NetAddress.ipFromString(ipv4String)))
63 // IPv6 construction
64 .add("ipv6 fromString (full)", () => consume(NetAddress.ipv6FromString(ipv6FullString)))
65 .add("ipv6 fromString (compressed)", () => consume(NetAddress.ipv6FromString(ipv6CompressedString)))
66 .add("ipv6 fromString (ipv4-mapped)", () => consume(NetAddress.ipv6FromString(ipv6MappedString)))
67 .add("ipv6 fromSegments", () => consume(NetAddress.ipv6FromSegments(ipv6Segments)))
68 .add("ipv6 fromBytesUnsafe", () => consume(NetAddress.ipv6FromBytesUnsafe(ipv6Bytes)))
69 .add("ip fromString (ipv6 compressed)", () => consume(NetAddress.ipFromString(ipv6CompressedString)))
70 // Equality and hashing on the address implementations
71 .add("ipv4 equals method (equal)", () => consume(equalsMethod(ipv4, ipv4Same)))
72 .add("ipv4 equals method (different)", () => consume(equalsMethod(ipv4, ipv4Other)))
73 .add("ipv4 hash method", () => consume(hashMethod(ipv4)))
74 .add("ipv6 equals method (equal)", () => consume(equalsMethod(ipv6, ipv6Same)))
75 .add("ipv6 equals method (different)", () => consume(equalsMethod(ipv6, ipv6Other)))
76 .add("ipv6 hash method", () => consume(hashMethod(ipv6)))
77 // Uncached public Equal.equals and Hash.hash, including construction
78 .add(
79 "ipv4 Equal.equals public (fresh)",
80 () => consume(Equal.equals(NetAddress.ipv4FromBytesUnsafe(ipv4Bytes), ipv4Same))
81 )
82 .add("ipv4 Hash.hash public (fresh)", () => consume(Hash.hash(NetAddress.ipv4FromBytesUnsafe(ipv4Bytes))))
83 .add(
84 "ipv6 Equal.equals public (fresh)",
85 () => consume(Equal.equals(NetAddress.ipv6FromBytesUnsafe(ipv6Bytes), ipv6Same))
86 )
87 .add("ipv6 Hash.hash public (fresh)", () => consume(Hash.hash(NetAddress.ipv6FromBytesUnsafe(ipv6Bytes))))
88
89await bench.run()
90
91console.log(bench.name)
92console.table(bench.table())
93if (sink !== 0) console.log(sink)