Appleシリコン(M5 / M2 Ultra)環境におけるストレージキャッシュバイパス検証レポート

macOS環境において、標準的なファイルI/Oを行うとOSのUnified Buffer Cache(UBC)による先読み・ライトバック制御が働き、ストレージデバイス本来の物理的な実効性能が隠蔽されます。

本検証では、Darwinレイヤーの低級システムコール(fcntl の F_NOCACHE および F_FULLSYNC)を用いてOSキャッシュを完全にバイパスし、デバイスの「生の物理限界」と「純カーネル処理遅延(ナノ秒)」を抽出しました。Apple M5(Mac17,2) および Apple M2 Ultra(Mac14,14) の2つの環境で行った実測サンプリングデータを元に、ハードウェアのアーキテクチャ特性を客観的に考察します。


💡 1. プレビュー記事との決定的な違い

当ブログの初期検証フェーズ(プレビュー記事)では、主に「シーケンシャルアクセスの可視化」と「大まかなキャッシュ有無による速度差の確認」に主眼を置いていました。今回の検証(v1.8.0)では、そこからさらに低級システムシステムコールの挙動を限界まで追い込み、以下の3つの圧倒的なアップデートと明確な違いを内包しています。

① 「ランダム書き込み(Rand Write)」IOPSの完全新設

以前のプレビュー検証は、読み込みキャッシュの可視化が中心でした。今回のv1.8.0パイプラインでは、マルチスレッド環境下での「4KB分散シーク書き込みテスト」を完全新設。これにより、OSキャッシュの裏側に隠された、SSDコントローラおよび磁気HDDの物理ヘッドの真の悲鳴(物理書き換え限界)を数値化しました。

② 自己ジッター補正付き「純カーネル遅延(ナノ秒)」のサンプリング

時間の計測において、タイマー呼び出し自体にかかるナノ秒単位のジッター(遅延オーバーヘッド)を事前に1,000回プレクエリして測定値から差し引く「自己補正ロジック」を実装。これにより、1ミリ秒未満のシステムコールが、macOSカーネルの内部処理だけで消費した純粋な時間をナノ秒単位で正確に分離抽出しています。

③ Swift 6 Strict Concurrency への完全適合

旧来のCスタイルや初期Swiftコードに存在したマルチスレッド間でのデータ競合リスクを排除。トップレベルの不変性および @Sendable 隔離境界を厳格に適用し、2026年現在の最新の静的並行性ルールを満たした上でPOSIXシステムコール(pread/pwrite)を直叩きしています。


📊 2. 検証結果:実測値マトリクスサマリー

M5 測定結果
==================================================
 macOS Swift 6 Low-Level Storage Profiler v1.8.0
 [Machine Model] : Mac17,2
 [Detected CPU]  : Apple M5
==================================================

