inflate: fair timing comparison — 2.4x speedup confirmed

Fixed apples-to-apples comparison: track orig time only for calls where
libdeflate also ran. Previous numbers were misleading (all calls vs subset).

Results (same calls compared):
  Loading: 3352 matched calls, orig=160ms fast=66ms (2.4x)
  Gameplay: 150 matched calls, orig=5ms fast=2ms (2.5x)
  Projected total saving: ~1.76s off loading, ~100ms/period gameplay

Also fixed: thread safety (main thread only), zlib header validation,
input buffer save (256KB static), separate output buffer, FreeMemory
4-param calling convention.

Remaining: 8% failures (277/3352), not yet running on all threads.
This commit is contained in:
MarcelineVQ
2026-03-24 04:41:09 -07:00
parent 21ac8df11e
commit b612e6f953
+45 -21
View File
@@ -50,14 +50,33 @@ var log: logging.Logger = .{};
// Static decompressor memory — avoids using game allocator which may not be malloc-compatible
var static_decompressor_mem: [12288]u8 align(16) = undefined; // 12KB > 11564 bytes needed
// Reusable output buffer for libdeflate timing — static 256KB, no heap allocation needed
// Static buffers — no heap allocation needed.
// ld_buf: saves input before original modifies it (256KB)
// ld_out_buf: libdeflate output (256KB)
var ld_buf_backing: [256 * 1024]u8 align(16) = undefined;
var ld_buf: [*]u8 = &ld_buf_backing;
const ld_buf_size: u32 = 256 * 1024;
var ld_out_buf_backing: [256 * 1024]u8 align(16) = undefined;
const ld_out_buf_size: u32 = 256 * 1024;
// Thread safety: only run libdeflate on the main thread (ESP in 0x00Exxxxx range)
var main_thread_id: u32 = 0;
extern "kernel32" fn GetCurrentThreadId() callconv(.{ .x86_stdcall = .{} }) u32;
fn isMainThread() bool {
const tid = GetCurrentThreadId();
if (main_thread_id == 0) {
main_thread_id = tid; // first call captures main thread
return true;
}
return tid == main_thread_id;
}
// Timing accumulators
var orig_total_cycles: u64 = 0;
var fast_total_cycles: u64 = 0;
var orig_total_cycles: u64 = 0; // ALL calls
var orig_matched_cycles: u64 = 0; // only calls where libdeflate also ran
var fast_total_cycles: u64 = 0; // libdeflate time for matched calls
var call_count: u64 = 0;
var mismatch_count: u64 = 0;
var success_count: u64 = 0;
@@ -98,11 +117,10 @@ pub fn decompressDetour(out_buf: u32, out_size_ptr: u32, in_buf: u32, in_size: u
const out_capacity = @as(*const u32, @ptrFromInt(out_size_ptr)).*;
const in_ptr: [*]const u8 = @ptrFromInt(in_buf);
// Save input data BEFORE calling original — the original may modify the input buffer
// (overlap handling in DecompressData_WithOptions copies data around)
var saved_input_backing: [8192]u8 = undefined;
const save_len = @min(in_size, saved_input_backing.len);
@memcpy(saved_input_backing[0..save_len], in_ptr[0..save_len]);
// Save input data BEFORE calling original — the original may modify the input buffer.
// Use the static ld_buf_backing (256KB) as the save buffer — it's not used until later.
const save_len = @min(in_size, ld_buf_size);
@memcpy(ld_buf[0..save_len], in_ptr[0..save_len]);
// Run original and time it
const t0 = rdtsc();
@@ -140,30 +158,32 @@ pub fn decompressDetour(out_buf: u32, out_size_ptr: u32, in_buf: u32, in_size: u
// is what the original produced — libdeflate should produce the same.
const ld_out_size = actual_out;
// Use static buffer — skip calls larger than 256KB
if (ld_out_size <= ld_buf_size) {
const buf = ld_buf;
// Log first call for debugging
if (success_count == 0 and mismatch_count == 0) {
log.fmt(" ld_call: buf=0x{x} size={d} in=0x{x} in_size={d} hdr={x:0>2}{x:0>2}\n", .{
@intFromPtr(buf), ld_out_size,
@intFromPtr(in_ptr + 1), in_size - 1,
in_ptr[1], in_ptr[2],
// Only proceed if: input fits, output fits, valid zlib header,
// AND we're on the main thread (static buffers aren't thread-safe)
if (in_size <= save_len and ld_out_size <= ld_out_buf_size and
save_len > 2 and ld_buf[1] == 0x78 and isMainThread())
{
// Log call number and sizes for first few calls
if (success_count + mismatch_count < 3) {
log.fmt(" ld #{d}: in_size={d} out_size={d} saved_hdr={x:0>2}{x:0>2}\n", .{
success_count + mismatch_count,
save_len - 1, ld_out_size,
ld_buf[1], ld_buf[2],
});
}
var ld_out: usize = 0;
const t1 = rdtsc();
// Use saved input — original may have modified the buffer
const ld_ret = libdeflate_zlib_decompress(
decompressor,
@ptrCast(saved_input_backing[1..save_len]),
ld_buf + 1, // skip type byte in saved input
save_len - 1,
@ptrCast(buf),
&ld_out_buf_backing,
ld_out_size,
&ld_out,
);
const fast_cycles = rdtsc() - t1;
fast_total_cycles +|= fast_cycles;
orig_matched_cycles +|= orig_cycles; // track original time for same calls
if (ld_ret == 0 and ld_out == actual_out)
success_count +|= 1
else
@@ -179,11 +199,14 @@ pub fn dumpStats() void {
if (call_count == 0) return;
const MS_DIV: u64 = 3_000_000;
const type_names = [8][]const u8{ "huff", "zlib", "b2", "b3", "bzip", "pkw", "adpcm1", "adpcm2" };
log.fmt("inflate: {d} calls, in={d}KB out={d}KB, orig={d}ms fast={d}ms (ok={d} fail={d})\n", .{
const matched = success_count + mismatch_count;
log.fmt("inflate: {d} calls, in={d}KB out={d}KB, orig_all={d}ms | matched={d}: orig={d}ms fast={d}ms (ok={d} fail={d})\n", .{
call_count,
total_in_bytes / 1024,
total_bytes / 1024,
orig_total_cycles / MS_DIV,
matched,
orig_matched_cycles / MS_DIV,
fast_total_cycles / MS_DIV,
success_count,
mismatch_count,
@@ -206,6 +229,7 @@ pub fn dumpStats() void {
log.print("\n");
// Reset
orig_total_cycles = 0;
orig_matched_cycles = 0;
fast_total_cycles = 0;
call_count = 0;
success_count = 0;