OSX
Timer Coalescing
Process Management
System Optimization
MacOS

Disabling Timer Coalescing in OSX for a given process

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Timer coalescing is a macOS power-saving feature that groups nearby timer firings together, reducing CPU wake-ups and saving battery. Instead of waking the CPU for each individual timer, the system delays timers slightly to fire them in batches. This is great for battery life but problematic for latency-sensitive applications like audio processing, real-time data collection, or benchmarking tools that need precise timer resolution. You can disable it per-process using QoS classes, mach_timebase APIs, or by launching with specific arguments.

How Timer Coalescing Works

Without coalescing:

 
1Timer A fires at 100ms
2Timer B fires at 102ms
3Timer C fires at 105ms
43 CPU wake-ups

With coalescing (default):

 
Timer A, B, C all fire at ~105ms
1 CPU wake-up

macOS applies a "leeway" to each timer. The system can delay the timer by up to the leeway amount to align it with other timers. The default leeway depends on the timer type and QoS class.

The Quality of Service class controls timer coalescing behavior. Higher QoS classes get less coalescing:

c
1#include <pthread.h>
2#include <sys/qos.h>
3
4// Set the current thread to UserInteractive QoS
5// This minimizes timer coalescing for this thread
6pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
swift
1// Swift equivalent
2DispatchQueue.global(qos: .userInteractive).async {
3    // Timers on this queue have minimal coalescing
4    let timer = Timer.scheduledTimer(withTimeInterval: 0.001, repeats: true) { _ in
5        // Fires with high precision
6    }
7    RunLoop.current.add(timer, forMode: .common)
8    RunLoop.current.run()
9}

QoS classes and their coalescing behavior:

QoS ClassTimer LeewayUse Case
.userInteractiveMinimal (~0ms)UI animations, audio
.userInitiatedLow (~1ms)User-triggered tasks
.defaultMedium (~5ms)General work
.utilityHigh (~10ms)Long-running tasks
.backgroundMaximum (~100ms+)Maintenance, backups

Method 2: dispatch_source Timer with Zero Leeway

c
1#include <dispatch/dispatch.h>
2
3dispatch_source_t timer = dispatch_source_create(
4    DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
5    dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)
6);
7
8// Set timer with zero leeway — disables coalescing for this timer
9dispatch_source_set_timer(timer,
10    dispatch_time(DISPATCH_TIME_NOW, 0),
11    1000000,   // 1ms interval in nanoseconds
12    0          // 0 leeway — no coalescing
13);
14
15dispatch_source_set_event_handler(timer, ^{
16    // Fires with maximum precision
17});
18
19dispatch_resume(timer);
swift
1// Swift equivalent
2let timer = DispatchSource.makeTimerSource(
3    flags: .strict,  // .strict flag minimizes coalescing
4    queue: DispatchQueue.global(qos: .userInteractive)
5)
6
7timer.schedule(
8    deadline: .now(),
9    repeating: .milliseconds(1),
10    leeway: .nanoseconds(0)  // Zero leeway
11)
12
13timer.setEventHandler {
14    // High-precision callback
15}
16
17timer.resume()

The .strict flag tells the system this timer should not be coalesced.

Method 3: NSTimer with Tolerance

swift
1let timer = Timer.scheduledTimer(
2    withTimeInterval: 0.001,
3    repeats: true
4) { _ in
5    // Timer callback
6}
7
8// Set tolerance to 0 — requests no coalescing
9timer.tolerance = 0
10
11// Compare with default behavior:
12// timer.tolerance = 0.0001  // 10% of interval is common default

Setting tolerance = 0 is a request, not a guarantee. The system may still apply minimal coalescing.

Method 4: Mach Absolute Time (Lowest Level)

For the highest precision, use Mach APIs directly:

c
1#include <mach/mach_time.h>
2#include <mach/thread_policy.h>
3
4// Set real-time thread scheduling — bypasses timer coalescing
5kern_return_t set_realtime_thread() {
6    mach_timebase_info_data_t info;
7    mach_timebase_info(&info);
8
9    thread_time_constraint_policy_data_t policy;
10    policy.period      = 1000000;  // 1ms in absolute time units
11    policy.computation = 500000;   // 0.5ms of compute per period
12    policy.constraint  = 1000000;  // Must complete within 1ms
13    policy.preemptible = FALSE;
14
15    return thread_policy_set(
16        mach_thread_self(),
17        THREAD_TIME_CONSTRAINT_POLICY,
18        (thread_policy_t)&policy,
19        THREAD_TIME_CONSTRAINT_POLICY_COUNT
20    );
21}

Real-time threads get the highest scheduling priority and effectively bypass timer coalescing.

Method 5: Launch with caffeinate

For quick testing, wrap your process with caffeinate:

bash
1# Prevent system sleep and reduce timer coalescing
2caffeinate -i ./my_latency_sensitive_app
3
4# -i: prevent idle sleep
5# -d: prevent display sleep
6# -s: prevent system sleep (AC power)

caffeinate raises the process priority and reduces power management interference, indirectly reducing timer coalescing.

Checking Timer Coalescing Status

bash
1# View current timer coalescing settings
2sysctl kern.timer.coalescing_enabled
3# kern.timer.coalescing_enabled: 1
4
5# View per-process timer info (requires root)
6sudo dtrace -n 'profile-997 { @[execname] = count(); }'
7
8# Check process QoS
9sudo taskpolicy -p <pid>
bash
1# Disable timer coalescing system-wide (requires SIP off, not recommended)
2sudo sysctl -w kern.timer.coalescing_enabled=0
3
4# This affects ALL processes and significantly increases power consumption
5# Only use for benchmarking, never in production

Common Pitfalls

  • Battery drain: Disabling timer coalescing increases CPU wake-ups significantly. A MacBook might see 20-30% more power usage. Only disable for processes that genuinely need sub-millisecond precision.
  • Not using the right QoS: Setting .userInteractive on a background task wastes power and starves other processes. Match QoS to actual requirements.
  • Tolerance vs leeway confusion: NSTimer.tolerance and dispatch_source leeway both control coalescing but through different APIs. Setting one does not affect the other.
  • Expecting microsecond precision: Even with coalescing disabled, macOS is not a real-time OS. Thread scheduling, interrupts, and other system activity introduce jitter. For true real-time, use a dedicated audio or I/O thread with THREAD_TIME_CONSTRAINT_POLICY.
  • SIP restrictions: On modern macOS with System Integrity Protection enabled, some kernel-level timer settings cannot be modified. Use QoS and dispatch source APIs instead.

Summary

  • Timer coalescing groups timer firings to save power — adds small delays to timers
  • Use QOS_CLASS_USER_INTERACTIVE to minimize coalescing for a thread
  • Use DispatchSource with .strict flag and zero leeway for precise timers
  • Set NSTimer.tolerance = 0 to request no coalescing
  • Use Mach real-time thread policies for the highest precision
  • Only disable coalescing for genuinely latency-sensitive work — it significantly impacts battery life

Course illustration
Course illustration

All Rights Reserved.