📂 ターゲット [1/3]: ./ (動的テストサイズ: 16384 MB)
--------------------------------------------------

  >> [./] [物理層] 開始 (サイズ: 16384MB)

    📊 [ 集計結果 - 物理層 ]
      Seq Write : 平均 5335.75 MB/s (最高: 5519.46 MB/s / 最低: 5212.21 MB/s)
      Seq Read  : 平均 5387.05 MB/s (最高: 5996.68 MB/s / 最低: 4603.02 MB/s)
      Rand Read : 平均 50010 IOPS (最高: 52295 IOPS / 最低: 47855 IOPS)
      Rand Write: 平均 30098 IOPS (最高: 30828 IOPS / 最低: 29502 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 187930ns | 4KB RandRead: 77461ns | 4KB RandWrite: 132045ns

  >> [./] [キャッシュ] 開始 (サイズ: 16384MB)

    📊 [ 集計結果 - キャッシュ ]
      Seq Write : 平均 5636.07 MB/s (最高: 6413.68 MB/s / 最低: 5198.97 MB/s)
      Seq Read  : 平均 37063.25 MB/s (最高: 37082.67 MB/s / 最低: 37032.50 MB/s)
      Rand Read : 平均 1855078 IOPS (最高: 1877934 IOPS / 最低: 1812518 IOPS)
      Rand Write: 平均 49794 IOPS (最高: 50662 IOPS / 最低: 48356 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 26973ns | 4KB RandRead: 1848ns | 4KB RandWrite: 6629ns

📂 ターゲット [2/3]: /Volumes/USB-SSD (動的テストサイズ: 16384 MB)
--------------------------------------------------

  >> [/Volumes/USB-SSD] [物理層] 開始 (サイズ: 16384MB)

    📊 [ 集計結果 - 物理層 ]
      Seq Write : 平均 51.73 MB/s (最高: 52.10 MB/s / 最低: 51.13 MB/s)
      Seq Read  : 平均 421.85 MB/s (最高: 428.93 MB/s / 最低: 410.54 MB/s)
      Rand Read : 平均 5116 IOPS (最高: 7672 IOPS / 最低: 98 IOPS)
      Rand Write: 平均 3877 IOPS (最高: 5970 IOPS / 最低: 94 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 2371404ns | 4KB RandRead: 13989289ns | 4KB RandWrite: 14599896ns

  >> [/Volumes/USB-SSD] [キャッシュ] 開始 (サイズ: 16384MB)

    📊 [ 集計結果 - キャッシュ ]
      Seq Write : 平均 50.71 MB/s (最高: 51.23 MB/s / 最低: 50.06 MB/s)
      Seq Read  : 平均 36428.98 MB/s (最高: 36770.39 MB/s / 最低: 36115.59 MB/s)
      Rand Read : 平均 1842604 IOPS (最高: 1856185 IOPS / 最低: 1833355 IOPS)
      Rand Write: 平均 3262 IOPS (最高: 6650 IOPS / 最低: 1562 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 27444ns | 4KB RandRead: 1848ns | 4KB RandWrite: 4435ns

📂 ターゲット [3/3]: /Volumes/RAMDisk (動的テストサイズ: 1024 MB)
--------------------------------------------------

  >> [/Volumes/RAMDisk] [物理層] 開始 (サイズ: 1024MB)

    📊 [ 集計結果 - 物理層 ]
      Seq Write : 平均 6262.49 MB/s (最高: 7412.73 MB/s / 最低: 4273.75 MB/s)
      Seq Read  : 平均 8002.99 MB/s (最高: 8660.82 MB/s / 最低: 7412.60 MB/s)
      Rand Read : 平均 79267 IOPS (最高: 80330 IOPS / 最低: 78078 IOPS)
      Rand Write: 平均 48635 IOPS (最高: 51559 IOPS / 最低: 47004 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 125435ns | 4KB RandRead: 49647ns | 4KB RandWrite: 81796ns

  >> [/Volumes/RAMDisk] [キャッシュ] 開始 (サイズ: 1024MB)

    📊 [ 集計結果 - キャッシュ ]
      Seq Write : 平均 14772.24 MB/s (最高: 15446.09 MB/s / 最低: 13628.13 MB/s)
      Seq Read  : 平均 29862.78 MB/s (最高: 30163.11 MB/s / 最低: 29453.39 MB/s)
      Rand Read : 平均 1830661 IOPS (最高: 1865854 IOPS / 最低: 1790811 IOPS)
      Rand Write: 平均 163893 IOPS (最高: 174048 IOPS / 最低: 144037 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 33482ns | 4KB RandRead: 1875ns | 4KB RandWrite: 23695ns

==================================================
 正常に全セッションの同期永続化が完了しました。
==================================================
M2Ultra 測定結果
==================================================
 macOS Swift 6 Low-Level Storage Profiler v1.8.0
 [Machine Model] : Mac14,14
 [Detected CPU]  : Apple M2 Ultra
==================================================

📂 ターゲット [1/3]: ./ (動的テストサイズ: 16384 MB)
--------------------------------------------------

  >> [./] [物理層] 開始 (サイズ: 16384MB)

    📊 [ 集計結果 - 物理層 ]
      Seq Write : 平均 5851.37 MB/s (最高: 6086.61 MB/s / 最低: 5582.60 MB/s)
      Seq Read  : 平均 2651.41 MB/s (最高: 2676.58 MB/s / 最低: 2628.43 MB/s)
      Rand Read : 平均 37796 IOPS (最高: 40269 IOPS / 最低: 34483 IOPS)
      Rand Write: 平均 25737 IOPS (最高: 26058 IOPS / 最低: 25261 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 377168ns | 4KB RandRead: 102295ns | 4KB RandWrite: 154477ns

  >> [./] [キャッシュ] 開始 (サイズ: 16384MB)

    📊 [ 集計結果 - キャッシュ ]
      Seq Write : 平均 5181.13 MB/s (最高: 6222.75 MB/s / 最低: 4643.37 MB/s)
      Seq Read  : 平均 18017.61 MB/s (最高: 18191.21 MB/s / 最低: 17714.33 MB/s)
      Rand Read : 平均 1320685 IOPS (最高: 1617387 IOPS / 最低: 767484 IOPS)
      Rand Write: 平均 31694 IOPS (最高: 32090 IOPS / 最低: 31461 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 55499ns | 4KB RandRead: 2698ns | 4KB RandWrite: 7898ns

📂 ターゲット [2/3]: /Volumes/USB_HDD02 (動的テストサイズ: 16384 MB)
--------------------------------------------------

  >> [/Volumes/USB_HDD02] [物理層] 開始 (サイズ: 16384MB)

    📊 [ 集計結果 - 物理層 ]
      Seq Write : 平均 107.54 MB/s (最高: 107.75 MB/s / 最低: 107.18 MB/s)
      Seq Read  : 平均 108.58 MB/s (最高: 108.60 MB/s / 最低: 108.56 MB/s)
      Rand Read : 平均 133 IOPS (最高: 134 IOPS / 最低: 132 IOPS)
      Rand Write: 平均 72 IOPS (最高: 72 IOPS / 最低: 71 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 9210163ns | 4KB RandRead: 29204950ns | 4KB RandWrite: 55516885ns

  >> [/Volumes/USB_HDD02] [キャッシュ] 開始 (サイズ: 16384MB)

    📊 [ 集計結果 - キャッシュ ]
      Seq Write : 平均 107.39 MB/s (最高: 107.55 MB/s / 最低: 107.29 MB/s)
      Seq Read  : 平均 17985.39 MB/s (最高: 18138.79 MB/s / 最低: 17899.47 MB/s)
      Rand Read : 平均 1613447 IOPS (最高: 1626319 IOPS / 最低: 1603688 IOPS)
      Rand Write: 平均 234 IOPS (最高: 236 IOPS / 最低: 233 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 55592ns | 4KB RandRead: 2118ns | 4KB RandWrite: 3980ns

📂 ターゲット [3/3]: /Volumes/RAMDisk (動的テストサイズ: 1024 MB)
--------------------------------------------------

  >> [/Volumes/RAMDisk] [物理層] 開始 (サイズ: 1024MB)

    📊 [ 集計結果 - 物理層 ]
      Seq Write : 平均 6587.67 MB/s (最高: 6716.17 MB/s / 最低: 6485.17 MB/s)
      Seq Read  : 平均 6300.33 MB/s (最高: 6350.13 MB/s / 最低: 6251.66 MB/s)
      Rand Read : 平均 64792 IOPS (最高: 67687 IOPS / 最低: 60349 IOPS)
      Rand Write: 平均 31670 IOPS (最高: 33398 IOPS / 最低: 29735 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 158715ns | 4KB RandRead: 61008ns | 4KB RandWrite: 126037ns

  >> [/Volumes/RAMDisk] [キャッシュ] 開始 (サイズ: 1024MB)

    📊 [ 集計結果 - キャッシュ ]
      Seq Write : 平均 11308.42 MB/s (最高: 12141.64 MB/s / 最低: 10695.31 MB/s)
      Seq Read  : 平均 18454.19 MB/s (最高: 18542.21 MB/s / 最低: 18402.51 MB/s)
      Rand Read : 平均 1835639 IOPS (最高: 1876613 IOPS / 最低: 1810399 IOPS)
      Rand Write: 平均 80729 IOPS (最高: 82879 IOPS / 最低: 77946 IOPS)
      --------------------------------------------------
      純遅延 -> 1MB Read: 54179ns | 4KB RandRead: 1860ns | 4KB RandWrite: 47244ns

==================================================
 正常に全セッションの同期永続化が完了しました。
==================================================
測定項目M5 内蔵SSDM2 Ultra 内蔵SSDM5 外付けSSDM2 Ultra 外付けHDD
物理層 Seq Write5,335.75 MB/s5,851.37 MB/s51.73 MB/s ⚠️107.54 MB/s
物理層 Seq Read5,387.05 MB/s2,651.41 MB/s421.85 MB/s108.58 MB/s
物理層 Rand Read50,010 IOPS37,796 IOPS5,116 IOPS133 IOPS ⚠️
物理層 Rand Write30,098 IOPS25,737 IOPS3,877 IOPS72 IOPS ⚠️
物理層 Rand Read 遅延77,461 ns (約0.07ms)102,295 ns (約0.1ms)13,989,289 ns29,204,950 ns (約29.2ms) ⚠️
物理層 Rand Write 遅延132,045 ns (約0.13ms)154,477 ns (約0.15ms)14,599,896 ns55,516,885 ns (約55.5ms) ⚠️
キャッシュ層 Seq Read37,063.25 MB/s 🚀18,017.61 MB/s36,428.98 MB/s17,985.39 MB/s
キャッシュ層 Rand Read1,855,078 IOPS1,320,685 IOPS1,842,604 IOPS1,613,447 IOPS
キャッシュ層 Rand Write49,794 IOPS 🛑31,694 IOPS3,262 IOPS234 IOPS 🛑

🔍 3. サンプリングデータに基づく技術的考察

① 内蔵SSDにおけるAPFSの「Copy-on-Write(CoW)」負荷

キャッシュ層テスト(cached)では、OSがI/Oをメモリ上で処理するため、M5環境ではシーケンシャルリード約37 GB/s、ランダムリード約185万IOPSという、ユニファイドメモリ(UMA)自体のバス帯域に近い最高値を記録します。
しかし、ランダムライトに関しては、M5で49,794 IOPS、M2 Ultraで31,694 IOPSと、キャッシュ有効状態であっても数値が著しく律速されています。

純カーネル遅延を見ると、M5のランダムリード(1,848ナノ秒)に対し、ランダムライトは 6,629ナノ秒と約3.5倍の遅延 が発生しています。これはmacOSの標準ファイルシステム(APFS)の基本設計であるCopy-on-Write(データを上書きせず別の空きブロックに新規作成する)に伴う、カーネル側のメタデータ割り当て処理負荷がメモリ空間上であっても隠れたボトルネックとして作用しているリアルな証拠です。

② M5 vs M2 Ultra:世代間メモリマップドI/Oの処理格差

キャッシュ有効時のシーケンシャルリードにおいて、M5(37,063.25 MB/s)はM2 Ultra(18,017.61 MB/s)に対して約2倍の転送レート を叩き出しています。
同一のSwift 6バイナリ、同一のシステムコール(read)をシミュレートしているにもかかわらず、カーネル空間からユーザー空間へのバッファコピー、およびメモリコントローラのバス帯域幅の違いといった「アーキテクチャの世代間実効スペック差」が、ソフトウェア側から正確にサンプリングされています。

③ 外付けHDDにおける「機械的駆動シーク」の可視化

外付けHDD(USB_HDD02)の物理層テストでは、シーケンシャル速度が約107〜108 MB/sと、SATA磁気ディスクの物理的な限界レートを正確に捉えています。
驚くべきは、4KBランダムアクセスが リード133 IOPS / ライト72 IOPS まで大暴落し、純カーネル遅延が最大 55,516,885ナノ秒(約55.5ミリ秒) に達している点です。

内蔵SSD(132,045ナノ秒)と比較して 約420倍もの遅延 となるこの数値は、フラッシュメモリのような電気的制御ではなく、磁気ヘッドを目的のトラックへ移動させる「物理的なシーク動作」とプラッタの「回転待ち時間」というマクロな機械駆動現象が、ナノ秒スケールで完全にサンプリングされた決定的な証拠です。

④ 外付けSSDにおけるSLCキャッシュ切れ(ライト・ストール)

M5環境で検証した外付けUSB-SSDは、キャッシュ層では36 GB/sとメモリの速度でカモフラージュされていますが、物理層(F_NOCACHE)で生性能を剥ぎ取った瞬間、シーケンシャルライトが51.73 MB/sへと急降下します。
これは16,384 MBの大容量データを直接流し込まれたことにより、SSD内部の高速バッファ(SLCキャッシュ領域)を完全に使い果たし、NANDフラッシュセル本来の低速な書き換えサイクル(ライト・ストール現象)がそのまま表面化した典型的な挙動です。


⚠️ 4. main.swift 実行時における使用上の注意点

この検証プログラムは、OSの安全バッファを強制的にバイパスしてハードウェアを剥き出しにする低級プロファイラであるため、実行にあたっては以下の厳格な制限と注意を遵守する必要があります。

  1. 過度な繰り返し実行によるSSDの寿命摩耗(TBWの消費)
    物理層書き込みテストでは、1セッション(3試行)あたり 約50GB〜100GB以上の生データ を、OSの平準化バッファを介さずSSDのセルに直接書き込みます。さらに F_FULLSYNC によって物理的な強制フラッシュを行うため、SSDコントローラに強いガベージコレクション負荷がかかります。1日に何十回、何百回と連続して本ツールを回す行為は、フラッシュストレージの寿命(TBW)を著しく縮める原因となるため絶対に避けてください。
  2. 外付けUSBメディアにおける「熱スロットリング」とデータ破損リスク
    前述の外付けHDDや低価格SSDのように、応答遅延がミリ秒単位(20ms〜55ms)に達している最中に強制終了(Ctrl + C)をかけたり、ドライブを引き抜いたりすると、 APFSノードのメタデータ不整合を招き、対象ボリューム全体のデータが未マウント化(破損)するリスクがあります。物理I/Oが走っている間は、処理が完全に「同期永続化完了」するまで絶対に切断しないでください。
  3. 安全な検証環境の確保(RAMDiskの活用推奨)
    ツールの挙動確認、デバッグ、または単純なスクリプトのベンチマークテストを行いたい場合は、物理セルの摩耗やデータ破損のリスクが100%存在しない RAMDisk(メモリ上に確保した仮想ディスク空間)をターゲットパスに指定して実行することを強く推奨します。

🛠️ 5. 検証ツールの再現手順(ビルド・実行)

本検証結果は、Swift 6環境が導入されたMacのターミナルから、以下の3ステップを実行することで完全に再現可能です。

STEP 1: main.swift の作成

作業ディレクトリ内に、プロファイラプログラムの実体となる main.swift ファイルを作成し、v1.8.0ソースコードを配置します。

mkdir -p swift/SwiftBench
cd swift/SwiftBench
nano main.swift
import Foundation
import Darwin

// =======================================================================
// ーーー ブロック1: Swift 6 厳格並行性タイマー & システム情報の圧縮定義 ーーー
// =======================================================================

let SWIFT_F_FULLSYNC: Int32 = 49
let SWIFT_F_NOCACHE: Int32 = 48

struct PrecisionTimer: Sendable {
    private static let info: mach_timebase_info_data_t = {
        var i = mach_timebase_info_data_t(); mach_timebase_info(&i); return i
    }()
    
    @inline(__always) static func nano() -> UInt64 {
        let d = info.denom == 0 ? 1 : info.denom
        return mach_absolute_time() * UInt64(info.numer) / UInt64(d)
    }
    
    static func overhead() -> UInt64 {
        var m = UInt64.max; for _ in 1...1000 { let t = nano(), d = nano() - t; if d < m { m = d } }; return m
    }
}

struct SystemInfo: Sendable {
    static var ram: UInt64 {
        var n = [CTL_HW, HW_MEMSIZE], m = UInt64(0), s = MemoryLayout<UInt64>.size
        precondition(sysctl(&n, UInt32(n.count), &m, &s, nil, 0) == 0); return m
    }
    
    /// sysctlbynameを限界圧縮し、カーネルからプロセッサ名(M2 Ultra等)やモデル識別子を動的取得
    static func sysStr(_ k: String) -> String {
        var s = 0; sysctlbyname(k, nil, &s, nil, 0); var b = [CChar](repeating: 0, count: s)
        return sysctlbyname(k, &b, &s, nil, 0) == 0 ? String(cString: b) : "Unknown"
    }
    
    static func size(for p: String) -> Int {
        let g = Double(ram) / 1073741824.0, t = g <= 8.0 ? 4.0 : (g <= 16.0 ? 8.0 : 16.0)
        if let a = try? FileManager.default.attributesOfFileSystem(forPath: p), let f = a[.systemFreeSize] as? Int64 {
            let v = Double(f) / 1073741824.0; if t > (v * 0.2) { return Int(max(1.0, v * 0.15) * 1024.0) }
        }
        return Int(t * 1024.0)
    }
}
// =======================================================================
// ーーー ブロック2: 低級スレッドセーフ永続化マネージャー & I/Oワーカー ーーー
// =======================================================================

struct PersistenceManager: Sendable {
    static let dir = FileManager.default.currentDirectoryPath
    static var log: URL { URL(fileURLWithPath: dir).appendingPathComponent("b_\(f("yyyyMMdd")).log") }
    static var csv: URL { URL(fileURLWithPath: dir).appendingPathComponent("b_\(f("yyyyMMdd")).csv") }
    
    private static func f(_ p: String) -> String { let m = DateFormatter(); m.dateFormat = p; return m.string(from: Date()) }
    
    static func setup(m: String, o: String) {
        if !FileManager.default.fileExists(atPath: log.path) { try? "=== v1.8.0 Swift6 ===\nModel: \(m) | OS: \(o)\n\n".write(to: log, atomically: true, encoding: .utf8) }
        if !FileManager.default.fileExists(atPath: csv.path) { try? "TS,Path,State,SizeMB,SW_Avg,SW_Max,SW_Min,SR_Avg,SR_Max,SR_Min,RR_IOPS,RR_Max,RR_Min,RW_IOPS,RW_Max,RW_Min,SeqOvh,RROvh,RWOvh\n".write(to: csv, atomically: true, encoding: .utf8) }
    }
    
    static func wLog(_ t: String) {
        print(t, terminator: ""); if let h = try? FileHandle(forWritingTo: log) { h.seekToEndOfFile(); if let d = t.data(using: .utf8) { h.write(d); try? h.synchronize() }; h.closeFile() }
    }
    
    static func wCSV(_ p: String, _ s: String, _ z: Int, _ d: [Double], _ o: [UInt64]) {
        let l = String(format: "%@,%@,%@,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%llu,%llu,%llu\n", f("yyyy-MM-dd HH:mm:ss"), p, s, z, d, d, d, d, d, d, d, d, d, d, d, d, o, o, o)
        if let h = try? FileHandle(forWritingTo: csv) { h.seekToEndOfFile(); if let b = l.data(using: .utf8) { h.write(b); try? h.synchronize() }; h.closeFile() }
    }
}

struct WorkerResult: Sendable { let dur: UInt64; let count: Int; let ovh: UInt64 }

@Sendable func runRandWorker(fd: Int32, isR: Bool, blocks: Int32, bSize: Int, fSize: Int64, pat: [UInt8]) -> WorkerResult {
    let maxOff = (fSize - Int64(bSize)) / Int64(bSize); guard maxOff > 0 else { return WorkerResult(dur: 0, count: 0, ovh: 0) }
    var buf = [UInt8](repeating: 0, count: bSize), p = pat, selfOvh = PrecisionTimer.overhead(), start = PrecisionTimer.nano(), scOvh = UInt64(0), c = 0
    for _ in 0..<blocks {
        let off = Int64(arc4random_uniform(UInt32(maxOff))) * Int64(bSize), scS = PrecisionTimer.nano()
        if isR { _ = Darwin.pread(fd, &buf, bSize, off) } else { _ = Darwin.pwrite(fd, &p, bSize, off) }
        scOvh += (PrecisionTimer.nano() - scS); c += 1
    }
    let diff = PrecisionTimer.nano() - start
    return WorkerResult(dur: diff > selfOvh ? diff - selfOvh : 1, count: c, ovh: scOvh)
}

@Sendable func purge() { let p = Process(); p.executableURL = URL(fileURLWithPath: "/usr/sbin/purge"); try? p.run(); p.waitUntilExit() }

enum IOState: Sendable { case physical, cached }
// =======================================================================
// ーーー ブロック3: 限界圧縮 POSIX I/O プロファイリング・パイプライン ーーー
// =======================================================================

func executeCoreBenchmark(targetPath: String, state: IOState, iterations: Int = 3, largeFileSizeMB: Int, threadCount: Int = 4) {
    let stStr = state == .physical ? "物理層" : "キャッシュ", selfOvh = PrecisionTimer.overhead(), filePath = (targetPath as NSString).appendingPathComponent(".s_tmp.dat"), totalBytes = largeFileSizeMB * 1024 * 1024, sBuf = 1024 * 1024, rBuf = 4 * 1024, rBlocks: Int32 = 1000
    PersistenceManager.wLog("\n  >> [\(targetPath)] [\(stStr)] 開始 (サイズ: \(largeFileSizeMB)MB)\n")
    var r1M = [UInt8](repeating: 0, count: sBuf), r4K = [UInt8](repeating: 0, count: rBuf)
    if let u = fopen("/dev/urandom", "r") { _ = fread(&r1M, 1, sBuf, u); _ = fread(&r4K, 1, rBuf, u); fclose(u) }
    var wSpd: [Double] = [], rSpd: [Double] = [], rrIops: [Double] = [], rwIops: [Double] = [], sOvh: [UInt64] = [], rOvh: [UInt64] = [], wOvh: [UInt64] = []
    
    for _ in 1...iterations {
        if state == .physical { Darwin.sync(); purge(); Darwin.sleep(2) }
        
        // ① Sequential Write
        let fdW = Darwin.open(filePath, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); guard fdW >= 0 else { return }
        let swS = PrecisionTimer.nano(); var bW = 0
        while bW < totalBytes { let w = Darwin.write(fdW, &r1M, sBuf); if w <= 0 { break }; bW += w }
        if state == .physical { _ = Darwin.fcntl(fdW, SWIFT_F_FULLSYNC) } else { Darwin.fsync(fdW) }; Darwin.close(fdW)
        wSpd.append((Double(totalBytes)/1048576.0)/((Double(PrecisionTimer.nano() - swS) - Double(selfOvh))/1_000_000_000.0))
        
        // ② Sequential Read
        if state == .physical { Darwin.sync(); purge(); Darwin.sleep(2) }
        let fdR = Darwin.open(filePath, O_RDONLY); guard fdR >= 0 else { return }
        if state == .physical { _ = Darwin.fcntl(fdR, SWIFT_F_NOCACHE, 1 as CInt) }
        var rB = [UInt8](repeating: 0, count: sBuf), bR = 0, scO = UInt64(0), srS = PrecisionTimer.nano()
        while bR < totalBytes { let s = PrecisionTimer.nano(), r = Darwin.read(fdR, &rB, sBuf); if r <= 0 { break }; bR += r; scO += (PrecisionTimer.nano() - s) }
        let srE = PrecisionTimer.nano(); Darwin.close(fdR)
        rSpd.append((Double(totalBytes)/1048576.0)/(Double(srE - srS - selfOvh)/1_000_000_000.0))
        sOvh.append(bR / sBuf > 0 ? scO / UInt64(bR / sBuf) : 0)
        
        // ③ Distributed Rand Read
        if state == .physical { Darwin.sync(); purge(); Darwin.sleep(2) }
        let fdRR = Darwin.open(filePath, O_RDONLY); if state == .physical { _ = Darwin.fcntl(fdRR, SWIFT_F_NOCACHE, 1 as CInt) }
        let rrS = PrecisionTimer.nano(), semRR = DispatchSemaphore(value: 0), qRR = DispatchQueue(label: "q.rr", attributes: .concurrent), lRR = NSLock(); var resRR: [WorkerResult] = []
        for _ in 0..<threadCount { qRR.async { let r = runRandWorker(fd: fdRR, isR: true, blocks: rBlocks, bSize: rBuf, fSize: Int64(totalBytes), pat: r4K); lRR.lock(); resRR.append(r); lRR.unlock(); semRR.signal() } }
        for _ in 0..<threadCount { semRR.wait() }; let rrE = PrecisionTimer.nano(); Darwin.close(fdRR)
        let tRRC = resRR.reduce(0) { $0 + $1.count }, tRRO = resRR.reduce(0) { $0 + $1.ovh }
        rrIops.append(Double(tRRC)/(Double(rrE - rrS - selfOvh)/1_000_000_000.0))
        rOvh.append(tRRC > 0 ? tRRO / UInt64(tRRC) : 0)
        
        // ④ Distributed Rand Write
        if state == .physical { Darwin.sync(); purge(); Darwin.sleep(2) }
        let fdRW = Darwin.open(filePath, O_RDWR); if state == .physical { _ = Darwin.fcntl(fdRW, SWIFT_F_NOCACHE, 1 as CInt) }
        let rwS = PrecisionTimer.nano(), semRW = DispatchSemaphore(value: 0), qRW = DispatchQueue(label: "q.rw", attributes: .concurrent), lRW = NSLock(); var resRW: [WorkerResult] = []
        for _ in 0..<threadCount { qRW.async { let r = runRandWorker(fd: fdRW, isR: false, blocks: rBlocks, bSize: rBuf, fSize: Int64(totalBytes), pat: r4K); lRW.lock(); resRW.append(r); lRW.unlock(); semRW.signal() } }
        for _ in 0..<threadCount { semRW.wait() }; if state == .physical { _ = Darwin.fcntl(fdRW, SWIFT_F_FULLSYNC) } else { Darwin.fsync(fdRW) }; let rwE = PrecisionTimer.nano(); Darwin.close(fdRW)
        let tRWC = resRW.reduce(0) { $0 + $1.count }, tRWO = resRW.reduce(0) { $0 + $1.ovh }
        rwIops.append(Double(tRWC)/(Double(rwE - rwS - selfOvh)/1_000_000_000.0))
        wOvh.append(tRWC > 0 ? tRWO / UInt64(tRWC) : 0)
    }
    _ = Darwin.unlink(filePath)
    
    let aW = wSpd.reduce(0,+)/Double(iterations), aR = rSpd.reduce(0,+)/Double(iterations), aRR = rrIops.reduce(0,+)/Double(iterations), aRW = rwIops.reduce(0,+)/Double(iterations)
    let oS = sOvh.reduce(0,+)/UInt64(iterations), oRR = rOvh.reduce(0,+)/UInt64(iterations), oRW = wOvh.reduce(0,+)/UInt64(iterations)
    
    var o = "\n    📊 [ 集計結果 - \(stStr) ]\n"
    o += String(format: "      Seq Write : 平均 %.2f MB/s (最高: %.2f MB/s / 最低: %.2f MB/s)\n", aW, wSpd.max() ?? 0, wSpd.min() ?? 0)
    o += String(format: "      Seq Read  : 平均 %.2f MB/s (最高: %.2f MB/s / 最低: %.2f MB/s)\n", aR, rSpd.max() ?? 0, rSpd.min() ?? 0)
    o += String(format: "      Rand Read : 平均 %.0f IOPS (最高: %.0f IOPS / 最低: %.0f IOPS)\n", aRR, rrIops.max() ?? 0, rrIops.min() ?? 0)
    o += String(format: "      Rand Write: 平均 %.0f IOPS (最高: %.0f IOPS / 最低: %.0f IOPS)\n", aRW, rwIops.max() ?? 0, rwIops.min() ?? 0)
    o += "      --------------------------------------------------\n"
    o += "      純遅延 -> 1MB Read: \(oS)ns | 4KB RandRead: \(oRR)ns | 4KB RandWrite: \(oRW)ns\n"
    PersistenceManager.wLog(o)
    
    PersistenceManager.wCSV(targetPath, stStr, largeFileSizeMB, 
                           [aW, wSpd.max() ?? 0, wSpd.min() ?? 0, aR, rSpd.max() ?? 0, rSpd.min() ?? 0, aRR, rrIops.max() ?? 0, rrIops.min() ?? 0, aRW, rwIops.max() ?? 0, rwIops.min() ?? 0], 
                           [oS, oRR, oRW])
}
// =======================================================================
// ーーー ブロック4: Swift 6 トップレベル・エントリーポイント ーーー
// =======================================================================

func main() {
    let args = CommandLine.arguments; guard args.count > 1 else { print("Usage: sudo ./SwiftProfiler <Path1> [Path2] ..."); exit(1) }
    let cpu = SystemInfo.sysStr("machdep.cpu.brand_string"), hwm = SystemInfo.sysStr("hw.model"), os = "macOS Hardware Layer"
    PersistenceManager.setup(m: "\(hwm) (\(cpu))", o: os)
    
    PersistenceManager.wLog("==================================================\n")
    PersistenceManager.wLog(" macOS Swift 6 Low-Level Storage Profiler v1.8.0\n")
    PersistenceManager.wLog(" [Machine Model] : \(hwm)\n")
    PersistenceManager.wLog(" [Detected CPU]  : \(cpu)\n")
    PersistenceManager.wLog("==================================================\n")
    
    for i in 1..<args.count {
        let p = args[i], z = SystemInfo.size(for: p)
        PersistenceManager.wLog("\n📂 ターゲット [\(i)/\(args.count - 1)]: \(p) (動的テストサイズ: \(z) MB)\n--------------------------------------------------\n")
        executeCoreBenchmark(targetPath: p, state: .physical, iterations: 3, largeFileSizeMB: z, threadCount: 4)
        executeCoreBenchmark(targetPath: p, state: .cached, iterations: 3, largeFileSizeMB: z, threadCount: 4)
    }
    PersistenceManager.wLog("\n==================================================\n 正常に全セッションの同期永続化が完了しました。\n==================================================\n")
}

main()

STEP 2: 最適化ビルドの実行

Swift 6コンパイラを呼び出し、最高パフォーマンスを引き出すための最高度最適化フラグ(-O)を付与してバイナリをネイティブコンパイルします。

swiftc main.swift -O -o SwiftProfiler

コードは注意してご使用ください。

STEP 3: 特権モードでの測定実行

システムキャッシュの強制パージ(/usr/sbin/purge)およびカーネルバイパス用システムコール(fcntl)を確実に通過させるため、sudo を付与した特権モードで起動します。末尾にプロファイリングを行いたいマウントパスを引数として指定します。

sudo ./SwiftProfiler ./
sudo ./SwiftProfiler ./ /Volumes/USB_HDD02 /Volumes/RAMDisk

コードは注意してご使用ください。

🏁 総括

本ツール(v1.8.0)の実装は、通常のベンチマークソフトが表示する表面上の転送レートの裏側に隠されている、接続デバイスの物理的限界(APFSのCoWオーバーヘッド、磁気HDDの物理シーク遅延)を正確に分離・抽出できる強力な手段です。ハードウェアとファイルシステムの素性をニュートラルに評価する定点観測データとしてお役立てください。


タイトルとURLをコピーしました