Added cross type overhead benchmark
This commit is contained in:
parent
bbab41008f
commit
01c5b50b95
136
src/Quantity.zig
136
src/Quantity.zig
@ -652,3 +652,139 @@ test "Overhead Analysis: Quantity vs Native" {
|
|||||||
std.debug.print("└───────────┴──────┴───────────┴───────────┴───────────┘\n", .{});
|
std.debug.print("└───────────┴──────┴───────────┴───────────┴───────────┘\n", .{});
|
||||||
try std.testing.expect(gsink != 0);
|
try std.testing.expect(gsink != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Cross-Type Overhead Analysis: Quantity vs Native" {
|
||||||
|
const Io = std.Io;
|
||||||
|
const ITERS: usize = 100_000;
|
||||||
|
const SAMPLES: usize = 5;
|
||||||
|
const io = std.testing.io;
|
||||||
|
|
||||||
|
const getTime = struct {
|
||||||
|
fn f(i: Io) Io.Timestamp {
|
||||||
|
return Io.Clock.awake.now(i);
|
||||||
|
}
|
||||||
|
}.f;
|
||||||
|
|
||||||
|
const fold = struct {
|
||||||
|
fn f(comptime TT: type, s: *f64, v: TT) void {
|
||||||
|
s.* += if (comptime @typeInfo(TT) == .float)
|
||||||
|
@as(f64, @floatCast(v))
|
||||||
|
else
|
||||||
|
@as(f64, @floatFromInt(v));
|
||||||
|
}
|
||||||
|
}.f;
|
||||||
|
|
||||||
|
const getValT = struct {
|
||||||
|
fn f(comptime TT: type, i: usize) TT {
|
||||||
|
// Keep values safe and non-zero to avoid division by zero or overflows during cross-casting
|
||||||
|
const v = (i % 50) + 1;
|
||||||
|
return if (comptime @typeInfo(TT) == .float) @floatFromInt(v) else @intCast(v);
|
||||||
|
}
|
||||||
|
}.f;
|
||||||
|
|
||||||
|
// Helper for the Native baseline: explicitly casting T2 to T1 before the operation
|
||||||
|
const castTo = struct {
|
||||||
|
fn f(comptime DestT: type, comptime SrcT: type, val: SrcT) DestT {
|
||||||
|
if (comptime DestT == SrcT) return val;
|
||||||
|
const src_info = @typeInfo(SrcT);
|
||||||
|
const dest_info = @typeInfo(DestT);
|
||||||
|
|
||||||
|
if (dest_info == .int and src_info == .int) return @intCast(val);
|
||||||
|
if (dest_info == .float and src_info == .int) return @floatFromInt(val);
|
||||||
|
if (dest_info == .int and src_info == .float) return @intFromFloat(val);
|
||||||
|
if (dest_info == .float and src_info == .float) return @floatCast(val);
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
}.f;
|
||||||
|
|
||||||
|
const Types = .{ i16, i64, i128, f32, f64 };
|
||||||
|
const TNames = .{ "i16", "i64", "i128", "f32", "f64" };
|
||||||
|
const Ops = .{ "add", "mulBy", "divBy" };
|
||||||
|
|
||||||
|
var gsink: f64 = 0;
|
||||||
|
|
||||||
|
std.debug.print(
|
||||||
|
\\
|
||||||
|
\\ Cross-Type Overhead Analysis: Quantity vs Native
|
||||||
|
\\
|
||||||
|
\\┌─────────┬──────┬──────┬───────────┬───────────┬───────────┐
|
||||||
|
\\│ Op │ T1 │ T2 │ Native │ Quantity │ Slowdown │
|
||||||
|
\\├─────────┼──────┼──────┼───────────┼───────────┼───────────┤
|
||||||
|
\\
|
||||||
|
, .{});
|
||||||
|
|
||||||
|
inline for (Ops, 0..) |op_name, j| {
|
||||||
|
inline for (Types, 0..) |T1, t1_idx| {
|
||||||
|
inline for (Types, 0..) |T2, t2_idx| {
|
||||||
|
var native_total_ns: f64 = 0;
|
||||||
|
var quantity_total_ns: f64 = 0;
|
||||||
|
|
||||||
|
const M1 = Quantity(T1, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||||
|
const M2 = Quantity(T2, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||||
|
const S2 = Quantity(T2, Dimensions.init(.{ .T = 1 }), Scales.init(.{}));
|
||||||
|
|
||||||
|
for (0..SAMPLES) |_| {
|
||||||
|
// --- 1. Benchmark Native (Cast T2 to T1, then math) ---
|
||||||
|
var n_sink: T1 = 0;
|
||||||
|
const n_start = getTime(io);
|
||||||
|
for (0..ITERS) |i| {
|
||||||
|
const a = getValT(T1, i);
|
||||||
|
const b_raw = getValT(T2, 2);
|
||||||
|
const b = castTo(T1, T2, b_raw);
|
||||||
|
|
||||||
|
const r = if (comptime std.mem.eql(u8, op_name, "add"))
|
||||||
|
a + b
|
||||||
|
else if (comptime std.mem.eql(u8, op_name, "mulBy"))
|
||||||
|
a * b
|
||||||
|
else if (comptime @typeInfo(T1) == .int)
|
||||||
|
@divTrunc(a, b)
|
||||||
|
else
|
||||||
|
a / b;
|
||||||
|
|
||||||
|
if (comptime @typeInfo(T1) == .float) n_sink += r else n_sink ^= r;
|
||||||
|
}
|
||||||
|
const n_end = getTime(io);
|
||||||
|
native_total_ns += @as(f64, @floatFromInt(n_start.durationTo(n_end).toNanoseconds()));
|
||||||
|
fold(T1, &gsink, n_sink);
|
||||||
|
|
||||||
|
// --- 2. Benchmark Quantity ---
|
||||||
|
var q_sink: T1 = 0;
|
||||||
|
const q_start = getTime(io);
|
||||||
|
for (0..ITERS) |i| {
|
||||||
|
const qa = M1{ .value = getValT(T1, i) };
|
||||||
|
const qb = if (comptime std.mem.eql(u8, op_name, "divBy"))
|
||||||
|
S2{ .value = getValT(T2, 2) }
|
||||||
|
else
|
||||||
|
M2{ .value = getValT(T2, 2) };
|
||||||
|
|
||||||
|
const r = if (comptime std.mem.eql(u8, op_name, "add"))
|
||||||
|
qa.add(qb)
|
||||||
|
else if (comptime std.mem.eql(u8, op_name, "mulBy"))
|
||||||
|
qa.mulBy(qb)
|
||||||
|
else
|
||||||
|
qa.divBy(qb);
|
||||||
|
|
||||||
|
if (comptime @typeInfo(T1) == .float) q_sink += r.value else q_sink ^= r.value;
|
||||||
|
}
|
||||||
|
const q_end = getTime(io);
|
||||||
|
quantity_total_ns += @as(f64, @floatFromInt(q_start.durationTo(q_end).toNanoseconds()));
|
||||||
|
fold(T1, &gsink, q_sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
const avg_n = (native_total_ns / SAMPLES) / @as(f64, @floatFromInt(ITERS));
|
||||||
|
const avg_q = (quantity_total_ns / SAMPLES) / @as(f64, @floatFromInt(ITERS));
|
||||||
|
const slowdown = avg_q / avg_n;
|
||||||
|
|
||||||
|
std.debug.print("│ {s:<7} │ {s:<4} │ {s:<4} │ {d:>7.2}ns │ {d:>7.2}ns │ {d:>8.2}x │\n", .{
|
||||||
|
op_name, TNames[t1_idx], TNames[t2_idx], avg_n, avg_q, slowdown,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (j != Ops.len - 1) {
|
||||||
|
std.debug.print("├─────────┼──────┼──────┼───────────┼───────────┼───────────┤\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.print("└─────────┴──────┴──────┴───────────┴───────────┴───────────┘\n", .{});
|
||||||
|
try std.testing.expect(gsink != 0);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user