Files
pugz/benchmarks/bench.zig

230 lines
11 KiB
Zig
Raw Normal View History

2026-01-24 23:53:19 +05:30
//! Pugz Benchmark - Template Rendering
2026-01-22 11:10:47 +05:30
//!
//! Two benchmark modes:
//! 1. Cached AST: Parse once, render 2000 times (matches Pug.js behavior)
//! 2. No Cache: Parse + render on every iteration
2026-01-22 11:10:47 +05:30
//!
//! Run: zig build bench
2026-01-22 11:10:47 +05:30
const std = @import("std");
2026-01-24 23:53:19 +05:30
const pugz = @import("pugz");
2026-01-22 11:10:47 +05:30
const iterations: usize = 2000;
const templates_dir = "benchmarks/templates";
2026-01-22 11:10:47 +05:30
// 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,
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Load JSON data
var data_arena = std.heap.ArenaAllocator.init(allocator);
defer data_arena.deinit();
const data_alloc = data_arena.allocator();
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");
// Load template sources
2026-01-24 23:53:19 +05:30
const simple0_tpl = try loadTemplate(data_alloc, "simple-0.pug");
const simple1_tpl = try loadTemplate(data_alloc, "simple-1.pug");
const simple2_tpl = try loadTemplate(data_alloc, "simple-2.pug");
const if_expr_tpl = try loadTemplate(data_alloc, "if-expression.pug");
const projects_tpl = try loadTemplate(data_alloc, "projects-escaped.pug");
const search_tpl = try loadTemplate(data_alloc, "search-results.pug");
const friends_tpl = try loadTemplate(data_alloc, "friends.pug");
// ═══════════════════════════════════════════════════════════════════════
// Benchmark 1: Cached AST (parse once, render many)
// ═══════════════════════════════════════════════════════════════════════
std.debug.print("\n", .{});
std.debug.print("╔═══════════════════════════════════════════════════════════════╗\n", .{});
std.debug.print("║ Pugz Benchmark - CACHED AST ({d} iterations) ║\n", .{iterations});
std.debug.print("║ Mode: Parse once, render many (like Pug.js) ║\n", .{});
std.debug.print("╚═══════════════════════════════════════════════════════════════╝\n", .{});
std.debug.print("\nParsing templates...\n", .{});
const simple0_ast = try pugz.template.parse(data_alloc, simple0_tpl);
const simple1_ast = try pugz.template.parse(data_alloc, simple1_tpl);
const simple2_ast = try pugz.template.parse(data_alloc, simple2_tpl);
const if_expr_ast = try pugz.template.parse(data_alloc, if_expr_tpl);
const projects_ast = try pugz.template.parse(data_alloc, projects_tpl);
const search_ast = try pugz.template.parse(data_alloc, search_tpl);
const friends_ast = try pugz.template.parse(data_alloc, friends_tpl);
std.debug.print("Starting benchmark (render only)...\n\n", .{});
2026-01-22 11:10:47 +05:30
var total_cached: f64 = 0;
total_cached += try benchCached("simple-0", allocator, simple0_ast, simple0);
total_cached += try benchCached("simple-1", allocator, simple1_ast, simple1);
total_cached += try benchCached("simple-2", allocator, simple2_ast, simple2);
total_cached += try benchCached("if-expression", allocator, if_expr_ast, if_expr);
total_cached += try benchCached("projects-escaped", allocator, projects_ast, projects);
total_cached += try benchCached("search-results", allocator, search_ast, search);
total_cached += try benchCached("friends", allocator, friends_ast, friends_data);
2026-01-22 11:10:47 +05:30
std.debug.print("\n", .{});
std.debug.print(" {s:<20} => {d:>7.1}ms\n", .{ "TOTAL (cached)", total_cached });
std.debug.print("\n", .{});
2026-01-22 11:10:47 +05:30
// ═══════════════════════════════════════════════════════════════════════
// Benchmark 2: No Cache (parse + render every time)
// ═══════════════════════════════════════════════════════════════════════
std.debug.print("\n", .{});
std.debug.print("╔═══════════════════════════════════════════════════════════════╗\n", .{});
std.debug.print("║ Pugz Benchmark - NO CACHE ({d} iterations) ║\n", .{iterations});
std.debug.print("║ Mode: Parse + render every iteration ║\n", .{});
std.debug.print("╚═══════════════════════════════════════════════════════════════╝\n", .{});
std.debug.print("\nStarting benchmark (parse + render)...\n\n", .{});
var total_nocache: f64 = 0;
total_nocache += try benchNoCache("simple-0", allocator, simple0_tpl, simple0);
total_nocache += try benchNoCache("simple-1", allocator, simple1_tpl, simple1);
total_nocache += try benchNoCache("simple-2", allocator, simple2_tpl, simple2);
total_nocache += try benchNoCache("if-expression", allocator, if_expr_tpl, if_expr);
total_nocache += try benchNoCache("projects-escaped", allocator, projects_tpl, projects);
total_nocache += try benchNoCache("search-results", allocator, search_tpl, search);
total_nocache += try benchNoCache("friends", allocator, friends_tpl, friends_data);
std.debug.print("\n", .{});
std.debug.print(" {s:<20} => {d:>7.1}ms\n", .{ "TOTAL (no cache)", total_nocache });
2026-01-22 11:10:47 +05:30
std.debug.print("\n", .{});
// ═══════════════════════════════════════════════════════════════════════
// Summary
// ═══════════════════════════════════════════════════════════════════════
std.debug.print("╔═══════════════════════════════════════════════════════════════╗\n", .{});
std.debug.print("║ SUMMARY ║\n", .{});
std.debug.print("╚═══════════════════════════════════════════════════════════════╝\n", .{});
std.debug.print(" Cached AST (render only): {d:>7.1}ms\n", .{total_cached});
std.debug.print(" No Cache (parse+render): {d:>7.1}ms\n", .{total_nocache});
std.debug.print(" Parse overhead: {d:>7.1}ms ({d:.1}%)\n", .{
total_nocache - total_cached,
((total_nocache - total_cached) / total_nocache) * 100.0,
});
2026-01-22 11:10:47 +05:30
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;
}
2026-01-24 23:53:19 +05:30
fn loadTemplate(alloc: std.mem.Allocator, comptime filename: []const u8) ![]const u8 {
const path = templates_dir ++ "/" ++ filename;
return try std.fs.cwd().readFileAlloc(alloc, path, 1 * 1024 * 1024);
}
// Benchmark with cached AST (render only)
fn benchCached(
2026-01-22 11:10:47 +05:30
name: []const u8,
allocator: std.mem.Allocator,
ast: *pugz.parser.Node,
2026-01-22 11:10:47 +05:30
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);
_ = pugz.template.renderAst(arena.allocator(), ast, data) catch |err| {
2026-01-24 23:53:19 +05:30
std.debug.print(" {s:<20} => ERROR: {}\n", .{ name, err });
return 0;
};
2026-01-22 11:10:47 +05:30
}
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;
}
// Benchmark without cache (parse + render every iteration)
fn benchNoCache(
name: []const u8,
allocator: std.mem.Allocator,
source: []const u8,
data: anytype,
) !f64 {
var timer = try std.time.Timer.start();
for (0..iterations) |_| {
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
_ = pugz.template.renderWithData(arena.allocator(), source, data) catch |err| {
std.debug.print(" {s:<20} => ERROR: {}\n", .{ name, err });
return 0;
};
}
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;
}