Skip to content

Commit 5bdb111

Browse files
authored
Merge pull request #46 from lpgauth/bench-glazer-validate-utf8
Benchmark glazer with UTF-8 validation enabled
2 parents a7fd0d9 + 14a30fd commit 5bdb111

5 files changed

Lines changed: 69 additions & 48 deletions

File tree

README.md

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -202,62 +202,67 @@ Apple M2 Pro, OTP 29, Elixir 1.20. Both libraries are profile-guided
202202
optimised (PGO) builds: **Torque PGO** (via `scripts/pgo-build.sh`) and
203203
**Glazer PGO** (via `OPTIMIZE=1`).
204204

205+
glazer is benchmarked with UTF-8 validation enabled (`validate_utf8` on
206+
decode, `force_utf8` on encode — both off by default in glazer) so every
207+
library provides the same guarantee Torque always does: JSON strings are
208+
valid UTF-8.
209+
205210
### Decode (1.2 KB OpenRTB)
206211

207212
| Library | ips | mean | median | p99 | memory |
208213
|---|---|---|---|---|---|
209-
| **torque** | **413.3K** | **2.42 μs** | **2.29 μs** | **4.04 μs** | 1.56 KB |
210-
| **glazer** | 401.8K | 2.49 μs | 2.38 μs | 4.58 μs | 1.56 KB |
211-
| **jiffy** | 202.5K | 4.94 μs | 4.50 μs | 9.75 μs | **1.55 KB** |
212-
| **otp json** | 147.4K | 6.78 μs | 6.58 μs | 10.58 μs | 7.73 KB |
213-
| **jason** | 113.2K | 8.83 μs | 8.46 μs | 13.96 μs | 9.54 KB |
214+
| **torque** | **412.8K** | **2.42 μs** | **2.29 μs** | **4.67 μs** | 1.56 KB |
215+
| **glazer** | 355.5K | 2.81 μs | 2.67 μs | 5.00 μs | 1.56 KB |
216+
| **jiffy** | 200.0K | 5.00 μs | 4.50 μs | 11.00 μs | **1.55 KB** |
217+
| **otp json** | 143.8K | 6.95 μs | 6.71 μs | 12.54 μs | 7.73 KB |
218+
| **jason** | 109.0K | 9.17 μs | 8.54 μs | 20.58 μs | 9.54 KB |
214219

215220
### Decode (750 KB Twitter)
216221

217222
| Library | ips | mean | median | p99 | memory |
218223
|---|---|---|---|---|---|
219-
| **torque** | **660.7** | **1.51 ms** | **1.33 ms** | 2.21 ms | **1.57 KB** |
220-
| **glazer** | 659.6 | 1.52 ms | 1.43 ms | **1.94 ms** | 1.58 KB |
221-
| **jiffy** | 301.4 | 3.32 ms | 3.39 ms | 3.96 ms | 2.30 MB |
222-
| **otp json** | 205.3 | 4.87 ms | 4.80 ms | 7.52 ms | 2.48 MB |
223-
| **jason** | 151.7 | 6.59 ms | 6.58 ms | 6.97 ms | 3.52 MB |
224+
| **torque** | **659.4** | **1.52 ms** | **1.32 ms** | **2.14 ms** | **1.57 KB** |
225+
| **glazer** | 597.0 | 1.68 ms | 1.59 ms | 2.26 ms | 1.58 KB |
226+
| **jiffy** | 298.8 | 3.35 ms | 3.37 ms | 4.52 ms | 2.30 MB |
227+
| **otp json** | 211.3 | 4.73 ms | 4.77 ms | 5.57 ms | 2.48 MB |
228+
| **jason** | 150.2 | 6.66 ms | 6.59 ms | 8.21 ms | 3.54 MB |
224229

225230
### Encode (1.2 KB OpenRTB)
226231

