Skip to content

Commit 6857815

Browse files
authored
Merge pull request #10 from lpgauth/upkeep/perf
Optimize NIF: panic=abort, if-let number dispatch, tilde-escape stack…
2 parents eb1f41c + 4773560 commit 6857815

6 files changed

Lines changed: 416 additions & 18 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ members = ["native/torque_nif"]
66
opt-level = 3
77
lto = "fat"
88
codegen-units = 1
9+
panic = "abort"

bench/torque_only_bench.exs

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
# Torque-only benchmark (skips simdjsone, jiffy, jason, otp json)
2+
#
3+
# Run with: MIX_ENV=bench mix run bench/torque_only_bench.exs
4+
5+
sample_json =
6+
Jason.encode!(%{
7+
"id" => "req-#{:rand.uniform(1_000_000)}",
8+
"site" => %{
9+
"domain" => "example.com",
10+
"page" => "https://example.com/articles/some-article-title",
11+
"ref" => "https://google.com/search?q=something",
12+
"publisher" => %{"id" => "pub-12345"},
13+
"cat" => ["IAB1", "IAB2-3"]
14+
},
15+
"device" => %{
16+
"devicetype" => 2,
17+
"ua" =>
18+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
19+
"ip" => "203.0.113.42",
20+
"ipv6" => "2001:db8::1",
21+
"ifa" => "cdda802e-fb9c-47ad-0794d394fbbd",
22+
"os" => "Apple iOS",
23+
"geo" => %{
24+
"country" => "US",
25+
"lat" => 40.7128,
26+
"lon" => -74.006,
27+
"region" => "NY",
28+
"type" => 2,
29+
"zip" => "10001"
30+
},
31+
"connectiontype" => 2,
32+
"carrier" => "Verizon",
33+
"language" => "en"
34+
},
35+
"user" => %{
36+
"id" => "user-abcdef123456",
37+
"buyeruid" => "buyer-xyz789",
38+
"ext" => %{
39+
"eids" => [
40+
%{"source" => "adserver.org", "uids" => [%{"id" => "uid-tdid-1234"}]},
41+
%{"source" => "criteo.com", "uids" => [%{"id" => "uid-criteo-5678"}]},
42+
%{"source" => "uidapi.com", "uids" => [%{"id" => "uid2-raw-token-abcdef"}]}
43+
]
44+
}
45+
},
46+
"imp" => [
47+
%{
48+
"id" => "imp-1",
49+
"banner" => %{"w" => 300, "h" => 250, "pos" => 1},
50+
"bidfloor" => 0.5,
51+
"pmp" => %{
52+
"private_auction" => 1,
53+
"deals" => [
54+
%{"id" => "deal-abc", "bidfloor" => 1.0},
55+
%{"id" => "deal-def", "bidfloor" => 0.75}
56+
]
57+
}
58+
},
59+
%{
60+
"id" => "imp-2",
61+
"video" => %{
62+
"mimes" => ["video/mp4", "video/webm"],
63+
"minduration" => 5,
64+
"maxduration" => 30,
65+
"protocols" => [2, 5],
66+
"placement" => 1
67+
},
68+
"bidfloor" => 2.0
69+
}
70+
],
71+
"regs" => %{"coppa" => 0},
72+
"ext" => %{"appnexus" => %{"seller_member_id" => 1410}}
73+
})
74+
75+
IO.puts("JSON payload size: #{byte_size(sample_json)} bytes\n")
76+
77+
fields = [
78+
"/id",
79+
"/site/domain",
80+
"/site/page",
81+
"/site/ref",
82+
"/site/publisher/id",
83+
"/site/cat",
84+
"/device/devicetype",
85+
"/device/ua",
86+
"/device/ip",
87+
"/device/ipv6",
88+
"/device/ifa",
89+
"/device/os",
90+
"/device/geo/country",
91+
"/device/geo/lat",
92+
"/device/geo/lon",
93+
"/device/geo/region",
94+
"/device/geo/type",
95+
"/device/geo/zip",
96+
"/device/connectiontype",
97+
"/device/carrier",
98+
"/device/language",
99+
"/user/id",
100+
"/user/buyeruid",
101+
"/user/ext/eids",
102+
"/imp",
103+
"/regs/coppa"
104+
]
105+
106+
bid_response = %{
107+
id: "req-123",
108+
cur: "USD",
109+
seatbid: [
110+
%{
111+
seat: "458",
112+
bid: [
113+
%{
114+
id: "bid-abc",
115+
impid: "imp-1",
116+
price: 1.5,
117+
adomain: ["advertiser.com"],
118+
adm: "<script src=\"https://tracker.example.com/imp?id=123\"></script>",
119+
cid: "campaign-1",
120+
crid: "creative-1",
121+
burl: "https://tracker.example.com/win?id=123&price=${AUCTION_PRICE}",
122+
iurl: "https://cdn.example.com/preview.jpg"
123+
}
124+
]
125+
}
126+
],
127+
ext: %{protocol: "5.3"}
128+
}
129+
130+
bid_response_proplist =
131+
{[
132+
{:id, "req-123"},
133+
{:cur, "USD"},
134+
{:seatbid,
135+
[
136+
{[
137+
{:seat, "458"},
138+
{:bid,
139+
[
140+
{[
141+
{:id, "bid-abc"},
142+
{:impid, "imp-1"},
143+
{:price, 1.5},
144+
{:adomain, ["advertiser.com"]},
145+
{:adm, "<script src=\"https://tracker.example.com/imp?id=123\"></script>"},
146+
{:cid, "campaign-1"},
147+
{:crid, "creative-1"},
148+
{:burl, "https://tracker.example.com/win?id=123&price=${AUCTION_PRICE}"},
149+
{:iurl, "https://cdn.example.com/preview.jpg"}
150+
]}
151+
]}
152+
]}
153+
]}
154+
]}
155+
156+
IO.puts("=== DECODE BENCHMARK ===\n")
157+
158+
Benchee.run(
159+
%{
160+
"torque decode" => fn -> Torque.decode!(sample_json) end
161+
},
162+
warmup: 2,
163+
time: 5,
164+
memory_time: 2,
165+
percentiles: [50, 95, 99],
166+
formatters: [
167+
{Benchee.Formatters.Console, percentiles: [50, 95, 99]}
168+
]
169+
)
170+
171+
IO.puts("\n=== PARSE + GET BENCHMARK ===\n")
172+
173+
Benchee.run(
174+
%{
175+
"torque parse+get" => fn ->
176+
{:ok, doc} = Torque.parse(sample_json)
177+
for f <- fields, do: Torque.get(doc, f)
178+
end,
179+
"torque parse+get_many" => fn ->
180+
{:ok, doc} = Torque.parse(sample_json)
181+
Torque.get_many(doc, fields)
182+
end,
183+
"torque parse+get_many_nil" => fn ->
184+
{:ok, doc} = Torque.parse(sample_json)
185+
Torque.get_many_nil(doc, fields)
186+
end
187+
},
188+
warmup: 2,
189+
time: 5,
190+
memory_time: 2,
191+
percentiles: [50, 95, 99],
192+
formatters: [
193+
{Benchee.Formatters.Console, percentiles: [50, 95, 99]}
194+
]
195+
)
196+
197+
IO.puts("\n=== ENCODE BENCHMARK ===\n")
198+
199+
Benchee.run(
200+
%{
201+
"torque: map => binary" => fn -> Torque.encode!(bid_response) end,
202+
"torque: proplist => binary" => fn -> Torque.encode!(bid_response_proplist) end,
203+
"torque: map => iodata" => fn -> Torque.encode_to_iodata(bid_response) end,
204+
"torque: proplist => iodata" => fn -> Torque.encode_to_iodata(bid_response_proplist) end
205+
},
206+
warmup: 2,
207+
time: 5,
208+
memory_time: 2,
209+
percentiles: [50, 95, 99],
210+
formatters: [
211+
{Benchee.Formatters.Console, percentiles: [50, 95, 99]}
212+
]
213+
)
214+
215+
IO.puts("\n=== LARGE JSON DECODE BENCHMARK ===\n")
216+
217+
large_json =
218+
Jason.encode!(%{
219+
"statuses" =>
220+
Enum.map(1..320, fn i ->
221+
uid = rem(i, 200)
222+
223+
%{
224+
"metadata" => %{"result_type" => "recent", "iso_language_code" => "en"},
225+
"created_at" => "Sun Aug 31 00:29:15 +0000 2014",
226+
"id" => 505_874_924_000_000_000 + i,
227+
"id_str" => Integer.to_string(505_874_924_000_000_000 + i),
228+
"text" =>
229+
"Sample tweet #{i} #elixir #benchmark @user_#{uid} lorem ipsum dolor sit amet " <>
230+
"consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
231+
"source" =>
232+
"<a href=\"http://twitter.com/download/iphone\" rel=\"nofollow\">Twitter for iPhone</a>",
233+
"truncated" => false,
234+
"in_reply_to_status_id" => nil,
235+
"in_reply_to_status_id_str" => nil,
236+
"in_reply_to_user_id" => nil,
237+
"in_reply_to_user_id_str" => nil,
238+
"in_reply_to_screen_name" => nil,
239+
"user" => %{
240+
"id" => 1_000_000 + uid,
241+
"id_str" => Integer.to_string(1_000_000 + uid),
242+
"name" => "User Name #{uid}",
243+
"screen_name" => "username_#{uid}",
244+
"location" => "San Francisco, CA",
245+
"description" =>
246+
"Software engineer and open source contributor. Building things at the intersection of technology and creativity.",
247+
"url" => nil,
248+
"entities" => %{"description" => %{"urls" => []}},
249+
"protected" => false,
250+
"followers_count" => rem(i * 1337, 100_000),
251+
"friends_count" => rem(i * 42, 5_000),
252+
"listed_count" => rem(i * 7, 500),
253+
"created_at" => "Mon Jan 01 00:00:00 +0000 2010",
254+
"favourites_count" => rem(i * 13, 50_000),
255+
"utc_offset" => nil,
256+
"time_zone" => nil,
257+
"geo_enabled" => false,
258+
"verified" => false,
259+
"statuses_count" => rem(i * 17, 10_000),
260+
"lang" => "en",
261+
"contributors_enabled" => false,
262+
"is_translator" => false,
263+
"is_translation_enabled" => false,
264+
"profile_background_color" => "C0DEED",
265+
"profile_background_image_url" =>
266+
"http://pbs.twimg.com/profile_background_images/#{uid}/bg.png",
267+
"profile_background_image_url_https" =>
268+
"https://pbs.twimg.com/profile_background_images/#{uid}/bg.png",
269+
"profile_background_tile" => false,
270+
"profile_image_url" => "http://pbs.twimg.com/profile_images/#{uid}/photo_normal.jpeg",
271+
"profile_image_url_https" =>
272+
"https://pbs.twimg.com/profile_images/#{uid}/photo_normal.jpeg",
273+
"profile_banner_url" => "https://pbs.twimg.com/profile_banners/#{uid}/1409318784",
274+
"profile_link_color" => "0084B4",
275+
"profile_sidebar_border_color" => "C0DEED",
276+
"profile_sidebar_fill_color" => "DDEEF6",
277+
"profile_text_color" => "333333",
278+
"profile_use_background_image" => true,
279+
"default_profile" => true,
280+
"default_profile_image" => false,
281+
"following" => false,
282+
"follow_request_sent" => false,
283+
"notifications" => false
284+
},
285+
"geo" => nil,
286+
"coordinates" => nil,
287+
"place" => nil,
288+
"contributors" => nil,
289+
"retweet_count" => rem(i * 3, 1000),
290+
"favorite_count" => rem(i * 7, 2000),
291+
"entities" => %{
292+
"hashtags" => [
293+
%{"text" => "elixir", "indices" => [15, 22]},
294+
%{"text" => "benchmark", "indices" => [23, 33]}
295+
],
296+
"symbols" => [],
297+
"urls" => [],
298+
"user_mentions" => [
299+
%{
300+
"screen_name" => "user_#{uid}",
301+
"name" => "User #{uid}",
302+
"id" => 2_000_000 + uid,
303+
"id_str" => Integer.to_string(2_000_000 + uid),
304+
"indices" => [34, 42]
305+
}
306+
]
307+
},
308+
"favorited" => false,
309+
"retweeted" => false,
310+
"lang" => "en"
311+
}
312+
end),
313+
"search_metadata" => %{
314+
"count" => 320,
315+
"completed_in" => 0.035,
316+
"max_id" => 505_874_924_095_815_681,
317+
"since_id" => 0,
318+
"query" => "%23elixir",
319+
"refresh_url" => "?since_id=505874924095815681&q=%23elixir&include_entities=1"
320+
}
321+
})
322+
323+
IO.puts("JSON payload size: #{byte_size(large_json)} bytes\n")
324+
325+
Benchee.run(
326+
%{
327+
"torque decode" => fn -> Torque.decode!(large_json) end
328+
},
329+
warmup: 2,
330+
time: 5,
331+
memory_time: 2,
332+
percentiles: [50, 95, 99],
333+
formatters: [
334+
{Benchee.Formatters.Console, percentiles: [50, 95, 99]}
335+
]
336+
)
337+
338+
IO.puts("\n=== LARGE JSON ENCODE BENCHMARK ===\n")
339+
340+
large_decoded_json = Torque.decode!(large_json)
341+
342+
to_proplist = fn f, v ->
343+
cond do
344+
is_map(v) -> {Enum.map(v, fn {k, val} -> {k, f.(f, val)} end)}
345+
is_list(v) -> Enum.map(v, &f.(f, &1))
346+
true -> v
347+
end
348+
end
349+
350+
large_decoded_proplist = to_proplist.(to_proplist, large_decoded_json)
351+
352+
Benchee.run(
353+
%{
354+
"torque: map => binary" => fn -> Torque.encode!(large_decoded_json) end,
355+
"torque: proplist => binary" => fn -> Torque.encode!(large_decoded_proplist) end,
356+
"torque: map => iodata" => fn -> Torque.encode_to_iodata(large_decoded_json) end,
357+
"torque: proplist => iodata" => fn -> Torque.encode_to_iodata(large_decoded_proplist) end
358+
},
359+
warmup: 2,
360+
time: 5,
361+
memory_time: 2,
362+
percentiles: [50, 95, 99],
363+
formatters: [
364+
{Benchee.Formatters.Console, percentiles: [50, 95, 99]}
365+
]
366+
)

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ defmodule Torque.MixProject do
3232
{:stream_data, "~> 1.1", only: :test},
3333
{:simdjsone, "~> 0.5.0", only: :bench},
3434
{:jiffy, "~> 1.1", only: :bench},
35-
{:benchee, "~> 1.3", only: :bench}
35+
{:benchee, "~> 1.3", only: [:bench, :dev]}
3636
]
3737
end
3838

native/torque_nif/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ rustler = "0.36"
1313
sonic-rs = "0.3"
1414
itoa = "1"
1515
ryu = "1"
16+

0 commit comments

Comments
 (0)