【问题标题】:Understand DispatchTime on M1 machines了解 M1 机器上的 DispatchTime
【发布时间】:2022-01-07 07:18:50
【问题描述】:

在我的 iOS 项目中,能够复制 Combine's Schedulers 实现,我们进行了广泛的测试,在英特尔机器上一切正常,所有测试都通过了,现在我们得到了一些 M1 机器,看看是否有我们工作流程中的佼佼者。

我们的一些库代码突然开始失败,奇怪的是即使我们使用 Combine 的实现,测试仍然失败。

我们的假设是我们在滥用DispatchTime(uptimeNanoseconds:),正如您在以下屏幕截图(Combine 的实现)中看到的那样

根据文档,我们现在知道使用 uptimeNanoseconds 值初始化 DispatchTime 并不意味着它们是 M1 机器上的实际纳秒数

创建一个相对于系统时钟的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.

那么,是测试错误还是我们不应该这样使用DispatchTime

我们尝试关注Apple suggestion 并使用它:

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;
}

没有太大帮助。

编辑:截图代码:

 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))
  }

【问题讨论】:

  • 我之前在htop 工作过,它使用了这个功能。比较可能有用:github.com/htop-dev/htop/blob/…
  • 最好不要将重要信息作为图像包含,您可以复制/粘贴相关文本吗?

标签: swift macos apple-m1 arm64


【解决方案1】:

Intel 和 ARM 代码的区别在于精度。

使用英特尔代码,DispatchTime 在内部以纳秒为单位工作。对于 ARM 代码,它适用于 纳秒 * 3 / 125(加上一些整数舍入)。这同样适用于DispatchQueue.SchedulerTimeType

DispatchTimeIntervalDispatchQueue.SchedulerTimeType.Stride 在两个平台上内部使用纳秒。

因此,ARM 代码使用较低精度进行计算,但在比较距离时使用全精度。此外,从纳秒转换为内部单位时会损失精度。

DispatchTime 转换的确切公式是(作为整数运算执行):

rawValue = (nanoseconds * 3 + 124) / 125

nanoseconds = rawValue * 125 / 3

以这段代码为例:

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

计算结果:

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

458 和 431 之间的结果比较然后失败。

所以主要的解决办法是允许小的差异(我还没有验证 42 是否是最大差异):

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

还有更多惊喜:除了 Intel 代码之外,distantFuturenotSoDistantFuture 与 ARM 代码相同。它可能是这样实现的,以防止在乘以 3 时溢出。(实际计算为:0xFFFFFFFFFFFFFFFF * 3)。而从内部单位到纳秒的转换会得到 0xFFFFFFFFFFFFFFFF * 125 / 3,这个值很大,要用 64 位来表示。

此外,我认为在计算处于或接近 0 的时间戳与处于或接近遥远未来的时间戳之间的距离时,您依赖于实现特定的行为。测试依赖于遥远的未来在内部使用 0xFFFFFFFFFFFFFFFF 的事实,并且无符号减法回绕并产生一个结果,就好像内部值为 -1。

【讨论】:

  • 哇,好答案!你有任何来源或其他进一步阅读的链接吗?
  • 很遗憾没有。我主要检查了调试器中的变量并查看了打印输出。此外,我还单步调试了汇编代码。
【解决方案2】:

我认为你的问题在于这一行:

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

... 进行整数运算。

此处 M1 的实际比率为 125/3 (41.666...),因此您的转换因子将截断为 41。这是一个约 1.6% 的错误,这可以解释您所看到的差异。

【讨论】:

  • 如果代码是machTime * (sTimeBase.numer / sTimeBase.denom) 就是这种情况,但不是。假设乘法没有溢出,所写的应该是获得最接近整数的真商的正确方法。
  • 嗯,你是对的。
猜你喜欢
  • 2022-10-21
  • 2021-10-26
  • 2022-07-09
  • 1970-01-01
  • 2017-06-12
  • 1970-01-01
  • 2021-05-23
  • 2022-07-09
  • 2021-07-18
相关资源
最近更新 更多