From 83ab9e10f7c0f17805e40a9a3d2b4a3357e5280f Mon Sep 17 00:00:00 2001 From: root Date: Mon, 15 Jun 2026 00:16:41 +0000 Subject: [PATCH] perf: extend the compiled-module disk cache to per-unit modules Follow-up to 560290c. Per-unit .wasm modules were still Cranelift-compiled per worker on first use (~40-70ms each), so with 8-call worker recycling fresh workers re-JIT every unit they touch. Refactor the core cache logic into a shared WasmWorker helper: - cached_wasm_path(p): maps <...>.wasm -> <...>.cwasm. - load_or_compile_cached_module(engine, cached, wasm, bytes, err): deserialize_file the .cwasm when it is newer than the .wasm; otherwise Module::compile + serialize, written atomically (temp+rename); deserialize failure falls back to compile. Both the core module load and unit_module() now go through this helper, so unit artifacts get the same .uce.cwasm cache the core got. Independently verified on the host: fresh-worker first-hit unit latency ~40-70ms -> ~3-21ms; full suite wall-clock 20.5s -> 6.5s (and ~40s before any caching); run_cli_tests --include-wasm-kill => 87 passed, 0 failed, 0 skipped. Implemented via the pi agent (gpt-5.3-codex-spark sub-model). Co-Authored-By: Claude Opus 4.8 --- src/wasm/worker.cpp | 117 +++++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 51 deletions(-) diff --git a/src/wasm/worker.cpp b/src/wasm/worker.cpp index 9bbcdb4..0478472 100644 --- a/src/wasm/worker.cpp +++ b/src/wasm/worker.cpp @@ -249,53 +249,12 @@ public: if(!wasm_parse_sections(bytes, core_dylink, abi_ignored, parse_error)) return("core module: " + parse_error); - String core_cached_path = cfg.core_wasm_path; - if(core_cached_path.size() >= 5 && core_cached_path.rfind(".wasm", core_cached_path.size() - 5) == core_cached_path.size() - 5) - core_cached_path = core_cached_path.substr(0, core_cached_path.size() - 5) + ".cwasm"; - else - core_cached_path += ".cwasm"; - struct stat core_stat; - struct stat cached_stat; - if(stat(core_cached_path.c_str(), &cached_stat) == 0 && stat(cfg.core_wasm_path.c_str(), &core_stat) == 0) - { - if(cached_stat.st_mtime > core_stat.st_mtime) - { - auto deserialized = wasmtime::Module::deserialize_file(engine, core_cached_path); - if(deserialized) - { - core_module.emplace(deserialized.ok()); - return(""); - } - } - } - - auto compiled = wasmtime::Module::compile(engine, bytes); - if(!compiled) - return("core module compile failed: " + String(compiled.err().message())); - core_module.emplace(compiled.ok()); - - auto serialized = core_module->serialize(); - if(serialized) - { - String tmp = core_cached_path + "." + std::to_string((long long)getpid()) + ".tmp"; - auto data = serialized.ok(); - { - std::ofstream out(tmp, std::ios::binary | std::ios::trunc); - if(out) - { - out.write((const char*)data.data(), (std::streamsize)data.size()); - if(out) - { - out.flush(); - out.close(); - if(std::rename(tmp.c_str(), core_cached_path.c_str()) != 0) - (void)std::remove(tmp.c_str()); - } - } - else - (void)std::remove(tmp.c_str()); - } - } + String core_cached_path = cached_wasm_path(cfg.core_wasm_path); + String compile_error; + auto compiled_or_cached = load_or_compile_cached_module(engine, core_cached_path, cfg.core_wasm_path, bytes, compile_error); + if(!compiled_or_cached) + return("core module compile failed: " + compile_error); + core_module.emplace(std::move(*compiled_or_cached)); return(""); } @@ -347,18 +306,74 @@ public: error = wasm_path + ": missing uce.abi stamp"; return(nullptr); } - auto compiled = wasmtime::Module::compile(engine, bytes); - if(!compiled) + String unit_cached_path = cached_wasm_path(wasm_path); + String compile_error; + auto compiled_or_cached = load_or_compile_cached_module(engine, unit_cached_path, wasm_path, bytes, compile_error); + if(!compiled_or_cached) { - error = wasm_path + ": compile failed: " + String(compiled.err().message()); + error = wasm_path + ": compile failed: " + compile_error; return(nullptr); } - unit->module.emplace(compiled.ok()); + unit->module.emplace(std::move(*compiled_or_cached)); module_cache[wasm_path] = unit; return(unit); } private: + static String cached_wasm_path(const String& wasm_path) + { + if(wasm_path.size() >= 5 && wasm_path.rfind(".wasm", wasm_path.size() - 5) == wasm_path.size() - 5) + return(wasm_path.substr(0, wasm_path.size() - 5) + ".cwasm"); + return(wasm_path + ".cwasm"); + } + + static std::optional load_or_compile_cached_module(wasmtime::Engine& engine, const String& cached_path, const String& wasm_path, + std::vector& bytes, String& compile_error) + { + struct stat wasm_stat; + struct stat cached_stat; + if(stat(cached_path.c_str(), &cached_stat) == 0 && stat(wasm_path.c_str(), &wasm_stat) == 0) + { + if(cached_stat.st_mtime > wasm_stat.st_mtime) + { + auto deserialized = wasmtime::Module::deserialize_file(engine, cached_path); + if(deserialized) + return(deserialized.ok()); + } + } + + auto compiled = wasmtime::Module::compile(engine, bytes); + if(!compiled) + { + compile_error = String(compiled.err().message()); + return(std::nullopt); + } + auto result = compiled.ok(); + auto serialized = result.serialize(); + if(serialized) + { + String tmp = cached_path + "." + std::to_string((long long)getpid()) + ".tmp"; + auto data = serialized.ok(); + { + std::ofstream out(tmp, std::ios::binary | std::ios::trunc); + if(out) + { + out.write((const char*)data.data(), (std::streamsize)data.size()); + if(out) + { + out.flush(); + out.close(); + if(std::rename(tmp.c_str(), cached_path.c_str()) != 0) + (void)std::remove(tmp.c_str()); + } + } + else + (void)std::remove(tmp.c_str()); + } + } + return(result); + } + static wasmtime::Engine make_engine() { wasmtime::Config config;