Compiled temapltes.
Benchmark cleanup
This commit is contained in:
172
src/benchmarks/bench.zig
Normal file
172
src/benchmarks/bench.zig
Normal file
@@ -0,0 +1,172 @@
|
||||
//! Pugz Benchmark - Compiled Templates vs Pug.js
|
||||
//!
|
||||
//! Both Pugz and Pug.js benchmarks read from the same files:
|
||||
//! src/benchmarks/templates/*.pug (templates)
|
||||
//! src/benchmarks/templates/*.json (data)
|
||||
//!
|
||||
//! Run Pugz: zig build bench-all-compiled
|
||||
//! Run Pug.js: cd src/benchmarks/pugjs && npm install && npm run bench
|
||||
|
||||
const std = @import("std");
|
||||
const tpls = @import("tpls");
|
||||
|
||||
const iterations: usize = 2000;
|
||||
const templates_dir = "src/benchmarks/templates";
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Data structures matching JSON files
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const SubFriend = struct {
|
||||
id: i64,
|
||||
name: []const u8,
|
||||
};
|
||||
|
||||
const Friend = struct {
|
||||
name: []const u8,
|
||||
balance: []const u8,
|
||||
age: i64,
|
||||
address: []const u8,
|
||||
picture: []const u8,
|
||||
company: []const u8,
|
||||
email: []const u8,
|
||||
emailHref: []const u8,
|
||||
about: []const u8,
|
||||
tags: []const []const u8,
|
||||
friends: []const SubFriend,
|
||||
};
|
||||
|
||||
const Account = struct {
|
||||
balance: i64,
|
||||
balanceFormatted: []const u8,
|
||||
status: []const u8,
|
||||
negative: bool,
|
||||
};
|
||||
|
||||
const Project = struct {
|
||||
name: []const u8,
|
||||
url: []const u8,
|
||||
description: []const u8,
|
||||
};
|
||||
|
||||
const SearchRecord = struct {
|
||||
imgUrl: []const u8,
|
||||
viewItemUrl: []const u8,
|
||||
title: []const u8,
|
||||
description: []const u8,
|
||||
featured: bool,
|
||||
sizes: ?[]const []const u8,
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Main
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
std.debug.print("\n", .{});
|
||||
std.debug.print("╔═══════════════════════════════════════════════════════════════╗\n", .{});
|
||||
std.debug.print("║ Compiled Zig Templates Benchmark ({d} iterations) ║\n", .{iterations});
|
||||
std.debug.print("║ Templates: {s}/*.pug ║\n", .{templates_dir});
|
||||
std.debug.print("╚═══════════════════════════════════════════════════════════════╝\n", .{});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Load JSON data
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
std.debug.print("\nLoading JSON data...\n", .{});
|
||||
|
||||
var data_arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer data_arena.deinit();
|
||||
const data_alloc = data_arena.allocator();
|
||||
|
||||
// Load all JSON files
|
||||
const simple0 = try loadJson(struct { name: []const u8 }, data_alloc, "simple-0.json");
|
||||
const simple1 = try loadJson(struct {
|
||||
name: []const u8,
|
||||
messageCount: i64,
|
||||
colors: []const []const u8,
|
||||
primary: bool,
|
||||
}, data_alloc, "simple-1.json");
|
||||
const simple2 = try loadJson(struct {
|
||||
header: []const u8,
|
||||
header2: []const u8,
|
||||
header3: []const u8,
|
||||
header4: []const u8,
|
||||
header5: []const u8,
|
||||
header6: []const u8,
|
||||
list: []const []const u8,
|
||||
}, data_alloc, "simple-2.json");
|
||||
const if_expr = try loadJson(struct { accounts: []const Account }, data_alloc, "if-expression.json");
|
||||
const projects = try loadJson(struct {
|
||||
title: []const u8,
|
||||
text: []const u8,
|
||||
projects: []const Project,
|
||||
}, data_alloc, "projects-escaped.json");
|
||||
const search = try loadJson(struct { searchRecords: []const SearchRecord }, data_alloc, "search-results.json");
|
||||
const friends_data = try loadJson(struct { friends: []const Friend }, data_alloc, "friends.json");
|
||||
|
||||
std.debug.print("Loaded. Starting benchmark...\n\n", .{});
|
||||
|
||||
var total: f64 = 0;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Benchmark each template
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// simple-0
|
||||
total += try bench("simple-0", allocator, tpls.simple_0, simple0);
|
||||
|
||||
// simple-1
|
||||
total += try bench("simple-1", allocator, tpls.simple_1, simple1);
|
||||
|
||||
// simple-2
|
||||
total += try bench("simple-2", allocator, tpls.simple_2, simple2);
|
||||
|
||||
// if-expression
|
||||
total += try bench("if-expression", allocator, tpls.if_expression, if_expr);
|
||||
|
||||
// projects-escaped
|
||||
total += try bench("projects-escaped", allocator, tpls.projects_escaped, projects);
|
||||
|
||||
// search-results
|
||||
total += try bench("search-results", allocator, tpls.search_results, search);
|
||||
|
||||
// friends
|
||||
total += try bench("friends", allocator, tpls.friends, friends_data);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Summary
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
std.debug.print("\n", .{});
|
||||
std.debug.print(" {s:<20} => {d:>7.1}ms\n", .{ "TOTAL", total });
|
||||
std.debug.print("\n", .{});
|
||||
}
|
||||
|
||||
fn loadJson(comptime T: type, alloc: std.mem.Allocator, comptime filename: []const u8) !T {
|
||||
const path = templates_dir ++ "/" ++ filename;
|
||||
const content = try std.fs.cwd().readFileAlloc(alloc, path, 10 * 1024 * 1024);
|
||||
const parsed = try std.json.parseFromSlice(T, alloc, content, .{});
|
||||
return parsed.value;
|
||||
}
|
||||
|
||||
fn bench(
|
||||
name: []const u8,
|
||||
allocator: std.mem.Allocator,
|
||||
comptime render_fn: anytype,
|
||||
data: anytype,
|
||||
) !f64 {
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
var timer = try std.time.Timer.start();
|
||||
for (0..iterations) |_| {
|
||||
_ = arena.reset(.retain_capacity);
|
||||
_ = try render_fn(arena.allocator(), data);
|
||||
}
|
||||
const ms = @as(f64, @floatFromInt(timer.read())) / 1_000_000.0;
|
||||
std.debug.print(" {s:<20} => {d:>7.1}ms\n", .{ name, ms });
|
||||
return ms;
|
||||
}
|
||||
@@ -1,388 +0,0 @@
|
||||
//! Pugz Rendering Benchmark
|
||||
//!
|
||||
//! Measures template rendering performance with various template complexities.
|
||||
//! Run with: zig build bench
|
||||
//!
|
||||
//! Metrics reported:
|
||||
//! - Total time for N iterations
|
||||
//! - Average time per render
|
||||
//! - Renders per second
|
||||
//! - Memory usage per render
|
||||
|
||||
const std = @import("std");
|
||||
const pugz = @import("pugz");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
/// Benchmark configuration
|
||||
const Config = struct {
|
||||
warmup_iterations: usize = 200,
|
||||
benchmark_iterations: usize = 20_000,
|
||||
show_output: bool = false,
|
||||
};
|
||||
|
||||
/// Benchmark result
|
||||
const Result = struct {
|
||||
name: []const u8,
|
||||
iterations: usize,
|
||||
total_ns: u64,
|
||||
min_ns: u64,
|
||||
max_ns: u64,
|
||||
avg_ns: u64,
|
||||
ops_per_sec: f64,
|
||||
bytes_per_render: usize,
|
||||
arena_peak_bytes: usize,
|
||||
|
||||
pub fn print(self: Result) void {
|
||||
std.debug.print("\n{s}\n", .{self.name});
|
||||
std.debug.print(" Iterations: {d:>10}\n", .{self.iterations});
|
||||
std.debug.print(" Total time: {d:>10.2} ms\n", .{@as(f64, @floatFromInt(self.total_ns)) / 1_000_000.0});
|
||||
std.debug.print(" Avg per render: {d:>10.2} us\n", .{@as(f64, @floatFromInt(self.avg_ns)) / 1_000.0});
|
||||
std.debug.print(" Min: {d:>10.2} us\n", .{@as(f64, @floatFromInt(self.min_ns)) / 1_000.0});
|
||||
std.debug.print(" Max: {d:>10.2} us\n", .{@as(f64, @floatFromInt(self.max_ns)) / 1_000.0});
|
||||
std.debug.print(" Renders/sec: {d:>10.0}\n", .{self.ops_per_sec});
|
||||
std.debug.print(" Output size: {d:>10} bytes\n", .{self.bytes_per_render});
|
||||
std.debug.print(" Memory/render: {d:>10} bytes\n", .{self.arena_peak_bytes});
|
||||
}
|
||||
};
|
||||
|
||||
/// Run a benchmark for a template
|
||||
fn runBenchmark(
|
||||
allocator: Allocator,
|
||||
comptime name: []const u8,
|
||||
template: []const u8,
|
||||
data: anytype,
|
||||
config: Config,
|
||||
) !Result {
|
||||
// Warmup phase
|
||||
for (0..config.warmup_iterations) |_| {
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
_ = try pugz.renderTemplate(arena.allocator(), template, data);
|
||||
}
|
||||
|
||||
// Benchmark phase
|
||||
var total_ns: u64 = 0;
|
||||
var min_ns: u64 = std.math.maxInt(u64);
|
||||
var max_ns: u64 = 0;
|
||||
var output_size: usize = 0;
|
||||
var peak_memory: usize = 0;
|
||||
|
||||
var timer = try std.time.Timer.start();
|
||||
|
||||
for (0..config.benchmark_iterations) |i| {
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
timer.reset();
|
||||
const result = try pugz.renderTemplate(arena.allocator(), template, data);
|
||||
const elapsed = timer.read();
|
||||
|
||||
total_ns += elapsed;
|
||||
min_ns = @min(min_ns, elapsed);
|
||||
max_ns = @max(max_ns, elapsed);
|
||||
|
||||
if (i == 0) {
|
||||
output_size = result.len;
|
||||
// Measure memory used by arena (query state before deinit)
|
||||
const state = arena.queryCapacity();
|
||||
peak_memory = state;
|
||||
if (config.show_output) {
|
||||
std.debug.print("\n--- {s} output ---\n{s}\n", .{ name, result });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const avg_ns = total_ns / config.benchmark_iterations;
|
||||
const ops_per_sec = @as(f64, @floatFromInt(config.benchmark_iterations)) / (@as(f64, @floatFromInt(total_ns)) / 1_000_000_000.0);
|
||||
|
||||
return .{
|
||||
.name = name,
|
||||
.iterations = config.benchmark_iterations,
|
||||
.total_ns = total_ns,
|
||||
.min_ns = min_ns,
|
||||
.max_ns = max_ns,
|
||||
.avg_ns = avg_ns,
|
||||
.ops_per_sec = ops_per_sec,
|
||||
.bytes_per_render = output_size,
|
||||
.arena_peak_bytes = peak_memory,
|
||||
};
|
||||
}
|
||||
|
||||
/// Simple template - just a few elements
|
||||
const simple_template =
|
||||
\\doctype html
|
||||
\\html
|
||||
\\ head
|
||||
\\ title= title
|
||||
\\ body
|
||||
\\ h1 Hello, #{name}!
|
||||
\\ p Welcome to our site.
|
||||
;
|
||||
|
||||
/// Medium template - with conditionals and loops
|
||||
const medium_template =
|
||||
\\doctype html
|
||||
\\html
|
||||
\\ head
|
||||
\\ title= title
|
||||
\\ meta(charset="utf-8")
|
||||
\\ meta(name="viewport" content="width=device-width, initial-scale=1")
|
||||
\\ body
|
||||
\\ header
|
||||
\\ nav.navbar
|
||||
\\ a.brand(href="/") Brand
|
||||
\\ ul.nav-links
|
||||
\\ each link in navLinks
|
||||
\\ li
|
||||
\\ a(href=link.href)= link.text
|
||||
\\ main.container
|
||||
\\ h1= title
|
||||
\\ if showIntro
|
||||
\\ p.intro Welcome, #{userName}!
|
||||
\\ section.content
|
||||
\\ each item in items
|
||||
\\ .card
|
||||
\\ h3= item.title
|
||||
\\ p= item.description
|
||||
\\ footer
|
||||
\\ p Copyright 2024
|
||||
;
|
||||
|
||||
/// Complex template - with mixins, nested loops, conditionals
|
||||
const complex_template =
|
||||
\\mixin card(title, description)
|
||||
\\ .card
|
||||
\\ .card-header
|
||||
\\ h3= title
|
||||
\\ .card-body
|
||||
\\ p= description
|
||||
\\ block
|
||||
\\
|
||||
\\mixin button(text, type="primary")
|
||||
\\ button(class="btn btn-" + type)= text
|
||||
\\
|
||||
\\mixin navItem(href, text)
|
||||
\\ li
|
||||
\\ a(href=href)= text
|
||||
\\
|
||||
\\doctype html
|
||||
\\html
|
||||
\\ head
|
||||
\\ title= title
|
||||
\\ meta(charset="utf-8")
|
||||
\\ meta(name="viewport" content="width=device-width, initial-scale=1")
|
||||
\\ link(rel="stylesheet" href="/css/style.css")
|
||||
\\ body
|
||||
\\ header.site-header
|
||||
\\ .container
|
||||
\\ a.logo(href="/")
|
||||
\\ img(src="/img/logo.png" alt="Logo")
|
||||
\\ nav.main-nav
|
||||
\\ ul
|
||||
\\ each link in navLinks
|
||||
\\ +navItem(link.href, link.text)
|
||||
\\ .user-menu
|
||||
\\ if user
|
||||
\\ span.greeting Hello, #{user.name}!
|
||||
\\ +button("Logout", "secondary")
|
||||
\\ else
|
||||
\\ +button("Login")
|
||||
\\ +button("Sign Up", "success")
|
||||
\\ main.site-content
|
||||
\\ .container
|
||||
\\ .page-header
|
||||
\\ h1= pageTitle
|
||||
\\ if subtitle
|
||||
\\ p.subtitle= subtitle
|
||||
\\ .content-grid
|
||||
\\ each category in categories
|
||||
\\ section.category
|
||||
\\ h2= category.name
|
||||
\\ .cards
|
||||
\\ each item in category.items
|
||||
\\ +card(item.title, item.description)
|
||||
\\ .card-footer
|
||||
\\ +button("View Details")
|
||||
\\ aside.sidebar
|
||||
\\ .widget
|
||||
\\ h4 Recent Posts
|
||||
\\ ul.post-list
|
||||
\\ each post in recentPosts
|
||||
\\ li
|
||||
\\ a(href=post.url)= post.title
|
||||
\\ .widget
|
||||
\\ h4 Tags
|
||||
\\ .tag-cloud
|
||||
\\ each tag in allTags
|
||||
\\ span.tag= tag
|
||||
\\ footer.site-footer
|
||||
\\ .container
|
||||
\\ .footer-grid
|
||||
\\ .footer-col
|
||||
\\ h4 About
|
||||
\\ p Some description text here.
|
||||
\\ .footer-col
|
||||
\\ h4 Links
|
||||
\\ ul
|
||||
\\ each link in footerLinks
|
||||
\\ li
|
||||
\\ a(href=link.href)= link.text
|
||||
\\ .footer-col
|
||||
\\ h4 Contact
|
||||
\\ p Email: contact@example.com
|
||||
\\ .copyright
|
||||
\\ p Copyright #{year} Example Inc.
|
||||
;
|
||||
|
||||
pub fn main() !void {
|
||||
// Use GPA with leak detection enabled
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{
|
||||
.stack_trace_frames = 10,
|
||||
.safety = true,
|
||||
}){};
|
||||
defer {
|
||||
const leaked = gpa.deinit();
|
||||
if (leaked == .leak) {
|
||||
std.debug.print("\n⚠️ MEMORY LEAK DETECTED!\n", .{});
|
||||
} else {
|
||||
std.debug.print("\n✓ No memory leaks detected.\n", .{});
|
||||
}
|
||||
}
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
const config = Config{
|
||||
.warmup_iterations = 200,
|
||||
.benchmark_iterations = 20_000,
|
||||
.show_output = false,
|
||||
};
|
||||
|
||||
std.debug.print("\n", .{});
|
||||
std.debug.print("╔══════════════════════════════════════════════════════════════╗\n", .{});
|
||||
std.debug.print("║ Pugz Template Rendering Benchmark ║\n", .{});
|
||||
std.debug.print("╠══════════════════════════════════════════════════════════════╣\n", .{});
|
||||
std.debug.print("║ Warmup iterations: {d:>6} ║\n", .{config.warmup_iterations});
|
||||
std.debug.print("║ Benchmark iterations: {d:>6} ║\n", .{config.benchmark_iterations});
|
||||
std.debug.print("╚══════════════════════════════════════════════════════════════╝\n", .{});
|
||||
|
||||
// Simple template benchmark
|
||||
const simple_result = try runBenchmark(
|
||||
allocator,
|
||||
"Simple Template (basic elements, interpolation)",
|
||||
simple_template,
|
||||
.{
|
||||
.title = "Welcome",
|
||||
.name = "World",
|
||||
},
|
||||
config,
|
||||
);
|
||||
simple_result.print();
|
||||
|
||||
// Medium template benchmark
|
||||
const NavLink = struct { href: []const u8, text: []const u8 };
|
||||
const Item = struct { title: []const u8, description: []const u8 };
|
||||
|
||||
const medium_result = try runBenchmark(
|
||||
allocator,
|
||||
"Medium Template (loops, conditionals, nested elements)",
|
||||
medium_template,
|
||||
.{
|
||||
.title = "Dashboard",
|
||||
.userName = "Alice",
|
||||
.showIntro = true,
|
||||
.navLinks = &[_]NavLink{
|
||||
.{ .href = "/", .text = "Home" },
|
||||
.{ .href = "/about", .text = "About" },
|
||||
.{ .href = "/contact", .text = "Contact" },
|
||||
},
|
||||
.items = &[_]Item{
|
||||
.{ .title = "Item 1", .description = "Description for item 1" },
|
||||
.{ .title = "Item 2", .description = "Description for item 2" },
|
||||
.{ .title = "Item 3", .description = "Description for item 3" },
|
||||
.{ .title = "Item 4", .description = "Description for item 4" },
|
||||
},
|
||||
},
|
||||
config,
|
||||
);
|
||||
medium_result.print();
|
||||
|
||||
// Complex template benchmark
|
||||
const User = struct { name: []const u8 };
|
||||
const SimpleItem = struct { title: []const u8, description: []const u8 };
|
||||
const Category = struct { name: []const u8, items: []const SimpleItem };
|
||||
const Post = struct { url: []const u8, title: []const u8 };
|
||||
const FooterLink = struct { href: []const u8, text: []const u8 };
|
||||
|
||||
const complex_result = try runBenchmark(
|
||||
allocator,
|
||||
"Complex Template (mixins, nested loops, conditionals)",
|
||||
complex_template,
|
||||
.{
|
||||
.title = "Example Site",
|
||||
.pageTitle = "Welcome to Our Site",
|
||||
.subtitle = "The best place on the web",
|
||||
.year = "2024",
|
||||
.user = User{ .name = "Alice" },
|
||||
.navLinks = &[_]NavLink{
|
||||
.{ .href = "/", .text = "Home" },
|
||||
.{ .href = "/products", .text = "Products" },
|
||||
.{ .href = "/about", .text = "About" },
|
||||
.{ .href = "/contact", .text = "Contact" },
|
||||
},
|
||||
.categories = &[_]Category{
|
||||
.{
|
||||
.name = "Featured",
|
||||
.items = &[_]SimpleItem{
|
||||
.{ .title = "Product A", .description = "Amazing product A" },
|
||||
.{ .title = "Product B", .description = "Wonderful product B" },
|
||||
},
|
||||
},
|
||||
.{
|
||||
.name = "Popular",
|
||||
.items = &[_]SimpleItem{
|
||||
.{ .title = "Product C", .description = "Popular product C" },
|
||||
.{ .title = "Product D", .description = "Trending product D" },
|
||||
},
|
||||
},
|
||||
},
|
||||
.recentPosts = &[_]Post{
|
||||
.{ .url = "/blog/post-1", .title = "First Blog Post" },
|
||||
.{ .url = "/blog/post-2", .title = "Second Blog Post" },
|
||||
.{ .url = "/blog/post-3", .title = "Third Blog Post" },
|
||||
},
|
||||
.allTags = &[_][]const u8{ "tech", "news", "tutorial", "review", "guide" },
|
||||
.footerLinks = &[_]FooterLink{
|
||||
.{ .href = "/privacy", .text = "Privacy Policy" },
|
||||
.{ .href = "/terms", .text = "Terms of Service" },
|
||||
.{ .href = "/sitemap", .text = "Sitemap" },
|
||||
},
|
||||
},
|
||||
config,
|
||||
);
|
||||
complex_result.print();
|
||||
|
||||
// Summary
|
||||
std.debug.print("\n", .{});
|
||||
std.debug.print("╔══════════════════════════════════════════════════════════════╗\n", .{});
|
||||
std.debug.print("║ Summary ║\n", .{});
|
||||
std.debug.print("╠══════════════════════════════════════════════════════════════╣\n", .{});
|
||||
std.debug.print("║ Template │ Avg (us) │ Renders/sec │ Output (bytes) ║\n", .{});
|
||||
std.debug.print("╠──────────────────┼──────────┼─────────────┼─────────────────╣\n", .{});
|
||||
std.debug.print("║ Simple │ {d:>8.2} │ {d:>11.0} │ {d:>15} ║\n", .{
|
||||
@as(f64, @floatFromInt(simple_result.avg_ns)) / 1_000.0,
|
||||
simple_result.ops_per_sec,
|
||||
simple_result.bytes_per_render,
|
||||
});
|
||||
std.debug.print("║ Medium │ {d:>8.2} │ {d:>11.0} │ {d:>15} ║\n", .{
|
||||
@as(f64, @floatFromInt(medium_result.avg_ns)) / 1_000.0,
|
||||
medium_result.ops_per_sec,
|
||||
medium_result.bytes_per_render,
|
||||
});
|
||||
std.debug.print("║ Complex │ {d:>8.2} │ {d:>11.0} │ {d:>15} ║\n", .{
|
||||
@as(f64, @floatFromInt(complex_result.avg_ns)) / 1_000.0,
|
||||
complex_result.ops_per_sec,
|
||||
complex_result.bytes_per_render,
|
||||
});
|
||||
std.debug.print("╚══════════════════════════════════════════════════════════════╝\n", .{});
|
||||
std.debug.print("\n", .{});
|
||||
}
|
||||
@@ -1,479 +0,0 @@
|
||||
//! Pugz Benchmark - Comparison with template-engine-bench
|
||||
//!
|
||||
//! These benchmarks use the exact same templates from:
|
||||
//! https://github.com/itsarnaud/template-engine-bench
|
||||
//!
|
||||
//! Run individual benchmarks:
|
||||
//! zig build test-bench -- simple-0
|
||||
//! zig build test-bench -- friends
|
||||
//!
|
||||
//! Run all benchmarks:
|
||||
//! zig build test-bench
|
||||
//!
|
||||
//! Pug.js reference (2000 iterations on MacBook Air M2):
|
||||
//! - simple-0: pug => 2ms
|
||||
//! - simple-1: pug => 9ms
|
||||
//! - simple-2: pug => 9ms
|
||||
//! - if-expression: pug => 12ms
|
||||
//! - projects-escaped: pug => 86ms
|
||||
//! - search-results: pug => 41ms
|
||||
//! - friends: pug => 110ms
|
||||
|
||||
const std = @import("std");
|
||||
const pugz = @import("pugz");
|
||||
|
||||
const iterations: usize = 2000;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// simple-0
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const simple_0_tpl = "h1 Hello, #{name}";
|
||||
|
||||
test "bench: simple-0" {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer if (gpa.deinit() == .leak) @panic("leak!");
|
||||
|
||||
const engine = pugz.ViewEngine.init(.{});
|
||||
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
|
||||
defer arena.deinit();
|
||||
|
||||
var total_ns: u64 = 0;
|
||||
var timer = try std.time.Timer.start();
|
||||
|
||||
for (0..iterations) |_| {
|
||||
_ = arena.reset(.retain_capacity);
|
||||
timer.reset();
|
||||
_ = try engine.renderTpl(arena.allocator(), simple_0_tpl, .{
|
||||
.name = "John",
|
||||
});
|
||||
total_ns += timer.read();
|
||||
}
|
||||
|
||||
printResult("simple-0", total_ns, 2);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// simple-1
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const simple_1_tpl =
|
||||
\\.simple-1(style="background-color: blue; border: 1px solid black")
|
||||
\\ .colors
|
||||
\\ span.hello Hello #{name}!
|
||||
\\ strong You have #{messageCount} messages!
|
||||
\\ if colors
|
||||
\\ ul
|
||||
\\ each color in colors
|
||||
\\ li.color= color
|
||||
\\ else
|
||||
\\ div No colors!
|
||||
\\ if primary
|
||||
\\ button(type="button" class="primary") Click me!
|
||||
\\ else
|
||||
\\ button(type="button" class="secondary") Click me!
|
||||
;
|
||||
|
||||
test "bench: simple-1" {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
const engine = pugz.ViewEngine.init(.{});
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
|
||||
defer arena.deinit();
|
||||
|
||||
var total_ns: u64 = 0;
|
||||
var timer = try std.time.Timer.start();
|
||||
|
||||
const data = .{
|
||||
.name = "George Washington",
|
||||
.messageCount = 999,
|
||||
.colors = &[_][]const u8{ "red", "green", "blue", "yellow", "orange", "pink", "black", "white", "beige", "brown", "cyan", "magenta" },
|
||||
.primary = true,
|
||||
};
|
||||
|
||||
for (0..iterations) |_| {
|
||||
_ = arena.reset(.retain_capacity);
|
||||
timer.reset();
|
||||
_ = try engine.renderTpl(arena.allocator(), simple_1_tpl, data);
|
||||
total_ns += timer.read();
|
||||
}
|
||||
|
||||
printResult("simple-1", total_ns, 9);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// simple-2
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const simple_2_tpl =
|
||||
\\div
|
||||
\\ h1.header #{header}
|
||||
\\ h2.header2 #{header2}
|
||||
\\ h3.header3 #{header3}
|
||||
\\ h4.header4 #{header4}
|
||||
\\ h5.header5 #{header5}
|
||||
\\ h6.header6 #{header6}
|
||||
\\ ul.list
|
||||
\\ each item in list
|
||||
\\ li.item #{item}
|
||||
;
|
||||
|
||||
test "bench: simple-2" {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
const engine = pugz.ViewEngine.init(.{});
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
|
||||
defer arena.deinit();
|
||||
|
||||
var total_ns: u64 = 0;
|
||||
var timer = try std.time.Timer.start();
|
||||
const data = .{
|
||||
.header = "Header",
|
||||
.header2 = "Header2",
|
||||
.header3 = "Header3",
|
||||
.header4 = "Header4",
|
||||
.header5 = "Header5",
|
||||
.header6 = "Header6",
|
||||
.list = &[_][]const u8{ "1000000000", "2", "3", "4", "5", "6", "7", "8", "9", "10" },
|
||||
};
|
||||
|
||||
for (0..iterations) |_| {
|
||||
_ = arena.reset(.retain_capacity);
|
||||
timer.reset();
|
||||
_ = try engine.renderTpl(arena.allocator(), simple_2_tpl, data);
|
||||
total_ns += timer.read();
|
||||
}
|
||||
|
||||
printResult("simple-2", total_ns, 9);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// if-expression
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const if_expression_tpl =
|
||||
\\each account in accounts
|
||||
\\ div
|
||||
\\ if account.status == "closed"
|
||||
\\ div Your account has been closed!
|
||||
\\ if account.status == "suspended"
|
||||
\\ div Your account has been temporarily suspended
|
||||
\\ if account.status == "open"
|
||||
\\ div
|
||||
\\ | Bank balance:
|
||||
\\ if account.negative
|
||||
\\ span.negative= account.balanceFormatted
|
||||
\\ else
|
||||
\\ span.positive= account.balanceFormatted
|
||||
;
|
||||
|
||||
const Account = struct {
|
||||
balance: i32,
|
||||
balanceFormatted: []const u8,
|
||||
status: []const u8,
|
||||
negative: bool,
|
||||
};
|
||||
|
||||
test "bench: if-expression" {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
const engine = pugz.ViewEngine.init(.{});
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
|
||||
defer arena.deinit();
|
||||
|
||||
var total_ns: u64 = 0;
|
||||
var timer = try std.time.Timer.start();
|
||||
const data = .{
|
||||
.accounts = &[_]Account{
|
||||
.{ .balance = 0, .balanceFormatted = "$0.00", .status = "open", .negative = false },
|
||||
.{ .balance = 10, .balanceFormatted = "$10.00", .status = "closed", .negative = false },
|
||||
.{ .balance = -100, .balanceFormatted = "$-100.00", .status = "suspended", .negative = true },
|
||||
.{ .balance = 999, .balanceFormatted = "$999.00", .status = "open", .negative = false },
|
||||
},
|
||||
};
|
||||
|
||||
for (0..iterations) |_| {
|
||||
_ = arena.reset(.retain_capacity);
|
||||
timer.reset();
|
||||
_ = try engine.renderTpl(arena.allocator(), if_expression_tpl, data);
|
||||
total_ns += timer.read();
|
||||
}
|
||||
|
||||
printResult("if-expression", total_ns, 12);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// projects-escaped
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const projects_escaped_tpl =
|
||||
\\doctype html
|
||||
\\html
|
||||
\\ head
|
||||
\\ title #{title}
|
||||
\\ body
|
||||
\\ p #{text}
|
||||
\\ each project in projects
|
||||
\\ a(href=project.url) #{project.name}
|
||||
\\ p #{project.description}
|
||||
\\ else
|
||||
\\ p No projects
|
||||
;
|
||||
|
||||
const Project = struct {
|
||||
name: []const u8,
|
||||
url: []const u8,
|
||||
description: []const u8,
|
||||
};
|
||||
|
||||
test "bench: projects-escaped" {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
const engine = pugz.ViewEngine.init(.{});
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
|
||||
defer arena.deinit();
|
||||
|
||||
var total_ns: u64 = 0;
|
||||
var timer = try std.time.Timer.start();
|
||||
const data = .{
|
||||
.title = "Projects",
|
||||
.text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>",
|
||||
.projects = &[_]Project{
|
||||
.{ .name = "<strong>Facebook</strong>", .url = "http://facebook.com", .description = "Social network" },
|
||||
.{ .name = "<strong>Google</strong>", .url = "http://google.com", .description = "Search engine" },
|
||||
.{ .name = "<strong>Twitter</strong>", .url = "http://twitter.com", .description = "Microblogging service" },
|
||||
.{ .name = "<strong>Amazon</strong>", .url = "http://amazon.com", .description = "Online retailer" },
|
||||
.{ .name = "<strong>eBay</strong>", .url = "http://ebay.com", .description = "Online auction" },
|
||||
.{ .name = "<strong>Wikipedia</strong>", .url = "http://wikipedia.org", .description = "A free encyclopedia" },
|
||||
.{ .name = "<strong>LiveJournal</strong>", .url = "http://livejournal.com", .description = "Blogging platform" },
|
||||
},
|
||||
};
|
||||
|
||||
for (0..iterations) |_| {
|
||||
_ = arena.reset(.retain_capacity);
|
||||
timer.reset();
|
||||
_ = try engine.renderTpl(arena.allocator(), projects_escaped_tpl, data);
|
||||
total_ns += timer.read();
|
||||
}
|
||||
|
||||
printResult("projects-escaped", total_ns, 86);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// search-results
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Simplified to match original JS benchmark template exactly
|
||||
const search_results_tpl =
|
||||
\\.search-results.view-gallery
|
||||
\\ each searchRecord in searchRecords
|
||||
\\ .search-item
|
||||
\\ .search-item-container.drop-shadow
|
||||
\\ .img-container
|
||||
\\ img(src=searchRecord.imgUrl)
|
||||
\\ h4.title
|
||||
\\ a(href=searchRecord.viewItemUrl)= searchRecord.title
|
||||
\\ | #{searchRecord.description}
|
||||
\\ if searchRecord.featured
|
||||
\\ div Featured!
|
||||
\\ if searchRecord.sizes
|
||||
\\ div
|
||||
\\ | Sizes available:
|
||||
\\ ul
|
||||
\\ each size in searchRecord.sizes
|
||||
\\ li= size
|
||||
;
|
||||
|
||||
const SearchRecord = struct {
|
||||
imgUrl: []const u8,
|
||||
viewItemUrl: []const u8,
|
||||
title: []const u8,
|
||||
description: []const u8,
|
||||
featured: bool,
|
||||
sizes: ?[]const []const u8,
|
||||
};
|
||||
|
||||
test "bench: search-results" {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
const engine = pugz.ViewEngine.init(.{});
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
|
||||
defer arena.deinit();
|
||||
|
||||
const sizes = &[_][]const u8{ "S", "M", "L", "XL", "XXL" };
|
||||
|
||||
// Long descriptions matching original benchmark (Lorem ipsum paragraphs)
|
||||
const desc1 = "Duis laborum nostrud consectetur exercitation minim ad laborum velit adipisicing. Dolore adipisicing pariatur in fugiat nulla voluptate aliquip esse laboris quis exercitation aliqua labore.";
|
||||
const desc2 = "Incididunt ea mollit commodo velit officia. Enim officia occaecat nulla aute. Esse sunt laborum excepteur sint elit sit esse ad.";
|
||||
const desc3 = "Aliquip Lorem consequat sunt ipsum dolor amet amet cupidatat deserunt eiusmod qui anim cillum sint. Dolor exercitation tempor aliquip sunt nisi ipsum ullamco adipisicing.";
|
||||
const desc4 = "Est ad amet irure veniam dolore velit amet irure fugiat ut elit. Tempor fugiat dolor tempor aute enim. Ad sint mollit laboris id sint ullamco eu do irure nostrud magna sunt voluptate.";
|
||||
const desc5 = "Sunt ex magna culpa cillum esse irure consequat Lorem aliquip enim sit reprehenderit sunt. Exercitation esse irure magna proident ex ut elit magna mollit aliqua amet.";
|
||||
|
||||
var total_ns: u64 = 0;
|
||||
var timer = try std.time.Timer.start();
|
||||
const data = .{
|
||||
.searchRecords = &[_]SearchRecord{
|
||||
.{ .imgUrl = "img1.jpg", .viewItemUrl = "http://foo/1", .title = "Namebox", .description = desc1, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img2.jpg", .viewItemUrl = "http://foo/2", .title = "Arctiq", .description = desc2, .featured = false, .sizes = sizes },
|
||||
.{ .imgUrl = "img3.jpg", .viewItemUrl = "http://foo/3", .title = "Niquent", .description = desc3, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img4.jpg", .viewItemUrl = "http://foo/4", .title = "Remotion", .description = desc4, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img5.jpg", .viewItemUrl = "http://foo/5", .title = "Octocore", .description = desc5, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img6.jpg", .viewItemUrl = "http://foo/6", .title = "Spherix", .description = desc1, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img7.jpg", .viewItemUrl = "http://foo/7", .title = "Quarex", .description = desc2, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img8.jpg", .viewItemUrl = "http://foo/8", .title = "Supremia", .description = desc3, .featured = false, .sizes = sizes },
|
||||
.{ .imgUrl = "img9.jpg", .viewItemUrl = "http://foo/9", .title = "Amtap", .description = desc4, .featured = false, .sizes = sizes },
|
||||
.{ .imgUrl = "img10.jpg", .viewItemUrl = "http://foo/10", .title = "Qiao", .description = desc5, .featured = false, .sizes = sizes },
|
||||
.{ .imgUrl = "img11.jpg", .viewItemUrl = "http://foo/11", .title = "Pushcart", .description = desc1, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img12.jpg", .viewItemUrl = "http://foo/12", .title = "Eweville", .description = desc2, .featured = false, .sizes = sizes },
|
||||
.{ .imgUrl = "img13.jpg", .viewItemUrl = "http://foo/13", .title = "Senmei", .description = desc3, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img14.jpg", .viewItemUrl = "http://foo/14", .title = "Maximind", .description = desc4, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img15.jpg", .viewItemUrl = "http://foo/15", .title = "Blurrybus", .description = desc5, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img16.jpg", .viewItemUrl = "http://foo/16", .title = "Virva", .description = desc1, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img17.jpg", .viewItemUrl = "http://foo/17", .title = "Centregy", .description = desc2, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img18.jpg", .viewItemUrl = "http://foo/18", .title = "Dancerity", .description = desc3, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img19.jpg", .viewItemUrl = "http://foo/19", .title = "Oceanica", .description = desc4, .featured = true, .sizes = sizes },
|
||||
.{ .imgUrl = "img20.jpg", .viewItemUrl = "http://foo/20", .title = "Synkgen", .description = desc5, .featured = false, .sizes = null },
|
||||
},
|
||||
};
|
||||
|
||||
for (0..iterations) |_| {
|
||||
_ = arena.reset(.retain_capacity);
|
||||
timer.reset();
|
||||
_ = try engine.renderTpl(arena.allocator(), search_results_tpl, data);
|
||||
total_ns += timer.read();
|
||||
}
|
||||
|
||||
printResult("search-results", total_ns, 41);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// friends
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const friends_tpl =
|
||||
\\doctype html
|
||||
\\html(lang="en")
|
||||
\\ head
|
||||
\\ meta(charset="UTF-8")
|
||||
\\ title Friends
|
||||
\\ body
|
||||
\\ div.friends
|
||||
\\ each friend in friends
|
||||
\\ div.friend
|
||||
\\ ul
|
||||
\\ li Name: #{friend.name}
|
||||
\\ li Balance: #{friend.balance}
|
||||
\\ li Age: #{friend.age}
|
||||
\\ li Address: #{friend.address}
|
||||
\\ li Image:
|
||||
\\ img(src=friend.picture)
|
||||
\\ li Company: #{friend.company}
|
||||
\\ li Email:
|
||||
\\ a(href=friend.emailHref) #{friend.email}
|
||||
\\ li About: #{friend.about}
|
||||
\\ if friend.tags
|
||||
\\ li Tags:
|
||||
\\ ul
|
||||
\\ each tag in friend.tags
|
||||
\\ li #{tag}
|
||||
\\ if friend.friends
|
||||
\\ li Friends:
|
||||
\\ ul
|
||||
\\ each subFriend in friend.friends
|
||||
\\ li #{subFriend.name} (#{subFriend.id})
|
||||
;
|
||||
|
||||
const SubFriend = struct {
|
||||
id: i32,
|
||||
name: []const u8,
|
||||
};
|
||||
|
||||
const Friend = struct {
|
||||
name: []const u8,
|
||||
balance: []const u8,
|
||||
age: i32,
|
||||
address: []const u8,
|
||||
picture: []const u8,
|
||||
company: []const u8,
|
||||
email: []const u8,
|
||||
emailHref: []const u8,
|
||||
about: []const u8,
|
||||
tags: ?[]const []const u8,
|
||||
friends: ?[]const SubFriend,
|
||||
};
|
||||
|
||||
test "bench: friends" {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer if (gpa.deinit() == .leak) @panic("leadk");
|
||||
|
||||
const engine = pugz.ViewEngine.init(.{});
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
|
||||
defer arena.deinit();
|
||||
|
||||
const friend_tags = &[_][]const u8{ "id", "amet", "non", "ut", "dolore", "commodo", "consequat" };
|
||||
const sub_friends = &[_]SubFriend{
|
||||
.{ .id = 0, .name = "Gates Lewis" },
|
||||
.{ .id = 1, .name = "Britt Stokes" },
|
||||
.{ .id = 2, .name = "Reed Wade" },
|
||||
};
|
||||
|
||||
var friends_data: [100]Friend = undefined;
|
||||
for (&friends_data, 0..) |*f, i| {
|
||||
f.* = .{
|
||||
.name = "Gardner Alvarez",
|
||||
.balance = "$1,509.00",
|
||||
.age = 30 + @as(i32, @intCast(i % 20)),
|
||||
.address = "282 Lancaster Avenue, Bowden, Kansas, 666",
|
||||
.picture = "http://placehold.it/32x32",
|
||||
.company = "Dentrex",
|
||||
.email = "gardneralvarez@dentrex.com",
|
||||
.emailHref = "mailto:gardneralvarez@dentrex.com",
|
||||
.about = "Minim elit tempor enim voluptate labore do non nisi sint nulla deserunt officia proident excepteur.",
|
||||
.tags = friend_tags,
|
||||
.friends = sub_friends,
|
||||
};
|
||||
}
|
||||
|
||||
var total_ns: u64 = 0;
|
||||
var timer = try std.time.Timer.start();
|
||||
|
||||
for (0..iterations) |_| {
|
||||
_ = arena.reset(.retain_capacity);
|
||||
timer.reset();
|
||||
_ = try engine.renderTpl(arena.allocator(), friends_tpl, .{
|
||||
.friends = &friends_data,
|
||||
});
|
||||
total_ns += timer.read();
|
||||
}
|
||||
|
||||
printResult("friends", total_ns, 110);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Helper
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
fn printResult(name: []const u8, total_ns: u64, pug_ref_ms: f64) void {
|
||||
const total_ms = @as(f64, @floatFromInt(total_ns)) / 1_000_000.0;
|
||||
const avg_us = @as(f64, @floatFromInt(total_ns)) / @as(f64, @floatFromInt(iterations)) / 1_000.0;
|
||||
const speedup = pug_ref_ms / total_ms;
|
||||
|
||||
std.debug.print("\n{s:<20} => {d:>6.1}ms ({d:.2}us/render) | Pug.js: {d:.0}ms | {d:.1}x\n", .{
|
||||
name,
|
||||
total_ms,
|
||||
avg_us,
|
||||
pug_ref_ms,
|
||||
speedup,
|
||||
});
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
const std = @import("std");
|
||||
const pugz = @import("pugz");
|
||||
|
||||
const friends_tpl =
|
||||
\\doctype html
|
||||
\\html(lang="en")
|
||||
\\ head
|
||||
\\ meta(charset="UTF-8")
|
||||
\\ title Friends
|
||||
\\ body
|
||||
\\ div.friends
|
||||
\\ each friend in friends
|
||||
\\ div.friend
|
||||
\\ ul
|
||||
\\ li Name: #{friend.name}
|
||||
\\ li Balance: #{friend.balance}
|
||||
\\ li Age: #{friend.age}
|
||||
\\ li Address: #{friend.address}
|
||||
\\ li Image:
|
||||
\\ img(src=friend.picture)
|
||||
\\ li Company: #{friend.company}
|
||||
\\ li Email:
|
||||
\\ a(href=friend.emailHref) #{friend.email}
|
||||
\\ li About: #{friend.about}
|
||||
\\ if friend.tags
|
||||
\\ li Tags:
|
||||
\\ ul
|
||||
\\ each tag in friend.tags
|
||||
\\ li #{tag}
|
||||
\\ if friend.friends
|
||||
\\ li Friends:
|
||||
\\ ul
|
||||
\\ each subFriend in friend.friends
|
||||
\\ li #{subFriend.name} (#{subFriend.id})
|
||||
;
|
||||
|
||||
const SubFriend = struct { id: i32, name: []const u8 };
|
||||
const Friend = struct {
|
||||
name: []const u8,
|
||||
balance: []const u8,
|
||||
age: i32,
|
||||
address: []const u8,
|
||||
picture: []const u8,
|
||||
company: []const u8,
|
||||
email: []const u8,
|
||||
emailHref: []const u8,
|
||||
about: []const u8,
|
||||
tags: ?[]const []const u8,
|
||||
friends: ?[]const SubFriend,
|
||||
};
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
|
||||
defer arena.deinit();
|
||||
|
||||
const engine = pugz.ViewEngine.init(.{});
|
||||
|
||||
const friend_tags = &[_][]const u8{ "id", "amet", "non", "ut", "dolore", "commodo", "consequat" };
|
||||
const sub_friends = &[_]SubFriend{
|
||||
.{ .id = 0, .name = "Gates Lewis" },
|
||||
.{ .id = 1, .name = "Britt Stokes" },
|
||||
.{ .id = 2, .name = "Reed Wade" },
|
||||
};
|
||||
|
||||
var friends_data: [100]Friend = undefined;
|
||||
for (&friends_data, 0..) |*f, i| {
|
||||
f.* = .{
|
||||
.name = "Gardner Alvarez",
|
||||
.balance = "$1,509.00",
|
||||
.age = 30 + @as(i32, @intCast(i % 20)),
|
||||
.address = "282 Lancaster Avenue, Bowden, Kansas, 666",
|
||||
.picture = "http://placehold.it/32x32",
|
||||
.company = "Dentrex",
|
||||
.email = "gardneralvarez@dentrex.com",
|
||||
.emailHref = "mailto:gardneralvarez@dentrex.com",
|
||||
.about = "Minim elit tempor enim voluptate labore do non nisi sint nulla deserunt officia proident excepteur.",
|
||||
.tags = friend_tags,
|
||||
.friends = sub_friends,
|
||||
};
|
||||
}
|
||||
|
||||
const data = .{ .friends = &friends_data };
|
||||
|
||||
// Warmup
|
||||
for (0..10) |_| {
|
||||
_ = arena.reset(.retain_capacity);
|
||||
_ = try engine.renderTpl(arena.allocator(), friends_tpl, data);
|
||||
}
|
||||
|
||||
// Get output size
|
||||
_ = arena.reset(.retain_capacity);
|
||||
const output = try engine.renderTpl(arena.allocator(), friends_tpl, data);
|
||||
const output_size = output.len;
|
||||
|
||||
// Profile render
|
||||
const iterations: usize = 500;
|
||||
var total_render: u64 = 0;
|
||||
var timer = try std.time.Timer.start();
|
||||
|
||||
for (0..iterations) |_| {
|
||||
_ = arena.reset(.retain_capacity);
|
||||
timer.reset();
|
||||
_ = try engine.renderTpl(arena.allocator(), friends_tpl, data);
|
||||
total_render += timer.read();
|
||||
}
|
||||
|
||||
const avg_render_us = @as(f64, @floatFromInt(total_render)) / @as(f64, @floatFromInt(iterations)) / 1000.0;
|
||||
const total_ms = @as(f64, @floatFromInt(total_render)) / 1_000_000.0;
|
||||
|
||||
// Header
|
||||
std.debug.print("\n", .{});
|
||||
std.debug.print("╔══════════════════════════════════════════════════════════════╗\n", .{});
|
||||
std.debug.print("║ FRIENDS TEMPLATE CPU PROFILE ║\n", .{});
|
||||
std.debug.print("╠══════════════════════════════════════════════════════════════╣\n", .{});
|
||||
std.debug.print("║ Iterations: {d:<6} Output size: {d:<6} bytes ║\n", .{ iterations, output_size });
|
||||
std.debug.print("╚══════════════════════════════════════════════════════════════╝\n\n", .{});
|
||||
|
||||
// Results
|
||||
std.debug.print("┌────────────────────────────────────┬─────────────────────────┐\n", .{});
|
||||
std.debug.print("│ Metric │ Value │\n", .{});
|
||||
std.debug.print("├────────────────────────────────────┼─────────────────────────┤\n", .{});
|
||||
std.debug.print("│ Total time │ {d:>10.1} ms │\n", .{total_ms});
|
||||
std.debug.print("│ Avg per render │ {d:>10.1} µs │\n", .{avg_render_us});
|
||||
std.debug.print("│ Renders/sec │ {d:>10.0} │\n", .{1_000_000.0 / avg_render_us});
|
||||
std.debug.print("└────────────────────────────────────┴─────────────────────────┘\n", .{});
|
||||
|
||||
// Template complexity breakdown
|
||||
std.debug.print("\n📋 Template Complexity:\n", .{});
|
||||
std.debug.print(" • 100 friends (outer loop)\n", .{});
|
||||
std.debug.print(" • 7 tags per friend (nested loop) = 700 tag iterations\n", .{});
|
||||
std.debug.print(" • 3 sub-friends per friend (nested loop) = 300 sub-friend iterations\n", .{});
|
||||
std.debug.print(" • Total loop iterations: 100 + 700 + 300 = 1,100\n", .{});
|
||||
std.debug.print(" • ~10 interpolations per friend = 1,000+ variable lookups\n", .{});
|
||||
std.debug.print(" • 2 conditionals per friend = 200 conditional evaluations\n", .{});
|
||||
|
||||
// Cost breakdown estimate
|
||||
const loop_iterations: f64 = 1100;
|
||||
const var_lookups: f64 = 1500; // approximate
|
||||
|
||||
std.debug.print("\n💡 Estimated Cost Breakdown (per render):\n", .{});
|
||||
std.debug.print(" Total: {d:.1} µs\n", .{avg_render_us});
|
||||
std.debug.print(" Per loop iteration: ~{d:.2} µs ({d:.0} iterations)\n", .{ avg_render_us / loop_iterations, loop_iterations });
|
||||
std.debug.print(" Per variable lookup: ~{d:.3} µs ({d:.0} lookups)\n", .{ avg_render_us / var_lookups, var_lookups });
|
||||
|
||||
// Comparison
|
||||
std.debug.print("\n📊 Comparison with Pug.js:\n", .{});
|
||||
const pugjs_us: f64 = 55.0; // From benchmark: 110ms / 2000 = 55µs
|
||||
std.debug.print(" Pug.js: {d:.1} µs/render\n", .{pugjs_us});
|
||||
std.debug.print(" Pugz: {d:.1} µs/render\n", .{avg_render_us});
|
||||
const ratio = avg_render_us / pugjs_us;
|
||||
if (ratio > 1.0) {
|
||||
std.debug.print(" Status: Pugz is {d:.1}x SLOWER\n", .{ratio});
|
||||
} else {
|
||||
std.debug.print(" Status: Pugz is {d:.1}x FASTER\n", .{1.0 / ratio});
|
||||
}
|
||||
|
||||
std.debug.print("\nKey Bottlenecks (likely):\n", .{});
|
||||
std.debug.print(" 1. Data conversion: Zig struct -> pugz.Value (comptime reflection)\n", .{});
|
||||
std.debug.print(" 2. Variable lookup: HashMap get() for each interpolation\n", .{});
|
||||
std.debug.print(" 3. AST traversal: Walking tree nodes vs Pug.js compiled JS functions\n", .{});
|
||||
std.debug.print(" 4. Loop scope: Creating/clearing scope per loop iteration\n", .{});
|
||||
|
||||
std.debug.print("\nAlready optimized:\n", .{});
|
||||
std.debug.print(" - Scope pooling (reuse hashmap capacity)\n", .{});
|
||||
std.debug.print(" - Batched HTML escaping\n", .{});
|
||||
std.debug.print(" - Arena allocator with retain_capacity\n", .{});
|
||||
}
|
||||
Reference in New Issue
Block a user