227232
| Library | ips | mean | median | p99 | memory |
228233
|---|---|---|---|---|---|
229-
| **otp json** [map() :: iodata()] | **1180K** | **0.85 μs** | **0.79 μs** | **1.08 μs** | 3928 B |
230-
| **torque** [proplist() :: binary()] | 1110K | 0.90 μs | 0.83 μs | **1.08 μs** | 88 B |
231-
| **torque** [proplist() :: iodata()] | 1080K | 0.92 μs | 0.83 μs | 1.83 μs | **64 B** |
232-
| **torque** [map() :: iodata()] | 1000K | 1.00 μs | 0.96 μs | 1.13 μs | **64 B** |
233-
| **glazer** [map() :: binary()] | 990K | 1.01 μs | 0.92 μs | 1.29 μs | **64 B** |
234-
| **torque** [map() :: binary()] | 980K | 1.02 μs | 0.96 μs | 1.17 μs | 88 B |
235-
| **jiffy** [proplist() :: iodata()] | 640K | 1.57 μs | 1.33 μs | 1.83 μs | 120 B |
236-
| **jason** [map() :: iodata()] | 620K | 1.62 μs | 1.54 μs | 2.63 μs | 3848 B |
237-
| **jiffy** [map() :: iodata()] | 520K | 1.91 μs | 1.75 μs | 2.25 μs | 824 B |
238-
| **jason** [map() :: binary()] | 380K | 2.63 μs | 2.38 μs | 6.42 μs | 3912 B |
234+
| **otp json** [map() :: iodata()] | **1174K** | **0.85 μs** | **0.79 μs** | 1.21 μs | 3928 B |
235+
| **torque** [proplist() :: iodata()] | 1084K | 0.92 μs | 0.88 μs | **1.08 μs** | **64 B** |
236+
| **torque** [proplist() :: binary()] | 1048K | 0.95 μs | 0.88 μs | 1.21 μs | 88 B |
237+
| **torque** [map() :: iodata()] | 957K | 1.04 μs | 1.00 μs | 1.25 μs | **64 B** |
238+
| **torque** [map() :: binary()] | 953K | 1.05 μs | 1.00 μs | 1.21 μs | 88 B |
239+
| **glazer** [map() :: binary()] | 932K | 1.07 μs | 1.00 μs | 1.21 μs | **64 B** |
240+
| **jiffy** [proplist() :: iodata()] | 654K | 1.53 μs | 1.33 μs | 1.88 μs | 120 B |
241+
| **jason** [map() :: iodata()] | 598K | 1.67 μs | 1.54 μs | 3.21 μs | 3848 B |
242+
| **jiffy** [map() :: iodata()] | 526K | 1.90 μs | 1.75 μs | 2.17 μs | 824 B |
243+
| **jason** [map() :: binary()] | 401K | 2.49 μs | 2.33 μs | 4.63 μs | 3912 B |
239244

240245
### Encode (750 KB Twitter)
241246

