1import { Optic, Schema } from "effect"
2import { Bench } from "tinybench"
3
4// Batching bounds sample storage and keeps sub-microsecond timings above timer resolution.
5const batchSize = 1_000
6const bench = new Bench({
7 iterations: 1_000,
8 time: 0,
9 warmupIterations: 100,
10 warmupTime: 0,
11 timestampProvider: "hrtimeNow"
12})
13let sink: unknown
14
15const batch = <A>(run: () => A) => () => {
16 let value = run()
17 for (let index = 1; index < batchSize; index++) {
18 value = run()
19 }
20 sink = value
21}
22
23// Define a class with nested properties
24class User extends Schema.Class<User>("User")({
25 id: Schema.Number,
26 profile: Schema.Struct({
27 name: Schema.String,
28 email: Schema.String,
29 address: Schema.Struct({
30 street: Schema.String,
31 city: Schema.String,
32 country: Schema.String
33 })
34 })
35}) {}
36
37// Create a user instance
38const user = User.make({
39 id: 1,
40 profile: {
41 name: "John Doe",
42 email: "john@example.com",
43 address: {
44 street: "123 Main St",
45 city: "New York",
46 country: "USA"
47 }
48 }
49})
50
51const iso = Schema.toIso(User).key("profile").key("address").key("street")
52const optic = Optic.id<typeof User["Type"]>().key("profile").key("address").key("street")
53
54bench
55 .add("iso get", batch(() => iso.get(user)))
56 .add("optic get", batch(() => optic.get(user)))
57 .add("direct get", batch(() => user.profile.address.street))
58 .add("iso replace", batch(() => iso.replace("Updated", user)))
59 .add(
60 "direct replace",
61 batch(() =>
62 new User({
63 ...user,
64 profile: {
65 ...user.profile,
66 address: {
67 ...user.profile.address,
68 street: "Updated"
69 }
70 }
71 })
72 )
73 )
74
75await bench.run()
76
77if (sink === undefined) {
78 throw new Error("Benchmark did not run")
79}
80
81console.table(bench.table((task) => {
82 const result = task.result
83 if (result?.state === "errored") {
84 return {
85 "Task name": task.name,
86 Error: result.error.message
87 }
88 }
89 if (result?.state !== "completed") {
90 return {
91 "Task name": task.name,
92 State: result?.state ?? "missing result"
93 }
94 }
95 const latencyToNs = (value: number) => value * 1_000_000 / batchSize
96 return {
97 "Task name": task.name,
98 "Latency avg (ns/op)": latencyToNs(result.latency.mean).toFixed(2),
99 "Latency med (ns/op)": latencyToNs(result.latency.p50).toFixed(2),
100 "Latency RME": `${result.latency.rme.toFixed(2)}%`,
101 "Throughput avg (ops/s)": Math.round(result.throughput.mean * batchSize),
102 Samples: result.latency.samplesCount
103 }
104}))