MikeG MikeG - 8 days ago 5
iOS Question

Why is mach_wait_until late when run on simulator?

I am using the below code to use mach_wait_until() to wait for a specified period of time (in nanoseconds).

private func startTimerAndResume(){

let idealNanos: UInt64 = 1250130250 //1.25 seconds

let deadline = CFAbsoluteTime(mach_absolute_time() + (timeUnitsFor(nanos: idealNanos))/100)

let x = mach_absolute_time()

mach_wait_until(UInt64(deadline))

let y = mach_absolute_time()


var timeBaseInfo = mach_timebase_info_data_t()
mach_timebase_info(&timeBaseInfo)
let elapsedNanos = (y - x) * UInt64(timeBaseInfo.numer) / UInt64(timeBaseInfo.denom);

print("deadline (aka mach-abs-time + timeUnitsFor()) = \(deadline)")
print("(mach-abs-y)-(mach-abs-x) = \(y-x)")
print("error in time units = \((y-x)-(timeUnitsFor(nanos: idealNanos))/100)")
print("elapsed nanos actual = ", elapsedNanos)
print("elapsed nanos ideal = ", idealNanos)
print("error in nanoseconds = \(elapsedNanos - idealNanos)")
}


private func timeUnitsFor(nanos: UInt64)-> UInt64{

var timeBaseInfo = mach_timebase_info_data_t()
mach_timebase_info(&timeBaseInfo)

let numer: UInt64 = UInt64(timeBaseInfo.numer)
let denom: UInt64 = UInt64(timeBaseInfo.denom)

//elapsed time in nanoseconds = timUnits * (numer / denom) ... therefore ->
let timeUnits: UInt64 = (nanos*denom/numer)*(UInt64(100))//multiply by 100 to preserve decimal before truncation caused by UInt64() conversion
print("timeUnits = \((timeUnits)/100) for target nanos \(nanos) when numer = \(numer) and denom = \(denom)")
return timeUnits
}


When i run this on my actual iPhone device the error is usually around 1 milisecond and the output is this:

timeUnits = 30003126 for target nanos 1250130250 when numer = 125 and denom = 3


deadline (aka mach-abs-time + timeUnitsFor()) = 4025277628801.0


(mach-abs-y)-(mach-abs-x) = 30027213


timeUnits = 30003126 for target nanos 1250130250 when numer = 125 and denom = 3


error in time units = 24087


elapsed nanos actual = 1251133875


elapsed nanos ideal = 1250130250


error in nanoseconds = 1003625


However when I run this on my simulator the timer is consistently 70 to 74 milliseconds late, here is the output :

timeUnits = 1250130250 for target nanos 1250130250 when numer = 1 and denom = 1


deadline (aka mach-abs-time + timeUnitsFor()) = 691695760744956.0


(mach-abs-y)-(mach-abs-x) = 1322288698


timeUnits = 1250130250 for target nanos 1250130250 when numer = 1 and denom = 1


error in time units = 72158448


elapsed nanos actual = 1322288698


elapsed nanos ideal = 1250130250


error in nanoseconds = 72158448


I would like to know why the simulator is 70 to 74 milliseconds late every time. Am i doing the conversion between nanoseconds and mach time units wrong? Thanks

Answer

The simulator is not a clockcycle accurate arm emulator meant for clockcycle accurate testing. It is a build of iOS for Intel and shares the kernel with the rest of the host OS.

It's possible that your timers are getting coalesced in the simulator whereas they're not on device. Depending on your host configuration and runtime version, the simulator processes may be running at the Utility QoS tier.

So, the reason you're seeing a difference in timing is because of one or more of those reasons:

  • Intel vs ARM
  • different kernel version (host kernel vs paired iOS kernel version)
  • contention with other processes on your system
  • Timer coalescence due to different QoS tiers

You should not be relying on getting control back within a specific window as that's not in the API contract. You will be scheduled at some point after that time has elapsed.