242247
| Library | ips | mean | median | p99 | memory |
243248
|---|---|---|---|---|---|
244-
| **torque** [proplist() :: iodata()] | **1156.0** | **0.87 ms** | **0.85 ms** | **1.06 ms** | **64 B** |
245-
| **torque** [proplist() :: binary()] | 1154.1 | **0.87 ms** | **0.85 ms** | **1.06 ms** | 88 B |
246-
| **torque** [map() :: binary()] | 1045.4 | 0.96 ms | 0.95 ms | 1.13 ms | 88 B |
247-
| **torque** [map() :: iodata()] | 1040.4 | 0.96 ms | 0.96 ms | 1.13 ms | **64 B** |
248-
| **glazer** [map() :: binary()] | 1033.3 | 0.97 ms | 0.96 ms | 1.08 ms | **64 B** |
249-
| **jiffy** [proplist() :: iodata()] | 469.5 | 2.13 ms | 2.10 ms | 2.51 ms | 37.7 KB |
250-
| **jiffy** [map() :: iodata()] | 347.6 | 2.88 ms | 2.96 ms | 3.90 ms | 1.06 MB |
251-
| **otp json** [map() :: iodata()] | 251.8 | 3.97 ms | 4.13 ms | 6.44 ms | 5.40 MB |
252-
| **jason** [map() :: iodata()] | 233.5 | 4.28 ms | 4.03 ms | 6.50 ms | 4.96 MB |
253-
| **jason** [map() :: binary()] | 127.9 | 7.82 ms | 7.84 ms | 8.43 ms | 4.96 MB |
249+
| **torque** [proplist() :: iodata()] | **1163.7** | **0.86 ms** | **0.84 ms** | **1.08 ms** | **64 B** |
250+
| **torque** [proplist() :: binary()] | 1143.3 | 0.87 ms | **0.84 ms** | 1.44 ms | 88 B |
251+
| **torque** [map() :: binary()] | 1056.0 | 0.95 ms | 0.93 ms | 1.15 ms | 88 B |
252+
| **torque** [map() :: iodata()] | 1019.0 | 0.98 ms | 0.96 ms | 1.21 ms | **64 B** |
253+
| **glazer** [map() :: binary()] | 843.9 | 1.19 ms | 1.17 ms | 1.38 ms | **64 B** |
254+
| **jiffy** [proplist() :: iodata()] | 473.8 | 2.11 ms | 2.09 ms | 2.31 ms | 37.7 KB |
255+
| **jiffy** [map() :: iodata()] | 357.1 | 2.80 ms | 2.95 ms | 3.26 ms | 1.06 MB |
256+
| **otp json** [map() :: iodata()] | 270.5 | 3.70 ms | 3.93 ms | 4.77 ms | 5.40 MB |
257+
| **jason** [map() :: iodata()] | 261.0 | 3.83 ms | 3.54 ms | 5.86 ms | 4.96 MB |
258+
| **jason** [map() :: binary()] | 138.5 | 7.22 ms | 7.18 ms | 8.09 ms | 4.96 MB |
254259

255260
### Parse (1.2 KB OpenRTB)
256261

257262
| Library | ips | mean | median | p99 |
258263
|---|---|---|---|---|
259-
| **torque** parse(unique_keys) | **542.0K** | **1.84 μs** | 1.46 μs | **5.63 μs** |
260-
| **torque** parse | 522.8K | 1.91 μs | 1.46 μs | 6.00 μs |
264+
| **torque** parse(unique_keys) | **556.8K** | **1.80 μs** | 1.46 μs | **5.21 μs** |
265+
| **torque** parse | 555.4K | **1.80 μs** | **1.42 μs** | **5.21 μs** |
261266

262267
### Extract 5 fields from raw JSON (1.2 KB OpenRTB)
263268

@@ -268,10 +273,10 @@ selective extraction skips materializing the whole document.
268273

269274
| Library | ips | mean | median | p99 |
270275
|---|---|---|---|---|
271-
| **torque** parse + get_many | **436.6K** | **2.29 μs** | **1.79 μs** | **6.21 μs** |
272-
| **torque** parse(unique_keys) + get_many | 429.5K | 2.33 μs | **1.79 μs** | 6.67 μs |
273-
| **torque** parse + get x5 | 409.8K | 2.44 μs | 2.00 μs | 6.67 μs |
274-
| **glazer** decode + find x5 | 344.9K | 2.90 μs | 2.75 μs | 6.71 μs |
276+
| **torque** parse(unique_keys) + get_many | **467.3K** | **2.14 μs** | **1.79 μs** | **4.79 μs** |
277+
| **torque** parse + get_many | 455.1K | 2.20 μs | **1.79 μs** | 5.46 μs |
278+
| **torque** parse + get x5 | 420.1K | 2.38 μs | 1.96 μs | 6.08 μs |
279+
| **glazer** decode + find x5 | 315.1K | 3.17 μs | 3.04 μs | 4.88 μs |
275280

276281
Run benchmarks locally:
277282

