簡體   English   中英

了解 M1 機器上的 DispatchTime

[英]Understand DispatchTime on M1 machines

在我的 iOS 項目中,能夠復制Combine's Schedulers實現,我們有一套廣泛的測試,在 Intel 機器上一切正常,所有測試都通過了,現在我們得到了一些 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 的建議並使用它:

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

Intel 和 ARM 代碼之間的區別在於精度。

使用 Intel 代碼, DispatchTime在內部以納秒為單位工作。 使用 ARM 代碼, DispatchTime納秒* 3 / 125 (加上一些 integer 舍入)工作。 這同樣適用於DispatchQueue.SchedulerTimeType

DispatchTimeIntervalDispatchQueue.SchedulerTimeType.Stride內部使用納秒。

因此,ARM 代碼使用較低精度進行計算,但在比較距離時使用全精度。 此外,從納秒轉換為內部單位時,您會失去一些精度。

DispatchTime轉換的確切公式為(執行為 integer 操作):

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

此外,我認為在計算處於或接近 0 的時間戳與處於或接近遙遠未來的時間戳之間的距離時,您依賴於實現特定的行為。 測試依賴於遙遠的未來在內部使用 0xFFFFFFFFFFFFFFFF 的事實,並且無符號減法環繞並產生一個結果,就好像內部值為 -1。

我認為你的問題在於這一行:

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

...正在執行 integer 操作。

M1 的實際比率為125/3 ( 41.666... ),因此您的轉換因子被截斷為41 這是一個約 1.6% 的錯誤,這可能解釋了您所看到的差異。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM