OCaml wire format DSL with EverParse 3D output for verified parsers
at main 189 lines 5.6 kB view raw
1(** Micro-benchmark to identify Codec decode allocation sources. *) 2 3open Wire 4 5type r3 = { a : int; b : int; c : int } 6 7let codec3 = 8 let open Codec in 9 record "R3" (fun a b c -> { a; b; c }) 10 |+ field "a" uint16be (fun t -> t.a) 11 |+ field "b" uint16be (fun t -> t.b) 12 |+ field "c" uint16be (fun t -> t.c) 13 |> seal 14 15type r1 = { x : int } 16 17let codec1 = 18 let open Codec in 19 record "R1" (fun x -> { x }) |+ field "x" uint16be (fun t -> t.x) |> seal 20 21type r7 = { 22 f1 : int; 23 f2 : int; 24 f3 : int; 25 f4 : int; 26 f5 : int; 27 f6 : int; 28 f7 : int; 29} 30 31let codec7 = 32 let open Codec in 33 record "R7" (fun f1 f2 f3 f4 f5 f6 f7 -> { f1; f2; f3; f4; f5; f6; f7 }) 34 |+ field "f1" uint16be (fun t -> t.f1) 35 |+ field "f2" uint16be (fun t -> t.f2) 36 |+ field "f3" uint16be (fun t -> t.f3) 37 |+ field "f4" uint16be (fun t -> t.f4) 38 |+ field "f5" uint16be (fun t -> t.f5) 39 |+ field "f6" uint16be (fun t -> t.f6) 40 |+ field "f7" uint16be (fun t -> t.f7) 41 |> seal 42 43type r8 = { 44 g1 : int; 45 g2 : int; 46 g3 : int; 47 g4 : int; 48 g5 : int; 49 g6 : int; 50 g7 : int; 51 g8 : int; 52} 53 54let codec8 = 55 let open Codec in 56 record "R8" (fun g1 g2 g3 g4 g5 g6 g7 g8 -> 57 { g1; g2; g3; g4; g5; g6; g7; g8 }) 58 |+ field "g1" uint16be (fun t -> t.g1) 59 |+ field "g2" uint16be (fun t -> t.g2) 60 |+ field "g3" uint16be (fun t -> t.g3) 61 |+ field "g4" uint16be (fun t -> t.g4) 62 |+ field "g5" uint16be (fun t -> t.g5) 63 |+ field "g6" uint16be (fun t -> t.g6) 64 |+ field "g7" uint16be (fun t -> t.g7) 65 |+ field "g8" uint16be (fun t -> t.g8) 66 |> seal 67 68let buf6 = Bytes.create 6 69let buf2 = Bytes.create 2 70let buf14 = Bytes.create 14 71let buf16 = Bytes.create 16 72 73let measure name n f = 74 Gc.full_major (); 75 let before = (Gc.quick_stat ()).minor_words in 76 for _ = 1 to n do 77 f () 78 done; 79 let after = (Gc.quick_stat ()).minor_words in 80 let per_call = (after -. before) /. float_of_int n in 81 Fmt.pr " %-30s %6.1f words/call\n" name per_call 82 83let () = 84 let n = 1_000_000 in 85 Fmt.pr "Codec decode allocation breakdown (%d calls)\n\n" n; 86 87 Fmt.pr "Measurement baseline:\n"; 88 measure "noop" n (fun () -> ()); 89 measure "Bytes.length" n (fun () -> 90 let _ = Bytes.length buf6 in 91 ()); 92 93 Fmt.pr "\n1-field record (no intermediate closures):\n"; 94 measure "Codec.decode codec1" n (fun () -> 95 let _ = Codec.decode codec1 buf2 0 in 96 ()); 97 98 Fmt.pr "\n3-field record (2 intermediate closures):\n"; 99 measure "Codec.decode codec3" n (fun () -> 100 let _ = Codec.decode codec3 buf6 0 in 101 ()); 102 103 Fmt.pr "\nBaseline (hand-written, same record):\n"; 104 measure "hand-written decode" n (fun () -> 105 let a = Bytes.get_uint16_be buf6 0 in 106 let b = Bytes.get_uint16_be buf6 2 in 107 let c = Bytes.get_uint16_be buf6 4 in 108 let _ = { a; b; c } in 109 ()); 110 111 Fmt.pr "\n7-field record (chunked fallback, 1 PA):\n"; 112 measure "Codec.decode codec7" n (fun () -> 113 let _ = Codec.decode codec7 buf14 0 in 114 ()); 115 measure "hand-written 7-field" n (fun () -> 116 let f1 = Bytes.get_uint16_be buf14 0 in 117 let f2 = Bytes.get_uint16_be buf14 2 in 118 let f3 = Bytes.get_uint16_be buf14 4 in 119 let f4 = Bytes.get_uint16_be buf14 6 in 120 let f5 = Bytes.get_uint16_be buf14 8 in 121 let f6 = Bytes.get_uint16_be buf14 10 in 122 let f7 = Bytes.get_uint16_be buf14 12 in 123 let _ = { f1; f2; f3; f4; f5; f6; f7 } in 124 ()); 125 126 Fmt.pr "\n8-field record (chunked fallback, 1 PA):\n"; 127 measure "Codec.decode codec8" n (fun () -> 128 let _ = Codec.decode codec8 buf16 0 in 129 ()); 130 measure "hand-written 8-field" n (fun () -> 131 let g1 = Bytes.get_uint16_be buf16 0 in 132 let g2 = Bytes.get_uint16_be buf16 2 in 133 let g3 = Bytes.get_uint16_be buf16 4 in 134 let g4 = Bytes.get_uint16_be buf16 6 in 135 let g5 = Bytes.get_uint16_be buf16 8 in 136 let g6 = Bytes.get_uint16_be buf16 10 in 137 let g7 = Bytes.get_uint16_be buf16 12 in 138 let g8 = Bytes.get_uint16_be buf16 14 in 139 let _ = { g1; g2; g3; g4; g5; g6; g7; g8 } in 140 ()); 141 142 Fmt.pr "\nEncode:\n"; 143 let v = { a = 1; b = 2; c = 3 } in 144 measure "Codec.encode codec3" n (fun () -> Codec.encode codec3 v buf6 0); 145 measure "hand-written encode" n (fun () -> 146 Bytes.set_uint16_be buf6 0 v.a; 147 Bytes.set_uint16_be buf6 2 v.b; 148 Bytes.set_uint16_be buf6 4 v.c); 149 measure "Codec.encode codec3 + create" n (fun () -> 150 let b = Bytes.create 6 in 151 Codec.encode codec3 v b 0; 152 let _ = b in 153 ()); 154 155 Fmt.pr "\nInt32 boxing (CLCW-like):\n"; 156 let buf4 = Bytes.create 4 in 157 measure "Bytes.get_int32_be" n (fun () -> 158 let _ = Bytes.get_int32_be buf4 0 in 159 ()); 160 measure "byte-by-byte Int32" n (fun () -> 161 let b0 = Bytes.get_uint8 buf4 0 in 162 let b1 = Bytes.get_uint8 buf4 1 in 163 let b2 = Bytes.get_uint8 buf4 2 in 164 let b3 = Bytes.get_uint8 buf4 3 in 165 let _ = 166 Int32.of_int ((b0 lsl 24) lor (b1 lsl 16) lor (b2 lsl 8) lor b3) 167 in 168 ()); 169 170 Fmt.pr "\nUInt32 (unboxed on 64-bit):\n"; 171 measure "Wire.UInt32.get_be" n (fun () -> 172 let _ = Wire.UInt32.get_be buf4 0 in 173 ()); 174 measure "byte-by-byte int" n (fun () -> 175 let b0 = Bytes.get_uint8 buf4 0 in 176 let b1 = Bytes.get_uint8 buf4 1 in 177 let b2 = Bytes.get_uint8 buf4 2 in 178 let b3 = Bytes.get_uint8 buf4 3 in 179 let _ = (b0 lsl 24) lor (b1 lsl 16) lor (b2 lsl 8) lor b3 in 180 ()); 181 182 Fmt.pr "\nUInt63 (unboxed on 64-bit):\n"; 183 let buf8 = Bytes.create 8 in 184 measure "Bytes.get_int64_be (boxed)" n (fun () -> 185 let _ = Bytes.get_int64_be buf8 0 in 186 ()); 187 measure "Wire.UInt63.get_be" n (fun () -> 188 let _ = Wire.UInt63.get_be buf8 0 in 189 ())