bench/glazer_pgo_workload.exs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
# benchmark uses — keeping the glazer-vs-torque comparison apples-to-apples
77
# (both libraries profiled against equivalent decode/encode/find workloads).
88
#
9+
# Uses the same UTF-8 validation options as bench/torque_bench.exs
10+
# (`validate_utf8` decode, `force_utf8` encode) so the profile matches the
11+
# benchmarked configuration — keep them in sync.
12+
#
913
# Run with an instrumented glazer.so loaded: MIX_ENV=bench mix run this file.
1014

1115
small_json =
@@ -40,17 +44,17 @@ paths =
4044
IO.puts("glazer PGO workload: small=#{byte_size(small_json)}B large=#{byte_size(large_json)}B")
4145

4246
decode = fn ->
43-
:glazer_json.decode(small_json)
44-
:glazer_json.decode(large_json)
47+
:glazer_json.decode(small_json, [:validate_utf8])
48+
:glazer_json.decode(large_json, [:validate_utf8])
4549
end
4650

4751
encode = fn ->
48-
:glazer_json.encode(small_term)
49-
:glazer_json.encode(large_term)
52+
:glazer_json.encode(small_term, [:force_utf8])
53+
:glazer_json.encode(large_term, [:force_utf8])
5054
end
5155

5256
find = fn ->
53-
d = :glazer_json.decode(small_json)
57+
d = :glazer_json.decode(small_json, [:validate_utf8])
5458
Enum.each(paths, &:glazer.find(d, &1))
5559
end
5660

bench/index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,10 @@ <h1>Torque Benchmarks</h1>
108108
</p>
109109

110110
<h2>Library Comparison</h2>
111-
<p class="section-desc">Latest CI results — all libraries, sorted by throughput (higher is better).</p>
111+
<p class="section-desc">Latest CI results — all libraries, sorted by throughput (higher is better).
112+
glazer runs with UTF-8 validation enabled (<code>validate_utf8</code> on decode,
113+
<code>force_utf8</code> on encode; both default off in glazer) so all libraries
114+
provide the same guarantee Torque always does: JSON strings are valid UTF-8.</p>
112115
<div id="comparison"><div class="empty">Loading...</div></div>
113116

114117
<h2>Performance Over Time</h2>

bench/torque_bench.exs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
#
33
# Run with: MIX_ENV=bench mix run bench/torque_bench.exs
44
# CI JSON output: BENCH_OUTPUT=json MIX_ENV=bench mix run bench/torque_bench.exs
5+
#
6+
# glazer is benchmarked with UTF-8 validation enabled — `validate_utf8` on
7+
# decode, `force_utf8` on encode (both default OFF in glazer, needs >= 0.5.15)
8+
# — so every library in the comparison provides the guarantee Torque always
9+
# does: JSON strings are valid UTF-8. Keep these options in sync with
10+
# bench/glazer_pgo_workload.exs so glazer's PGO profile matches the
11+
# benchmarked configuration.
512

613
# CI formatter for github-action-benchmark (customBiggerIsBetter)
714
if System.get_env("BENCH_OUTPUT") == "json" do
@@ -306,7 +313,7 @@ IO.puts("=== DECODE BENCHMARK ===\n")
306313

