简体   繁体   English

了解 M1 机器上的 DispatchTime

[英]Understand DispatchTime on M1 machines

In my iOS project were were able to replicate Combine's Schedulers implementation and we have an extensive suit of testing, everything was fine on Intel machines all the tests were passing, now we got some of M1 machines to see if there is a showstopper in our workflow.在我的 iOS 项目中,能够复制Combine's Schedulers实现,我们有一套广泛的测试,在 Intel 机器上一切正常,所有测试都通过了,现在我们得到了一些 M1 机器,看看我们的工作流程中是否有一个停止器.

Suddenly some of our library code starts failing, the weird thing is even if we use Combine's Implementation the tests still failing.突然我们的一些库代码开始失败,奇怪的是即使我们使用 Combine 的实现,测试仍然失败。

Our assumption is we are misusing DispatchTime(uptimeNanoseconds:) as you can see in the following screen shot (Combine's implementation)我们的假设是我们在滥用DispatchTime(uptimeNanoseconds:) ,正如您在以下屏幕截图中看到的那样(Combine 的实现)

在此处输入图像描述

We know by now that initialising DispatchTime with uptimeNanoseconds value doesn't mean they are the actual nanoseconds on M1 machines, according to the docs根据文档,我们现在知道使用 uptimeNanoseconds 值初始化DispatchTime并不意味着它们是 M1 机器上的实际纳秒

Creates a DispatchTime relative to the system clock that ticks since boot.创建一个相对于自启动以来滴答作响的系统时钟的DispatchTime

 - Parameters:
   - uptimeNanoseconds: The number of nanoseconds since boot, excluding
                        time the system spent asleep
 - Returns: A new `DispatchTime`
 - Discussion: This clock is the same as the value returned by
               `mach_absolute_time` when converted into nanoseconds.
               On some platforms, the nanosecond value is rounded up to a
               multiple of the Mach timebase, using the conversion factors
               returned by `mach_timebase_info()`. The nanosecond equivalent
               of the rounded result can be obtained by reading the
               `uptimeNanoseconds` property.
               Note that `DispatchTime(uptimeNanoseconds: 0)` is
               equivalent to `DispatchTime.now()`, that is, its value
               represents the number of nanoseconds since boot (excluding
               system sleep time), not zero nanoseconds since boot.

so, is the test wrong or we should not use DispatchTime like this?那么,测试是错误的还是我们不应该像这样使用DispatchTime

we try to follow Apple suggestion and use this:我们尝试遵循Apple 的建议并使用它:

uint64_t MachTimeToNanoseconds(uint64_t machTime)
{
    uint64_t nanoseconds = 0;
    static mach_timebase_info_data_t sTimebase;
    if (sTimebase.denom == 0)
        (void)mach_timebase_info(&sTimebase);

    nanoseconds = ((machTime * sTimebase.numer) / sTimebase.denom);

    return nanoseconds;
}

it didnt help a lot.它没有多大帮助。

Edit: Screenshot code:编辑:截图代码:

 func testSchedulerTimeTypeDistance() {
    let time1 = DispatchQueue.SchedulerTimeType(.init(uptimeNanoseconds: 10000))
    let time2 = DispatchQueue.SchedulerTimeType(.init(uptimeNanoseconds: 10431))
    let distantFuture = DispatchQueue.SchedulerTimeType(.distantFuture)
    let notSoDistantFuture = DispatchQueue.SchedulerTimeType(
      DispatchTime(
        uptimeNanoseconds: DispatchTime.distantFuture.uptimeNanoseconds - 1024
      )
    )

    XCTAssertEqual(time1.distance(to: time2), .nanoseconds(431))
    XCTAssertEqual(time2.distance(to: time1), .nanoseconds(-431))

    XCTAssertEqual(time1.distance(to: distantFuture), .nanoseconds(-10001))
    XCTAssertEqual(distantFuture.distance(to: time1), .nanoseconds(10001))
    XCTAssertEqual(time2.distance(to: distantFuture), .nanoseconds(-10432))
    XCTAssertEqual(distantFuture.distance(to: time2), .nanoseconds(10432))

    XCTAssertEqual(time1.distance(to: notSoDistantFuture), .nanoseconds(-11025))
    XCTAssertEqual(notSoDistantFuture.distance(to: time1), .nanoseconds(11025))
    XCTAssertEqual(time2.distance(to: notSoDistantFuture), .nanoseconds(-11456))
    XCTAssertEqual(notSoDistantFuture.distance(to: time2), .nanoseconds(11456))

    XCTAssertEqual(distantFuture.distance(to: distantFuture), .nanoseconds(0))
    XCTAssertEqual(notSoDistantFuture.distance(to: notSoDistantFuture),
                   .nanoseconds(0))
  }

The difference between Intel and ARM code is precision. Intel 和 ARM 代码之间的区别在于精度。

With Intel code, DispatchTime internally works with nanoseconds.使用 Intel 代码, DispatchTime在内部以纳秒为单位工作。 With ARM code, DispatchTime works with nanoseconds * 3 / 125 (plus some integer rounding).使用 ARM 代码, DispatchTime纳秒* 3 / 125 (加上一些 integer 舍入)工作。 The same applies to DispatchQueue.SchedulerTimeType .这同样适用于DispatchQueue.SchedulerTimeType

DispatchTimeInterval and DispatchQueue.SchedulerTimeType.Stride internally use nanoseconds. DispatchTimeIntervalDispatchQueue.SchedulerTimeType.Stride内部使用纳秒。

So the ARM code uses lower precision for calculations but full precision when comparing the distances.因此,ARM 代码使用较低精度进行计算,但在比较距离时使用全精度。 In addition, you lose some precision when converting from nanoseconds to internal unit.此外,从纳秒转换为内部单位时,您会失去一些精度。

The exact formula for the DispatchTime conversion is (executed as integer operations): DispatchTime转换的确切公式为(执行为 integer 操作):

rawValue = (nanoseconds * 3 + 124) / 125

nanoseconds = rawValue * 125 / 3

As an example, let's take this code:举个例子,让我们看这段代码:

let time1 = DispatchQueue.SchedulerTimeType(.init(uptimeNanoseconds: 10000))
let time2 = DispatchQueue.SchedulerTimeType(.init(uptimeNanoseconds: 10431))
XCTAssertEqual(time1.distance(to: time2), .nanoseconds(431))

It results in the calculation:它导致计算:

(10000 * 3 + 124) / 125 -> 240
(10431 * 3 + 124) / 125 -> 251
251 - 240 -> 11
11 * 125 / 3 -> 458

The resulting comparison between 458 and 431 then fails. 458 和 431 之间的结果比较然后失败。

So the main fix would be to allow for small differences (I haven't verified if 42 is the maximum difference):所以主要的解决办法是允许小的差异(我还没有验证 42 是否是最大差异):

XCTAssertEqual(time1.distance(to: time2), .nanoseconds(431), accuracy: .nanoseconds(42))
XCTAssertEqual(time2.distance(to: time1), .nanoseconds(-431), accuracy: .nanoseconds(42))

And there are more surprises: Other than with Intel code, distantFuture and notSoDistantFuture are equal with ARM code.还有更多惊喜:除了 Intel 代码, distantFuturenotSoDistantFuture等同于 ARM 代码。 It has probably been implemented like so to protect from an overflow when multiplying with 3. (The actual calculation would be: 0xFFFFFFFFFFFFFFFF * 3).它可能是这样实现的,以防止在乘以 3 时发生溢出。(实际计算为:0xFFFFFFFFFFFFFFFF * 3)。

Furthermore I think that you are relying on implementation specific behavior when calculating the distance between time stamps at or close to 0 and time stamps at or close to distant future.此外,我认为在计算处于或接近 0 的时间戳与处于或接近遥远未来的时间戳之间的距离时,您依赖于实现特定的行为。 The tests rely on the fact the distant future internally uses 0xFFFFFFFFFFFFFFFF and that the unsigned subtraction wraps around and produces a result as if the internal value was -1.测试依赖于遥远的未来在内部使用 0xFFFFFFFFFFFFFFFF 的事实,并且无符号减法环绕并产生一个结果,就好像内部值为 -1。

I think your issue lies in this line:我认为你的问题在于这一行:

nanoseconds = ((machTime * sTimebase.numer) / sTimebase.denom)

... which is doing integer operations. ...正在执行 integer 操作。

The actual ratio here for M1 is 125/3 ( 41.666... ), so your conversion factor is truncating to 41 . M1 的实际比率为125/3 ( 41.666... ),因此您的转换因子被截断为41 This is a ~1.6% error, which might explain the differences you're seeing.这是一个约 1.6% 的错误,这可能解释了您所看到的差异。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM