Skip to content

Commit 57648c4

Browse files
committed
Add bench_allocs
1 parent 951f34a commit 57648c4

File tree

3 files changed

+188
-1
lines changed

3 files changed

+188
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ docs/docs/linter/rules/
66
# Herb output
77
/herb
88
/run_herb_tests
9+
/bench_allocs
910

1011
# Herb Ruby extension
1112
/ext/herb/extconf.h

Makefile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ test_sources = $(wildcard test/**/*.c)
2020
test_objects = $(test_sources:.c=.o)
2121
non_main_objects = $(filter-out src/main.o, $(objects))
2222

23+
bench_allocs_exec = bench_allocs
24+
bench_allocs_source = bench/bench_allocs.c
25+
2326
soext ?= $(shell ruby -e 'puts RbConfig::CONFIG["DLEXT"]')
2427
lib_name = $(build_dir)/lib$(exec).$(soext)
2528
static_lib_name = $(build_dir)/lib$(exec).a
@@ -102,9 +105,14 @@ test/%.o: test/%.c templates prism
102105
test: $(test_objects) $(non_main_objects)
103106
$(cc) $(test_objects) $(non_main_objects) $(test_cflags) $(test_ldflags) -o $(test_exec)
104107

108+
.PHONY: bench_allocs
109+
bench_allocs: $(non_main_objects)
110+
$(cc) $(bench_allocs_source) $(non_main_objects) $(flags) $(prism_ldflags) -o $(bench_allocs_exec)
111+
./$(bench_allocs_exec)
112+
105113
.PHONY: clean
106114
clean:
107-
rm -f $(exec) $(test_exec) $(lib_name) $(shared_lib_name) $(ruby_extension)
115+
rm -f $(exec) $(test_exec) $(bench_allocs_exec) $(lib_name) $(shared_lib_name) $(ruby_extension)
108116
rm -rf $(objects) $(test_objects) $(extension_objects) lib/herb/*.bundle tmp
109117
rm -rf $(prism_path)
110118
rake prism:clean

bench/bench_allocs.c

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#include "../src/include/herb.h"
2+
#include "../src/include/util/hb_allocator.h"
3+
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
#include <string.h>
7+
8+
static const char* SMALL_INPUT = "<div class=\"hello\"><%= foo %></div>";
9+
10+
static const char* MEDIUM_INPUT =
11+
"<!DOCTYPE html>\n"
12+
"<html lang=\"en\">\n"
13+
"<head>\n"
14+
" <meta charset=\"UTF-8\">\n"
15+
" <title><%= @title %></title>\n"
16+
"</head>\n"
17+
"<body>\n"
18+
" <div id=\"app\" class=\"container\">\n"
19+
" <h1><%= @heading %></h1>\n"
20+
" <% @items.each do |item| %>\n"
21+
" <div class=\"item\" data-id=\"<%= item.id %>\">\n"
22+
" <span class=\"name\"><%= item.name %></span>\n"
23+
" <p><%= item.description %></p>\n"
24+
" <% if item.active? %>\n"
25+
" <span class=\"badge\">Active</span>\n"
26+
" <% else %>\n"
27+
" <span class=\"badge inactive\">Inactive</span>\n"
28+
" <% end %>\n"
29+
" </div>\n"
30+
" <% end %>\n"
31+
" </div>\n"
32+
" <%# This is a comment %>\n"
33+
" <%= render partial: 'footer', locals: { year: 2024 } %>\n"
34+
"</body>\n"
35+
"</html>\n";
36+
37+
static const char* LARGE_INPUT =
38+
"<!DOCTYPE html>\n"
39+
"<html lang=\"<%= I18n.locale %>\">\n"
40+
"<head>\n"
41+
" <meta charset=\"UTF-8\">\n"
42+
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
43+
" <title><%= @page_title || 'Default' %></title>\n"
44+
" <%= csrf_meta_tags %>\n"
45+
" <%= csp_meta_tag %>\n"
46+
" <%= stylesheet_link_tag 'application' %>\n"
47+
" <%= javascript_include_tag 'application' %>\n"
48+
"</head>\n"
49+
"<body class=\"<%= body_class %>\">\n"
50+
" <nav class=\"navbar\">\n"
51+
" <div class=\"nav-brand\">\n"
52+
" <%= link_to root_path do %>\n"
53+
" <img src=\"<%= asset_path('logo.png') %>\" alt=\"Logo\">\n"
54+
" <% end %>\n"
55+
" </div>\n"
56+
" <ul class=\"nav-links\">\n"
57+
" <% @nav_items.each do |nav| %>\n"
58+
" <li class=\"<%= 'active' if current_page?(nav.path) %>\">\n"
59+
" <%= link_to nav.label, nav.path, class: 'nav-link' %>\n"
60+
" </li>\n"
61+
" <% end %>\n"
62+
" </ul>\n"
63+
" <div class=\"nav-user\">\n"
64+
" <% if current_user %>\n"
65+
" <span><%= current_user.name %></span>\n"
66+
" <%= link_to 'Logout', logout_path, method: :delete %>\n"
67+
" <% else %>\n"
68+
" <%= link_to 'Login', login_path %>\n"
69+
" <% end %>\n"
70+
" </div>\n"
71+
" </nav>\n"
72+
" <main id=\"content\">\n"
73+
" <% if flash[:notice] %>\n"
74+
" <div class=\"alert alert-info\"><%= flash[:notice] %></div>\n"
75+
" <% end %>\n"
76+
" <% if flash[:alert] %>\n"
77+
" <div class=\"alert alert-danger\"><%= flash[:alert] %></div>\n"
78+
" <% end %>\n"
79+
" <div class=\"container\">\n"
80+
" <h1><%= @heading %></h1>\n"
81+
" <table class=\"table\">\n"
82+
" <thead>\n"
83+
" <tr>\n"
84+
" <th>Name</th>\n"
85+
" <th>Email</th>\n"
86+
" <th>Role</th>\n"
87+
" <th>Status</th>\n"
88+
" <th>Actions</th>\n"
89+
" </tr>\n"
90+
" </thead>\n"
91+
" <tbody>\n"
92+
" <% @users.each do |user| %>\n"
93+
" <tr id=\"user-<%= user.id %>\" class=\"<%= cycle('odd', 'even') %>\">\n"
94+
" <td><%= user.name %></td>\n"
95+
" <td><%= mail_to user.email %></td>\n"
96+
" <td><%= user.role.titleize %></td>\n"
97+
" <td>\n"
98+
" <% if user.active? %>\n"
99+
" <span class=\"badge badge-success\">Active</span>\n"
100+
" <% elsif user.suspended? %>\n"
101+
" <span class=\"badge badge-warning\">Suspended</span>\n"
102+
" <% else %>\n"
103+
" <span class=\"badge badge-danger\">Inactive</span>\n"
104+
" <% end %>\n"
105+
" </td>\n"
106+
" <td>\n"
107+
" <%= link_to 'View', user_path(user), class: 'btn btn-sm' %>\n"
108+
" <%= link_to 'Edit', edit_user_path(user), class: 'btn btn-sm' %>\n"
109+
" <% if can?(:destroy, user) %>\n"
110+
" <%= link_to 'Delete', user_path(user), method: :delete, class: 'btn btn-sm btn-danger', data: { confirm: 'Are you sure?' } %>\n"
111+
" <% end %>\n"
112+
" </td>\n"
113+
" </tr>\n"
114+
" <% end %>\n"
115+
" </tbody>\n"
116+
" </table>\n"
117+
" <%= render 'shared/pagination', collection: @users %>\n"
118+
" </div>\n"
119+
" </main>\n"
120+
" <footer class=\"footer\">\n"
121+
" <p>&copy; <%= Time.current.year %> Company</p>\n"
122+
" </footer>\n"
123+
"</body>\n"
124+
"</html>\n";
125+
126+
typedef struct {
127+
const char* name;
128+
const char* source;
129+
} test_case_T;
130+
131+
static void run_lex_benchmark(const char* name, const char* source) {
132+
hb_allocator_T allocator = hb_allocator_with_tracking();
133+
134+
hb_array_T* tokens = herb_lex(source, &allocator);
135+
136+
hb_allocator_tracking_stats_T* stats = hb_allocator_tracking_stats(&allocator);
137+
138+
printf(" lex %-10s allocs: %-6zu deallocs: %-6zu bytes_alloc: %-8zu tokens: %zu\n",
139+
name, stats->allocation_count, stats->deallocation_count, stats->bytes_allocated, tokens->size);
140+
141+
herb_free_tokens(&tokens, &allocator);
142+
hb_allocator_destroy(&allocator);
143+
}
144+
145+
static void run_parse_benchmark(const char* name, const char* source) {
146+
hb_allocator_T allocator = hb_allocator_with_tracking();
147+
148+
AST_DOCUMENT_NODE_T* root = herb_parse(source, NULL, &allocator);
149+
150+
hb_allocator_tracking_stats_T* stats = hb_allocator_tracking_stats(&allocator);
151+
152+
printf(" parse %-9s allocs: %-6zu deallocs: %-6zu bytes_alloc: %-8zu\n",
153+
name, stats->allocation_count, stats->deallocation_count, stats->bytes_allocated);
154+
155+
ast_node_free((AST_NODE_T*) root, &allocator);
156+
hb_allocator_destroy(&allocator);
157+
}
158+
159+
int main(void) {
160+
test_case_T cases[] = {
161+
{ "small", SMALL_INPUT },
162+
{ "medium", MEDIUM_INPUT },
163+
{ "large", LARGE_INPUT },
164+
};
165+
166+
size_t num_cases = sizeof(cases) / sizeof(cases[0]);
167+
168+
printf("=== Allocation Benchmark ===\n\n");
169+
170+
for (size_t i = 0; i < num_cases; i++) {
171+
printf("[%s] (%zu bytes input)\n", cases[i].name, strlen(cases[i].source));
172+
run_lex_benchmark(cases[i].name, cases[i].source);
173+
run_parse_benchmark(cases[i].name, cases[i].source);
174+
printf("\n");
175+
}
176+
177+
return 0;
178+
}

0 commit comments

Comments
 (0)