307314
Benchee.run(
308315
%{
309-
"glazer decode" => fn -> :glazer_json.decode(sample_json) end,
316+
"glazer decode" => fn -> :glazer_json.decode(sample_json, [:validate_utf8]) end,
310317
"jason decode" => fn -> Jason.decode!(sample_json) end,
311318
"jiffy decode" => fn -> :jiffy.decode(sample_json, [:return_maps]) end,
312319
"otp json decode" => fn -> :json.decode(sample_json) end,
@@ -327,7 +334,7 @@ IO.puts("\n=== LARGE JSON DECODE BENCHMARK ===\n")
327334

328335
Benchee.run(
329336
%{
330-
"glazer decode" => fn -> :glazer_json.decode(large_json) end,
337+
"glazer decode" => fn -> :glazer_json.decode(large_json, [:validate_utf8]) end,
331338
"jason decode" => fn -> Jason.decode!(large_json) end,
332339
"jiffy decode" => fn -> :jiffy.decode(large_json, [:return_maps]) end,
333340
"otp json decode" => fn -> :json.decode(large_json) end,
@@ -355,7 +362,7 @@ Benchee.run(
355362
:jiffy.encode(bid_response_proplist, [:force_utf8])
356363
end,
357364
"otp json [map() :: iodata()]" => fn -> :json.encode(bid_response) end,
358-
"glazer [map() :: binary()]" => fn -> :glazer_json.encode(bid_response) end,
365+
"glazer [map() :: binary()]" => fn -> :glazer_json.encode(bid_response, [:force_utf8]) end,
359366
"torque [map() :: binary()]" => fn -> Torque.encode!(bid_response) end,
360367
"torque [map() :: iodata()]" => fn -> Torque.encode_to_iodata(bid_response) end,
361368
"torque [proplist() :: binary()]" => fn -> Torque.encode!(bid_response_proplist) end,
@@ -381,7 +388,9 @@ Benchee.run(
381388
"jiffy [map() :: iodata()]" => fn -> :jiffy.encode(large_decoded_json) end,
382389
"jiffy [proplist() :: iodata()]" => fn -> :jiffy.encode(large_decoded_proplist) end,
383390
"otp json [map() :: iodata()]" => fn -> :json.encode(large_decoded_json) end,
384-
"glazer [map() :: binary()]" => fn -> :glazer_json.encode(large_decoded_json) end,
391+
"glazer [map() :: binary()]" => fn ->
392+
:glazer_json.encode(large_decoded_json, [:force_utf8])
393+
end,
385394
"torque [map() :: binary()]" => fn -> Torque.encode!(large_decoded_json) end,
386395
"torque [map() :: iodata()]" => fn -> Torque.encode_to_iodata(large_decoded_json) end,
387396
"torque [proplist() :: binary()]" => fn -> Torque.encode!(large_decoded_proplist) end,
@@ -432,7 +441,7 @@ IO.puts("\n=== EXTRACT 5 FIELDS BENCHMARK ===\n")
432441
Benchee.run(
433442
%{
434443
"glazer decode + find x5" => fn ->
435-
d = :glazer_json.decode(sample_json)
444+
d = :glazer_json.decode(sample_json, [:validate_utf8])
436445
for p <- glazer_paths, do: :glazer.find(d, p)
437446
end,
438447
"torque parse + get x5" => fn ->

mix.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
77
"erlex": {:hex, :erlex, "0.2.9", "7debbbaa9f4f368b8cd648983e0f1d7963028508e9c59e9d4ed504e94ef52a55", [:mix], [], "hexpm", "8cfffc0ec7159e6d73de2ab28a588064de80f88b2798d5cbe4482cbbc200178b"},
88
"ex_doc": {:hex, :ex_doc, "0.40.3", "4a972ffe64bc07dc605af487e98fc19b72a4185f55ca031b94c0552d6071c1d9", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2756e357742fecd9749b489b85d67c9ce99c465f2e75728d9e6dc8d704b973de"},
9-
"glazer": {:hex, :glazer, "0.5.11", "c6f4bf32c3da539351d4e3811484ffe0ba341a871dc2ce47ddb5284c78f5166f", [:rebar3], [], "hexpm", "f5abec34de2f4cec226632f0db25567d4c6ffc0fd2e7ed5d285df81afb5582f8"},
9+
"glazer": {:hex, :glazer, "0.5.15", "cdbe06d3f82332575e3c10123989977e0d136241799d7292b657d2e01db89f1b", [:rebar3], [], "hexpm", "bc2cc19b63e459985029f995c96b29883e1b8d61383594b58dc8edb23718d314"},
1010
"jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},
1111
"jiffy": {:hex, :jiffy, "2.0.0", "1d7605ee31c4fd9b454725d744716b59e2abd88297862c8931b605c83b076480", [:rebar3], [], "hexpm", "73f58877ccaae14e14601c95928e2913fd1c1ef28e6ef4f274a8d62819f9e04f"},
1212
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},

0 commit comments

Comments
 (0)