Skip to content

Commit 8b33aaa

Browse files
authored
Add benchmark for URLPattern (#917)
1 parent 5804217 commit 8b33aaa

File tree

2 files changed

+350
-0
lines changed

2 files changed

+350
-0
lines changed

benchmarks/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
add_executable(bench_search_params bench_search_params.cpp)
33
target_link_libraries(bench_search_params PRIVATE ada)
44

5+
add_executable(urlpattern urlpattern.cpp)
6+
target_link_libraries(urlpattern PRIVATE ada)
7+
target_include_directories(urlpattern PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
8+
target_include_directories(urlpattern PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmarks>")
9+
510
# Bench
611
add_executable(wpt_bench wpt_bench.cpp)
712
target_link_libraries(wpt_bench PRIVATE ada)
@@ -57,6 +62,7 @@ target_link_libraries(benchdata PRIVATE benchmark::benchmark)
5762
target_link_libraries(bbc_bench PRIVATE benchmark::benchmark)
5863
target_link_libraries(percent_encode PRIVATE benchmark::benchmark)
5964
target_link_libraries(bench_search_params PRIVATE benchmark::benchmark)
65+
target_link_libraries(urlpattern PRIVATE benchmark::benchmark)
6066

6167
option(ADA_COMPETITION "Whether to install various competitors." OFF)
6268

benchmarks/urlpattern.cpp

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
#include "benchmark_header.h"
2+
3+
std::vector<std::pair<std::string_view, std::string>> url_pattern_examples = {
4+
{"https://example.com/foo/bar", "/foo/bar"},
5+
{"https://example.com/foo/bar/baz", "/foo/bar"},
6+
{"https://example.com/foo.html", ":name.html"},
7+
{"https://sub.example.com/foo/bar",
8+
"http{s}?://{*.}?example.com/:product/:endpoint"},
9+
{"https://example.com/?foo", "https://example.com?foo"},
10+
{"https://example.com:8080/?foo", "https://example.com:8080?foo"},
11+
{"https://example.com/?foo", "https://example.com/*\\?foo"},
12+
{"https://example.com/bar?foo", "https://example.com/:name?foo"}};
13+
14+
std::string url_examples[] = {
15+
"https://www.google.com/"
16+
"webhp?hl=en&amp;ictx=2&amp;sa=X&amp;ved=0ahUKEwil_"
17+
"oSxzJj8AhVtEFkFHTHnCGQQPQgI",
18+
"https://support.google.com/websearch/"
19+
"?p=ws_results_help&amp;hl=en-CA&amp;fg=1",
20+
"https://en.wikipedia.org/wiki/Dog#Roles_with_humans",
21+
"https://www.tiktok.com/@aguyandagolden/video/7133277734310038830",
22+
"https://business.twitter.com/en/help/troubleshooting/"
23+
"how-twitter-ads-work.html?ref=web-twc-ao-gbl-adsinfo&utm_source=twc&utm_"
24+
"medium=web&utm_campaign=ao&utm_content=adsinfo",
25+
"https://images-na.ssl-images-amazon.com/images/I/"
26+
"41Gc3C8UysL.css?AUIClients/AmazonGatewayAuiAssets",
27+
"https://www.reddit.com/?after=t3_zvz1ze",
28+
"https://www.reddit.com/login/?dest=https%3A%2F%2Fwww.reddit.com%2F",
29+
"postgresql://other:9818274x1!!@localhost:5432/"
30+
"otherdb?connect_timeout=10&application_name=myapp",
31+
"http://192.168.1.1", // ipv4
32+
"http://[2606:4700:4700::1111]", // ipv6
33+
};
34+
35+
double url_examples_bytes;
36+
37+
size_t init_data() {
38+
// compute the number of bytes.
39+
auto compute = []() -> double {
40+
size_t bytes{0};
41+
for (const auto& [base, input] : url_pattern_examples) {
42+
bytes += base.size() + input.size();
43+
}
44+
return double(bytes);
45+
};
46+
47+
url_examples_bytes = compute();
48+
return url_pattern_examples.size();
49+
}
50+
51+
size_t count_urlpattern_parse_invalid() {
52+
size_t how_many = 0;
53+
for (const auto& [base, input] : url_pattern_examples) {
54+
auto result =
55+
ada::parse_url_pattern<ada::url_pattern_regex::std_regex_provider>(
56+
input, &base, nullptr);
57+
if (!result) {
58+
how_many++;
59+
}
60+
}
61+
62+
return how_many;
63+
}
64+
65+
size_t count_urlpattern_exec_invalid() {
66+
size_t how_many = 0;
67+
auto pattern =
68+
ada::parse_url_pattern<ada::url_pattern_regex::std_regex_provider>(
69+
"https://*example.com/*");
70+
if (!pattern) {
71+
return std::size(url_examples);
72+
}
73+
74+
for (const std::string& url_example : url_examples) {
75+
auto result = pattern->exec(url_example);
76+
if (!result) {
77+
how_many++;
78+
}
79+
}
80+
81+
return how_many;
82+
}
83+
84+
size_t count_urlpattern_test_invalid() {
85+
size_t how_many = 0;
86+
auto pattern =
87+
ada::parse_url_pattern<ada::url_pattern_regex::std_regex_provider>(
88+
"https://*example.com/*");
89+
if (!pattern) {
90+
return std::size(url_examples);
91+
}
92+
93+
for (const std::string& url_example : url_examples) {
94+
auto result = pattern->test(url_example);
95+
if (!result) {
96+
how_many++;
97+
}
98+
}
99+
100+
return how_many;
101+
}
102+
103+
static void BasicBench_AdaURL_URLPattern_Parse(benchmark::State& state) {
104+
// volatile to prevent optimizations.
105+
volatile size_t success = 0;
106+
107+
for (auto _ : state) {
108+
for (const auto& [base, input] : url_pattern_examples) {
109+
auto result =
110+
ada::parse_url_pattern<ada::url_pattern_regex::std_regex_provider>(
111+
input, &base, nullptr);
112+
if (result) {
113+
success++;
114+
}
115+
}
116+
}
117+
if (collector.has_events()) {
118+
event_aggregate aggregate{};
119+
for (size_t i = 0; i < N; i++) {
120+
std::atomic_thread_fence(std::memory_order_acquire);
121+
collector.start();
122+
for (const auto& [base, input] : url_pattern_examples) {
123+
auto result =
124+
ada::parse_url_pattern<ada::url_pattern_regex::std_regex_provider>(
125+
input, &base, nullptr);
126+
if (result) {
127+
success++;
128+
}
129+
}
130+
std::atomic_thread_fence(std::memory_order_release);
131+
event_count allocate_count = collector.end();
132+
aggregate << allocate_count;
133+
}
134+
state.counters["cycles/url"] =
135+
aggregate.best.cycles() / std::size(url_pattern_examples);
136+
state.counters["instructions/url"] =
137+
aggregate.best.instructions() / std::size(url_pattern_examples);
138+
state.counters["instructions/cycle"] =
139+
aggregate.best.instructions() / aggregate.best.cycles();
140+
state.counters["instructions/byte"] =
141+
aggregate.best.instructions() / url_examples_bytes;
142+
state.counters["instructions/ns"] =
143+
aggregate.best.instructions() / aggregate.best.elapsed_ns();
144+
state.counters["GHz"] =
145+
aggregate.best.cycles() / aggregate.best.elapsed_ns();
146+
state.counters["ns/url"] =
147+
aggregate.best.elapsed_ns() / std::size(url_pattern_examples);
148+
state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
149+
}
150+
(void)success;
151+
state.counters["time/byte"] = benchmark::Counter(
152+
url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
153+
benchmark::Counter::kInvert);
154+
state.counters["time/url"] =
155+
benchmark::Counter(double(std::size(url_pattern_examples)),
156+
benchmark::Counter::kIsIterationInvariantRate |
157+
benchmark::Counter::kInvert);
158+
state.counters["speed"] = benchmark::Counter(
159+
url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
160+
state.counters["url/s"] =
161+
benchmark::Counter(double(std::size(url_pattern_examples)),
162+
benchmark::Counter::kIsIterationInvariantRate);
163+
}
164+
165+
BENCHMARK(BasicBench_AdaURL_URLPattern_Parse);
166+
167+
static void BasicBench_AdaURL_URLPattern_Exec(benchmark::State& state) {
168+
auto pattern =
169+
ada::parse_url_pattern<ada::url_pattern_regex::std_regex_provider>(
170+
"https://*example.com/*");
171+
if (!pattern) {
172+
state.SkipWithError("Failed to parse test pattern");
173+
return;
174+
}
175+
176+
// volatile to prevent optimizations.
177+
volatile size_t success = 0;
178+
179+
for (auto _ : state) {
180+
for (std::string& url_example : url_examples) {
181+
auto result = pattern->exec(url_example);
182+
if (result) {
183+
success++;
184+
}
185+
}
186+
}
187+
if (collector.has_events()) {
188+
event_aggregate aggregate{};
189+
for (size_t i = 0; i < N; i++) {
190+
std::atomic_thread_fence(std::memory_order_acquire);
191+
collector.start();
192+
for (std::string& url_example : url_examples) {
193+
auto result = pattern->exec(url_example);
194+
if (result) {
195+
success++;
196+
}
197+
}
198+
std::atomic_thread_fence(std::memory_order_release);
199+
event_count allocate_count = collector.end();
200+
aggregate << allocate_count;
201+
}
202+
state.counters["cycles/url"] =
203+
aggregate.best.cycles() / std::size(url_pattern_examples);
204+
state.counters["instructions/url"] =
205+
aggregate.best.instructions() / std::size(url_pattern_examples);
206+
state.counters["instructions/cycle"] =
207+
aggregate.best.instructions() / aggregate.best.cycles();
208+
state.counters["instructions/byte"] =
209+
aggregate.best.instructions() / url_examples_bytes;
210+
state.counters["instructions/ns"] =
211+
aggregate.best.instructions() / aggregate.best.elapsed_ns();
212+
state.counters["GHz"] =
213+
aggregate.best.cycles() / aggregate.best.elapsed_ns();
214+
state.counters["ns/url"] =
215+
aggregate.best.elapsed_ns() / std::size(url_pattern_examples);
216+
state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
217+
}
218+
(void)success;
219+
state.counters["time/byte"] = benchmark::Counter(
220+
url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
221+
benchmark::Counter::kInvert);
222+
state.counters["time/url"] =
223+
benchmark::Counter(double(std::size(url_pattern_examples)),
224+
benchmark::Counter::kIsIterationInvariantRate |
225+
benchmark::Counter::kInvert);
226+
state.counters["speed"] = benchmark::Counter(
227+
url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
228+
state.counters["url/s"] =
229+
benchmark::Counter(double(std::size(url_pattern_examples)),
230+
benchmark::Counter::kIsIterationInvariantRate);
231+
}
232+
233+
BENCHMARK(BasicBench_AdaURL_URLPattern_Exec);
234+
235+
static void BasicBench_AdaURL_URLPattern_Test(benchmark::State& state) {
236+
auto pattern =
237+
ada::parse_url_pattern<ada::url_pattern_regex::std_regex_provider>(
238+
"https://*example.com/*");
239+
if (!pattern) {
240+
state.SkipWithError("Failed to parse test pattern");
241+
return;
242+
}
243+
244+
// volatile to prevent optimizations.
245+
volatile size_t success = 0;
246+
247+
for (auto _ : state) {
248+
for (std::string& url_example : url_examples) {
249+
auto result = pattern->test(url_example);
250+
if (result) {
251+
success++;
252+
}
253+
}
254+
}
255+
if (collector.has_events()) {
256+
event_aggregate aggregate{};
257+
for (size_t i = 0; i < N; i++) {
258+
std::atomic_thread_fence(std::memory_order_acquire);
259+
collector.start();
260+
for (std::string& url_example : url_examples) {
261+
auto result = pattern->test(url_example);
262+
if (result) {
263+
success++;
264+
}
265+
}
266+
std::atomic_thread_fence(std::memory_order_release);
267+
event_count allocate_count = collector.end();
268+
aggregate << allocate_count;
269+
}
270+
state.counters["cycles/url"] =
271+
aggregate.best.cycles() / std::size(url_examples);
272+
state.counters["instructions/url"] =
273+
aggregate.best.instructions() / std::size(url_examples);
274+
state.counters["instructions/cycle"] =
275+
aggregate.best.instructions() / aggregate.best.cycles();
276+
state.counters["instructions/byte"] =
277+
aggregate.best.instructions() / url_examples_bytes;
278+
state.counters["instructions/ns"] =
279+
aggregate.best.instructions() / aggregate.best.elapsed_ns();
280+
state.counters["GHz"] =
281+
aggregate.best.cycles() / aggregate.best.elapsed_ns();
282+
state.counters["ns/url"] =
283+
aggregate.best.elapsed_ns() / std::size(url_examples);
284+
state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
285+
}
286+
(void)success;
287+
state.counters["time/byte"] = benchmark::Counter(
288+
url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
289+
benchmark::Counter::kInvert);
290+
state.counters["time/url"] =
291+
benchmark::Counter(double(std::size(url_examples)),
292+
benchmark::Counter::kIsIterationInvariantRate |
293+
benchmark::Counter::kInvert);
294+
state.counters["speed"] = benchmark::Counter(
295+
url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
296+
state.counters["url/s"] =
297+
benchmark::Counter(double(std::size(url_examples)),
298+
benchmark::Counter::kIsIterationInvariantRate);
299+
}
300+
301+
BENCHMARK(BasicBench_AdaURL_URLPattern_Test);
302+
303+
int main(int argc, char** argv) {
304+
init_data();
305+
size_t urlpattern_parse_bad_urls = count_urlpattern_parse_invalid();
306+
size_t urlpattern_exec_bad_urls = count_urlpattern_exec_invalid();
307+
size_t urlpattern_test_bad_urls = count_urlpattern_test_invalid();
308+
309+
#if (__APPLE__ && __aarch64__) || defined(__linux__)
310+
if (!collector.has_events()) {
311+
benchmark::AddCustomContext("performance counters",
312+
"No privileged access (sudo may help).");
313+
}
314+
#else
315+
if (!collector.has_events()) {
316+
benchmark::AddCustomContext("performance counters", "Unsupported system.");
317+
}
318+
#endif
319+
benchmark::AddCustomContext("input bytes",
320+
std::to_string(size_t(url_examples_bytes)));
321+
benchmark::AddCustomContext("number of URLs",
322+
std::to_string(std::size(url_pattern_examples)));
323+
benchmark::AddCustomContext(
324+
"bytes/URL",
325+
std::to_string(url_examples_bytes / std::size(url_pattern_examples)));
326+
327+
std::stringstream badcounts;
328+
badcounts << "---------------------\n";
329+
badcounts << "urlpattern-parse---count of bad URLs "
330+
<< std::to_string(urlpattern_parse_bad_urls) << "\n";
331+
badcounts << "urlpattern-exec---count of bad URLs "
332+
<< std::to_string(urlpattern_exec_bad_urls) << "\n";
333+
badcounts << "urlpattern-test---count of bad URLs "
334+
<< std::to_string(urlpattern_test_bad_urls) << "\n";
335+
benchmark::AddCustomContext("bad url patterns", badcounts.str());
336+
337+
if (collector.has_events()) {
338+
benchmark::AddCustomContext("performance counters", "Enabled");
339+
}
340+
341+
benchmark::Initialize(&argc, argv);
342+
benchmark::RunSpecifiedBenchmarks();
343+
benchmark::Shutdown();
344+
}

0 commit comments

Comments
 (0)