You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Date.now() does not advance during tight CPU-bound loops #74
On Perry 0.5.88, Date.now() returns an identical value before and after a 100M-iteration CPU-bound loop. Node and Bun advance correctly on the same machine and same script.
This breaks any micro-benchmark or per-operation timing that samples Date.now() in a tight loop, and means driver/bench users can't measure hot-path throughput from inside a Perry-compiled program.
t0 and t1 are literally the same integer on Perry — so it's not a subtraction quirk, Date.now() itself is returning a cached/stale value inside the loop.
Expected
Date.now() returns current wall-clock milliseconds on each call, advancing as real time passes — as it does on Bun / Node / V8 / JSC.
Impact
Breaks micro-benchmarks: 1000 SELECT-1 queries against Postgres reported as total=1ms avg=1.0µs on Perry, while Node reports ~63µs/query and Bun ~45µs/query for the same driver code.
Makes any measure() helper that does const t0 = Date.now(); work(); const elapsed = Date.now() - t0; return 0 for any work under the sampling quantum.
Masks real per-query cost in the @perry/postgres benchmark suite — every sample quantizes to 0.5ms or 0.0ms, which we initially mistook for a real performance floor.
Guess at root cause
Looks like Date.now() is being CSE'd / hoisted out of the loop, or the runtime is reading a cached wall-clock value that is only refreshed on I/O / event-loop yield. A volatile-equivalent fence around the syscall (or simply not folding calls at the JIT/LLVM level) should fix it. Worth confirming whether the Rust-side implementation caches the result per tick.
Environment
Perry 0.5.88
macOS (Darwin 25.4.0)
Comparison runtimes: Bun latest, Node 22 (--experimental-strip-types)
Verified on v0.5.91: Date.now() now advances correctly. Same 100M-iter repro:
Runtime
delta
Perry 0.5.91
32ms (was 0ms on 0.5.88)
Bun
53ms
Node
57ms
Perry is actually faster than V8/JSC on the pure arithmetic loop now. Thanks for the quick fix.
Separately, while re-running the @perry/postgres driver benchmarks to get accurate per-query numbers, I've hit a new blocker that looks like a Promise-resolution / async-continuation bug. Filing it as a separate issue — handshake messages arrive and are processed (traces show AuthenticationOk → 15×ParameterStatus → BackendKeyData → ReadyForQuery), but the startupGate.resolve(conn) call never wakes the await connect(...) site in the main async function. Reproduces on 0.5.87, 0.5.88, 0.5.89, and 0.5.91, so it predates the asm-sideeffect fix.
Activity
proggeramlug commentedon Apr 17, 2026
Verified on v0.5.91:
Date.now()now advances correctly. Same 100M-iter repro:Perry is actually faster than V8/JSC on the pure arithmetic loop now. Thanks for the quick fix.
Separately, while re-running the
@perry/postgresdriver benchmarks to get accurate per-query numbers, I've hit a new blocker that looks like a Promise-resolution / async-continuation bug. Filing it as a separate issue — handshake messages arrive and are processed (traces showAuthenticationOk → 15×ParameterStatus → BackendKeyData → ReadyForQuery), but thestartupGate.resolve(conn)call never wakes theawait connect(...)site in the main async function. Reproduces on 0.5.87, 0.5.88, 0.5.89, and 0.5.91, so it predates the asm-sideeffect fix.fix: insert asm sideeffect barrier in pure loop bodies (v0.5.91, #74)
perf(codegen): #140 restore SIMD autovectorization on accumulator loo…