| 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 |
| 6 | import { Equal, Hash } from "effect" |
| 7 | import { NetAddress } from "effect/net" |
| 8 | import { Bench } from "tinybench" |
| 9 | |
| 10 | const 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. |
| 17 | let sink = 0 |
| 18 | const consume = (value: unknown) => { |
| 19 | if (value === undefined) sink++ |
| 20 | } |
| 21 | |
| 22 | const ipv4String = "192.168.1.42" |
| 23 | const ipv4Octets = [192, 168, 1, 42] as const |
| 24 | const ipv4Bytes = new Uint8Array(ipv4Octets) |
| 25 | |
| 26 | const ipv6FullString = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" |
| 27 | const ipv6CompressedString = "2001:db8::8a2e:370:7334" |
| 28 | const ipv6MappedString = "::ffff:192.168.1.42" |
| 29 | const ipv6Segments = [0x2001, 0x0db8, 0x85a3, 0, 0, 0x8a2e, 0x0370, 0x7334] as const |
| 30 | const 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. |
| 34 | const ipv4 = NetAddress.ipv4FromBytesUnsafe(ipv4Bytes) |
| 35 | const ipv4Same = NetAddress.ipv4FromBytesUnsafe(ipv4Bytes) |
| 36 | const ipv4Other = NetAddress.ipv4FromBytesUnsafe(new Uint8Array([192, 168, 1, 43])) |
| 37 | const ipv6 = NetAddress.ipv6FromBytesUnsafe(ipv6Bytes) |
| 38 | const ipv6Same = NetAddress.ipv6FromBytesUnsafe(ipv6Bytes) |
| 39 | const ipv6OtherBytes = new Uint8Array(ipv6Bytes) |
| 40 | ipv6OtherBytes[15] ^= 1 |
| 41 | const 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. |
| 49 | const equalsMethod = (self: Equal.Equal, that: Equal.Equal) => self[Equal.symbol](that) |
| 50 | const hashMethod = (self: Hash.Hash) => self[Hash.symbol]() |
| 51 | |
| 52 | const bench = new Bench({ name: `NetAddress (${runtime})` }) |
| 53 | |
| 54 | bench |
| 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 | |
| 89 | await bench.run() |
| 90 | |
| 91 | console.log(bench.name) |
| 92 | console.table(bench.table()) |
| 93 | if (sink !== 0) console.log(sink) |