171 lines
7.9 KiB
Zig
171 lines
7.9 KiB
Zig
|
|
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", .{});
|
||
|
|
}
|