From 19b974717bdeb63597b97e8faf78c47c519003d0 Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Fri, 16 Oct 2026 11:40:19 +0200 Subject: [PATCH] JavaScriptEventLoop: Read the clock less often with `yieldInterval` --- .../JavaScriptEventLoop+YieldInterval.swift | 10 ++++------ Sources/JavaScriptEventLoop/JobQueue.swift | 13 ++++++++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+YieldInterval.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+YieldInterval.swift index 73effb850..dbcd30b9f 100644 --- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+YieldInterval.swift +++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+YieldInterval.swift @@ -20,10 +20,8 @@ extension JavaScriptEventLoop { return performanceNow!.now(this: performanceNow!.performance).number! } - func hasRun(since start: DrainInstant, for interval: Duration) -> Bool { - let (seconds, attoseconds) = interval.components - let intervalMilliseconds = Double(seconds) * 1_000 + Double(attoseconds) / 1_000_000_000_000_000 - return drainClockNow() - start >= intervalMilliseconds + func drainElapsed(since start: DrainInstant) -> Duration { + .milliseconds(drainClockNow() - start) } #else typealias DrainInstant = ContinuousClock.Instant @@ -32,8 +30,8 @@ extension JavaScriptEventLoop { ContinuousClock.now } - func hasRun(since start: DrainInstant, for interval: Duration) -> Bool { - start.duration(to: .now) >= interval + func drainElapsed(since start: DrainInstant) -> Duration { + start.duration(to: .now) } #endif diff --git a/Sources/JavaScriptEventLoop/JobQueue.swift b/Sources/JavaScriptEventLoop/JobQueue.swift index 280ddb035..0dcbcdc17 100644 --- a/Sources/JavaScriptEventLoop/JobQueue.swift +++ b/Sources/JavaScriptEventLoop/JobQueue.swift @@ -45,6 +45,10 @@ extension JavaScriptEventLoop { // The clock is only read while `yieldInterval` is set. var start: DrainInstant? = yieldInterval == nil ? nil : drainClockNow() + // A clock read costs more than a small job. Read it every `clockStride` jobs, and double + // the stride (up to 256) while the drain is far from the interval. + var clockStride = 1 + var jobsUntilClockRead = 1 while let job = self.claimNextFromQueue() { #if compiler(>=5.9) job.runSynchronously(on: self.asUnownedSerialExecutor()) @@ -54,11 +58,18 @@ extension JavaScriptEventLoop { if let yieldInterval, queueState.headJob != nil { if let start { - if hasRun(since: start, for: yieldInterval) { + jobsUntilClockRead -= 1 + if jobsUntilClockRead > 0 { continue } + let elapsed = drainElapsed(since: start) + if elapsed >= yieldInterval { // Keep `isSpinning` set so new jobs wait for the continuation. queueHostTask { self.runAllJobs() } return } + if elapsed * 4 < yieldInterval { + clockStride = min(clockStride * 2, 256) + } + jobsUntilClockRead = clockStride } else { start = drainClockNow() }