macOSのキャッシュ有無におけるストレージ性能を視覚化。自作スクリプトで見るApple Siliconの物理特性とインメモリの実効レート

macOS

Macのストレージ速度を計測しようと思ったとき、一般的にはグラフィカルなUIを持つ専用の測定ソフトウェアを使うケースが多いと思います。
しかし、自作PCユーザーやベンチマーカー、Unixライクな環境を愛するギークの皆様なら、こう思ったことはないでしょうか。

「速度を測るためだけに、わざわざ外部の測定アプリをインストールしたくない」
「システムを汚さず、標準のZsh(ターミナル)とスクリプトだけでエレガントに完結させたい」

外部アプリを使用せず、標準のZsh(ターミナル)とPOSIX標準の道具だけで、macOSの優秀なファイルシステムキャッシュ(インメモリ)が効いている状態と、それを完全に除外した状態の数値をそれぞれ正確に測定・プロファイリングしたい。

そう思い立ち、AIを共同デバッガーとして壁打ちしながら書き上げたのが、macOS専用の高精度ストレージプロファイラー『mac_storage_benchmark.sh(Ver 1.0.0)』です。
本記事では、このスクリプトに実装した設計思想と、Apple M5(32GB)およびM2 Ultra(192GB)環境において、各ストレージ(内蔵SSD・外付けSSD/HDD・RAMDisk)がキャッシュの有無によってどのような数値を示すのか、その測定結果をありのままに共有します。

1. 計測エンジン『mac_storage_benchmark.sh』の設計思想

本スクリプト(Enterprise Refined Edition)は、macOSのファイルシステム特性を考慮し、キャッシュの影響を取り除いた「物理層実効レート」と、キャッシュを活用した「インメモリレート」の2つの側面を厳密に仕分けて視覚化します。

macOSのメモリ管理「統合バッファキャッシュ(UBC)」の仕組み

なぜ、キャッシュの有無を仕分ける必要があるのか。その理由は、macOSに実装されている「アプリケーションが使用していない空きメモリ(RAM)を、すべてストレージの予備キャッシュとして自動的に割り当てる」という効率的な構造にあります。

この挙動は、よく「作業机と引き出し」の関係に例えられます。

  • データの書き込み:本来は「引き出し(SSD)」に格納すべきデータを、OSが判断して「まずはアクセスが速い机の上(メモリ)に置いておく」ことで、一瞬で処理を完了させます。
  • データの読み込み:直前に触ったデータをもう一度読もうとすると、引き出し(SSD)を開けずに、机の上からそのままデータを引き渡します。

このメモリ管理のおかげでMacの普段の動作は非常に滑らかなのですが、ハードウェア単体の純粋な性能を切り離して確認したい場合には、このメモリの速度が合算されて計算されます。

これはAppleが公式に実装している「UBC(Unified Buffer Cache)」というカーネルレベルの正式な仕様に基づいています。詳細な構造は、Appleの公式ドキュメントにも明記されています。

About the Virtual Memory System
Guidelines for improving the performance of memory-related code.

この「作業机(メモリ)からサッとデータを差し出す」macOSの挙動は、Appleの公式開発者ドキュメントにおいて『Soft Fault(ソフトフォールト)』として定義されている正式なシステム仕様です。メモリ上に残っているバッファを再利用するためストレージへの物理アクセスが発生しません。本スクリプトはこのSoft Faultを意図的に排除(Hard Faultを強制)することで、デバイス本来の数値を視覚化しています。

実際、このキャッシュ機能が働いている状態(キャッシュ有効)では、物理的な転送レートを遥かに超えた速度(例:外付けHDDで読み込み速度15GB/sなど)という、メモリの暴力とも言える圧倒的な実効レートが記録されます。これはこれで「実用上の体感速度」として正しい数値(システム全体の調和の成果)なのですが、ベンチマーカーとしては「ストレージ本来の素の性能」も分けて確認したくなります。

そこで、本スクリプトでは以下の3つのアプローチによって、キャッシュ有無の双方の状態を正確に測定しています。

① キャッシュをクリアした状態の測定(clear_cache)

メモリ上に残っているキャッシュの影響を排除し、ストレージ本来の素の性能を測定するため、物理層の測定前には以下の処理を実行しています。

clear_cache() {
  sync && sync && purge
  sleep 3
}
  • sync(2回実行):メモリ上の未保存データを確実に物理ディスクへ書き込ませます。
  • purge:macOS標準のバッファキャッシュ強制解放コマンドを実行し、メモリ上のディスクキャッシュをクリアします。
  • sleep 3:OS의 クリーンアップ処理が完全に落ち着くまで3秒間待機します。

この処理を挟むことで、毎回キャッシュの影響を受けない「ストレージデバイスそのものの読み書き速度」を正確に視覚化できるようになります。

② コントローラーをバイパスする「Perl内製リアルランダムI/O」

本スクリプトでは「Zero埋めデータ」と「リアルランダムデータ(擬似乱数)」の2パターンを用意し、さらにシェル内に低レイヤのPerlスクリプトを直接組み込んで双方を比較・検証できるようにしています。

# スクリプト中盤のシーケンシャル非圧縮書き込み(1-B)より抜粋
perl -e '
  open(my $fh, ">", "'"${file_seq_rand}"'") or die $!;
  my $buf = pack("C*", map { int(rand(256)) } 1..1048576);
  for (1..'"${LARGE_FILE_SIZE}"') { syswrite($fh, $buf); }
  close($fh);
' && sync

OSの標準的なバッファリングをバイパスするシステムコール(sysseek / syswrite / sysread)をPerl側から直接発行することで、一度UBCに載ったインメモリの数値と、完全にディスクの門を叩きにいく物理層の実効レートの双方を正確にプロファイリングできる設計にしています。

③ zshネイティブな型キャストと自動ラベル判定

スクリプトの末尾には、ツールを安全に動かし、ログをデバイスごとに美しく分類するための工夫を施しています。

まずは「空き容量ガード」の型キャスト(丸め処理)です。

# 浮動小数点を整数変数に代入することで、Zshネイティブに安全な型丸め(キャスト)を実行
local -F required_mb_float=$(( LARGE_FILE_SIZE * multiplier * SAFETY_MULTIPLIER ))
local -i required_mb_int=$required_mb_float

Zshの特性に合わせ、あえて「整数型(-i)」の変数に「浮動小数点型(-F)」の値を代入することで、エラーを起こさず安全に値を丸め込み、ストレージ溢れを未然に防ぐ安全設計にしています。

さらに、引数に渡されたパスをzshの正規表現(=~)を用いて自動で判別するラベル貼りロジックも組み込み、Internal-SSD(Home) や External-[ドライブ名] とログに自動で仕分けて出力する、視覚化ツールとしての完成度を高めました。

2. 『mac_storage_benchmark.sh』の実践的な使い方

本スクリプトは正確なディスクキャッシュの強制解放(purge)を行う仕様上、実行には管理者権限(sudo)が必要です。また、パス判定ロジックへ環境変数を正確に引き継ぐため、必ず -E オプションを付与して呼び出します。

ターミナルを開き、目的に応じて以下のコマンドを叩き分けることで、複数デバイスのプロファイリングを全自動で実行できます。

パターンA:最も基本。内蔵SSDのカレントディレクトリを測定する

現在作業しているディレクトリ(内蔵SSD上)の性能をシンプルに測定したい場合の基本形です。引数に ./ を渡します。

sudo -E ./mac_storage_benchmark.sh ./
パターンB:複数ストレージを一挙に連続測定する(今回の検証方法)

スペース区切りでパスを並べることで、内蔵SSD、外付けドライブ、メモリ上に構築したRAMディスクなどをノンストップで連続プロファイリングできます。スクリプトが引数を精密に条件分岐し、各ログにデバイス名の自動ラベル貼りを行います。

sudo -E ./mac_storage_benchmark.sh ./ /Volumes/USB-SSD /Volumes/RAMDisk
パターンC:外付けHDDやNASなど、特定の専用マウント先だけを指定する

内蔵SSDへの書き込みを発生させず、特定の外付けディスクや外部ボリュームの挙動だけを集中的にプロファイリングしたい場合は、対象のボリュームパスだけをピンポイントで指定します。

sudo -E ./mac_storage_benchmark.sh /Volumes/USB_HDD02

※いずれの実行パターンでも、処理が完了するとスクリプトと同じディレクトリ内に詳細ログ(.log)と、時系列比較用のCSV(.csv)が自動で永続化保存されます。

3. 検証データ①:Apple M5(32GB)環境でのプロファイリング

最新アーキテクチャ、Apple M5チップ(10コア/32GBメモリ)での実際の実行結果です。

【生ログデータ】 M5 Mac 実行結果
==================================================
 STORAGE BENCHMARK REPORT (Dual-Pattern Full Stats)
--------------------------------------------------
 [Execution Date] : 2026-06-12 20:13:36
 [System Information]
   Model    : Mac17,2
   CPU      : Apple M5
   Topology : Performance 4 Cores / Efficiency 6 Cores (Total 10 Threads)
   Memory   : 32 GB Unified Memory
   OS       : macOS 26.5.1 (Build 25F80)
--------------------------------------------------
 [Hardware Specs]
   S.M.A.R.T. Health: Verified
   TRIM Support     : Unknown / N/A
   Controller Type  : Apple NVMe Controller Native
--------------------------------------------------
 [Execution Specs]
   Large File Size  : 1024 MB
   Random Block     : 4 KB
   Concurrency      : 4 Parallel Threads
   Sampling Engine  : Iterations=3
   Mode Switching   : Cache-Test Execution = 1
==================================================

▶ [Analyzing Enterprise Workload] Internal-SSD(Current)
  (Path: /Users/***)
  📊 [連続I/O(シーケンシャル性能)- キャッシュ無効状態(物理層実効レート)]
    - インライン圧縮データ (書き込み): 平均  2771.13 MB/s | 最高  3289.33 MB/s | 最低  1876.21 MB/s
    - 非圧縮リアルランダム (書き込み): 平均  2650.46 MB/s | 最高  2897.08 MB/s | 最低  2480.92 MB/s
    - インライン圧縮データ (読み込み): 平均  5105.79 MB/s | 最高  5278.08 MB/s | 最低  4892.26 MB/s
    - 非圧縮リアルランダム (読み込み): 平均  5038.07 MB/s | 最高  5099.86 MB/s | 最低  4922.60 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ無効状態(物理層実効レート)]
    - マルチスレッド並行上書き   (並列4): 平均    88.16 MB/s | 最高    92.58 MB/s | 最低    79.70 MB/s [ 22567 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均   169.30 MB/s | 最高   171.08 MB/s | 最低   168.19 MB/s [ 43340 IOPS ]


  📊 [連続I/O(シーケンシャル性能)- キャッシュ有効状態(インメモリレート)]
    - インライン圧縮データ (書き込み): 平均  2905.26 MB/s | 最高  3660.93 MB/s | 最低  2067.85 MB/s
    - 非圧縮リアルランダム (書き込み): 平均  2930.71 MB/s | 最高  3004.69 MB/s | 最低  2859.22 MB/s
    - インライン圧縮データ (読み込み): 平均  9939.83 MB/s | 最高 10079.72 MB/s | 最低  9865.12 MB/s
    - 非圧縮リアルランダム (読み込み): 平均  9844.77 MB/s | 最高 10214.46 MB/s | 最低  9225.24 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ有効状態(インメモリレート)]
    - マルチスレッド並行上書き   (並列4): 平均   159.52 MB/s | 最高   163.22 MB/s | 最低   154.05 MB/s [ 40836 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均   795.27 MB/s | 最高   802.52 MB/s | 最低   782.02 MB/s [ 203590 IOPS ]


▶ [Analyzing Enterprise Workload] External-[USB-SSD]
  (Path: /Volumes/USB-SSD)
  📊 [連続I/O(シーケンシャル性能)- キャッシュ無効状態(物理層実効レート)]
    - インライン圧縮データ (書き込み): 平均   343.41 MB/s | 最高   458.97 MB/s | 最低   129.45 MB/s
    - 非圧縮リアルランダム (書き込み): 平均    68.20 MB/s | 最高   103.94 MB/s | 最低    45.64 MB/s
    - インライン圧縮データ (読み込み): 平均   504.93 MB/s | 最高   508.85 MB/s | 最低   502.83 MB/s
    - 非圧縮リアルランダム (読み込み): 平均   506.37 MB/s | 最高   517.34 MB/s | 最低   499.37 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ無効状態(物理層実効レート)]
    - マルチスレッド並行上書き   (並列4): 平均    10.19 MB/s | 最高    24.53 MB/s | 最低     0.19 MB/s [  2608 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均    14.42 MB/s | 最高    42.49 MB/s | 最低     0.38 MB/s [  3691 IOPS ]


  📊 [連続I/O(シーケンシャル性能)- キャッシュ有効状態(インメモリレート)]
    - インライン圧縮データ (書き込み): 平均    46.69 MB/s | 最高    47.44 MB/s | 最低    45.85 MB/s
    - 非圧縮リアルランダム (書き込み): 平均    46.02 MB/s | 最高    46.67 MB/s | 最低    45.65 MB/s
    - インライン圧縮データ (読み込み): 平均  6013.24 MB/s | 最高  9356.72 MB/s | 最低   489.06 MB/s
    - 非圧縮リアルランダム (読み込み): 平均  7448.66 MB/s | 最高 11499.15 MB/s | 最低   510.72 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ有効状態(インメモリレート)]
    - マルチスレッド並行上書き   (並列4): 平均    34.24 MB/s | 最高    39.87 MB/s | 最低    31.32 MB/s [  8764 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均   765.36 MB/s | 最高   800.46 MB/s | 最低   724.39 MB/s [ 195931 IOPS ]


▶ [Analyzing Enterprise Workload] RAMDisk
  (Path: /Volumes/RAMDisk)
  📊 [連続I/O(シーケンシャル性能)- キャッシュ無効状態(物理層実効レート)]
    - インライン圧縮データ (書き込み): 平均  3696.75 MB/s | 最高  3960.70 MB/s | 最低  3535.55 MB/s
    - 非圧縮リアルランダム (書き込み): 平均  2691.55 MB/s | 最高  2787.91 MB/s | 最低  2540.75 MB/s
    - インライン圧縮データ (読み込み): 平均  5411.38 MB/s | 最高  5696.48 MB/s | 最低  4916.23 MB/s
    - 非圧縮リアルランダム (読み込み): 平均  6104.95 MB/s | 最高  6697.19 MB/s | 最低  5619.89 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ無効状態(物理層実効レート)]
    - マルチスレッド並行上書き   (並列4): 平均   109.70 MB/s | 最高   112.92 MB/s | 最低   106.86 MB/s [ 28083 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均   216.00 MB/s | 最高   235.71 MB/s | 最低   197.71 MB/s [ 55294 IOPS ]


  📊 [連続I/O(シーケンシャル性能)- キャッシュ有効状態(インメモリレート)]
    - インライン圧縮データ (書き込み): 平均  3934.83 MB/s | 最高  4263.30 MB/s | 最低  3609.70 MB/s
    - 非圧縮リアルランダム (書き込み): 平均  2836.73 MB/s | 最高  2946.34 MB/s | 最低  2729.72 MB/s
    - インライン圧縮データ (読み込み): 平均 10494.47 MB/s | 最高 11215.77 MB/s | 最低 10086.68 MB/s
    - 非圧縮リアルランダム (読み込み): 平均 10884.52 MB/s | 最高 12769.68 MB/s | 最低  8365.33 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ有効状態(インメモリレート)]
    - マルチスレッド並行上書き   (並列4): 平均   141.68 MB/s | 最高   146.91 MB/s | 最低   135.98 MB/s [ 36269 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均   765.81 MB/s | 最高   793.15 MB/s | 最低   734.95 MB/s [ 196046 IOPS ]


==================================================
🔍 M5 Mac 測定結果の詳細解説

M5 Mac環境でのフルログデータは、キャッシュ制御の有無によって各デバイスが全く異なる表情を見せてくれる、ベンチマーカー歓喜の挙動となっています。

  • 内蔵SSD:物理層での「43,340 IOPS」
    キャッシュ無効状態の非圧縮リアルランダム読み込みで平均 5,038.07 MB/s、分散シーク(ランダム4KB読み込み)で43,340 IOPSを記録。M5の内蔵コントローラーが持つ素の読み書き能力が正確に数値化されています。キャッシュ有効時にはシーケンシャル読み込みが約10GB/s(平均 9,844.77 MB/s)、ランダム読み込みは203,590 IOPSまで一気に跳ね上がり、32GBのメモリ空間をフル活用したUBCの高速処理が如実に現れています。
  • 外付けUSB-SSD:キャッシュの有無による挙動の乱高下
    注目すべきは /Volumes/USB-SSD です。キャッシュ無効(物理)時は、非圧縮読込がインターフェース限界に近い平均 506.37 MB/s、ランダムが3,691 IOPSと、このドライブ自体の生の実力が見えています。
    これがキャッシュ有効(インメモリ)になると、非圧縮読込の最高値が11,499.15 MB/s(約11.5GB/s)まで一瞬跳ね上がります。これはM5マシンの32GB Unified Memoryにデータが乗ったためです。しかし平均値は7,448.66 MB/sであり、最低値は生のドライブ速度に近い510.72 MB/sまで綺麗に落ち込んでいます。OSがバックグラウンドでメモリ空間のデータを物理SSDへと同期・フラッシュするタイミングのブレが、最高と最低の激しいギャップとして生々しく可視化されています。
  • RAMDisk:M5メモリバス幅の実効レート
    物理(キャッシュパージ状態)としての RAMDisk は、非圧縮読込で平均 6,104.95 MB/s、ランダムで55,294 IOPSを記録。内蔵SSDを上回る低レイテンシを見せています。キャッシュ有効時には最大で12,769.68 MB/s(約12.7GB/s)に達し、32GBメモリ環境におけるLPDDR系の実効帯域の天井近くを綺麗にトレースしています。

4. 検証データ②:M2 Ultra(192GB)環境でのプロファイリング

続いて、圧倒的な物量で作られたモンスターマシン、M2 Ultra(24コア/192GBメモリ)環境でのデータです。ここでは内蔵SSD、RAMDisk、外部ストレージ、そして外付けHDD(USB_HDD02)の挙動を追います。

【生ログデータ】 M2 Ultra Mac 実行結果
==================================================
 STORAGE BENCHMARK REPORT (Dual-Pattern Full Stats)
--------------------------------------------------
 [Execution Date] : 2026-06-12 16:35:25
 [System Information]
   Model    : Mac14,14
   CPU      : Apple M2 Ultra
   Topology : Performance 16 Cores / Efficiency 8 Cores (Total 24 Threads)
   Memory   : 192 GB Unified Memory
   OS       : macOS 26.5.1 (Build 25F80)
--------------------------------------------------
 [Hardware Specs]
   S.M.A.R.T. Health: Verified
   TRIM Support     : Unknown / N/A
   Controller Type  : Apple NVMe Controller Native
--------------------------------------------------
 [Execution Specs]
   Large File Size  : 1024 MB
   Random Block     : 4 KB
   Concurrency      : 4 Parallel Threads
   Sampling Engine  : Iterations=3
   Mode Switching   : Cache-Test Execution = 1
==================================================

▶ [Analyzing Enterprise Workload] Internal-SSD(Home)
  (Path: /Users/***)
  📊 [連続I/O(シーケンシャル性能)- キャッシュ無効状態(物理層実効レート)]
    - インライン圧縮データ (書き込み): 平均  4168.10 MB/s | 最高  4680.29 MB/s | 最低  3557.53 MB/s
    - 非圧縮リアルランダム (書き込み): 平均  3055.67 MB/s | 最高  3266.35 MB/s | 最低  2877.78 MB/s
    - インライン圧縮データ (読み込み): 平均  5839.35 MB/s | 最高  5989.36 MB/s | 最低  5582.82 MB/s
    - 非圧縮リアルランダム (読み込み): 平均  5856.31 MB/s | 最高  5966.33 MB/s | 最低  5687.63 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ無効状態(物理層実効レート)]
    - マルチスレッド並行上書き   (並列4): 平均    77.36 MB/s | 最高    78.00 MB/s | 最低    76.70 MB/s [ 19802 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均   136.92 MB/s | 最高   139.57 MB/s | 最低   132.93 MB/s [ 35052 IOPS ]


  📊 [連続I/O(シーケンシャル性能)- キャッシュ有効状態(インメモリレート)]
    - インライン圧縮データ (書き込み): 平均  4073.10 MB/s | 最高  4670.68 MB/s | 最低  3031.83 MB/s
    - 非圧縮リアルランダム (書き込み): 平均  2947.77 MB/s | 最高  3210.33 MB/s | 最低  2427.57 MB/s
    - インライン圧縮データ (読み込み): 平均 14344.74 MB/s | 最高 14834.12 MB/s | 最低 13890.40 MB/s
    - 非圧縮リアルランダム (読み込み): 平均 14208.86 MB/s | 最高 14420.48 MB/s | 最低 13924.40 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ有効状態(インメモリレート)]
    - マルチスレッド並行上書き   (並列4): 平均   149.85 MB/s | 最高   159.41 MB/s | 最低   143.93 MB/s [ 38362 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均   964.05 MB/s | 最高  1058.60 MB/s | 最低   892.86 MB/s [ 246797 IOPS ]


▶ [Analyzing Enterprise Workload] Target-2
  (Path: /Volumes)
  📊 [連続I/O(シーケンシャル性能)- キャッシュ無効状態(物理層実効レート)]
    - インライン圧縮データ (書き込み): 平均  4377.98 MB/s | 最高  4846.19 MB/s | 最低  4074.97 MB/s
    - 非圧縮リアルランダム (書き込み): 平均  3240.20 MB/s | 最高  3271.67 MB/s | 最低  3186.76 MB/s
    - インライン圧縮データ (読み込み): 平均  5934.63 MB/s | 最高  5977.82 MB/s | 最低  5887.08 MB/s
    - 非圧縮リアルランダム (読み込み): 平均  5857.08 MB/s | 最高  5988.65 MB/s | 最低  5741.52 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ無効状態(物理層実効レート)]
    - マルチスレッド並行上書き   (並列4): 平均    75.93 MB/s | 最高    76.68 MB/s | 最低    75.51 MB/s [ 19438 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均   140.55 MB/s | 最高   143.10 MB/s | 最低   137.27 MB/s [ 35981 IOPS ]


  📊 [連続I/O(シーケンシャル性能)- キャッシュ有効状態(インメモリレート)]
    - インライン圧縮データ (書き込み): 平均  3774.29 MB/s | 最高  4059.14 MB/s | 最低  3487.62 MB/s
    - 非圧縮リアルランダム (書き込み): 平均  2945.02 MB/s | 最高  3106.23 MB/s | 最低  2644.77 MB/s
    - インライン圧縮データ (読み込み): 平均 14123.15 MB/s | 最高 14281.76 MB/s | 最低 14010.11 MB/s
    - 非圧縮リアルランダム (読み込み): 平均 14248.87 MB/s | 最高 14479.60 MB/s | 最低 13880.97 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ有効状態(インメモリレート)]
    - マルチスレッド並行上書き   (並列4): 平均   147.00 MB/s | 最高   148.67 MB/s | 最低   146.01 MB/s [ 37631 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均   918.36 MB/s | 最高   942.96 MB/s | 最低   903.70 MB/s [ 235101 IOPS ]


▶ [Analyzing Enterprise Workload] RAMDisk
  (Path: /Volumes/RAMDisk)
  📊 [連続I/O(シーケンシャル性能)- キャッシュ無効状態(物理層実効レート)]
    - インライン圧縮データ (書き込み): 平均  4840.14 MB/s | 最高  5126.66 MB/s | 最低  4350.41 MB/s
    - 非圧縮リアルランダム (書き込み): 平均  3348.53 MB/s | 最高  3513.59 MB/s | 最低  3044.63 MB/s
    - インライン圧縮データ (読み込み): 平均  8317.31 MB/s | 最高  9169.05 MB/s | 最低  6675.79 MB/s
    - 非圧縮リアルランダム (読み込み): 平均  8107.48 MB/s | 最高  9251.05 MB/s | 最低  6815.31 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ無効状態(物理層実効レート)]
    - マルチスレッド並行上書き   (並列4): 平均   111.14 MB/s | 最高   114.70 MB/s | 最低   108.24 MB/s [ 28452 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均   262.73 MB/s | 最高   269.82 MB/s | 最低   252.79 MB/s [ 67259 IOPS ]


  📊 [連続I/O(シーケンシャル性能)- キャッシュ有効状態(インメモリレート)]
    - インライン圧縮データ (書き込み): 平均  6078.63 MB/s | 最高  6216.62 MB/s | 最低  5933.82 MB/s
    - 非圧縮リアルランダム (書き込み): 平均  4151.57 MB/s | 最高  4205.68 MB/s | 最低  4056.09 MB/s
    - インライン圧縮データ (読み込み): 平均 13729.54 MB/s | 最高 15607.39 MB/s | 最低 10040.20 MB/s
    - 非圧縮リアルランダム (読み込み): 平均 15076.66 MB/s | 最高 16120.92 MB/s | 最低 13475.46 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ有効状態(インメモリレート)]
    - マルチスレッド並行上書き   (並列4): 平均   134.95 MB/s | 最高   137.71 MB/s | 最低   131.34 MB/s [ 34548 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均  1096.21 MB/s | 最高  1176.59 MB/s | 最低  1038.90 MB/s [ 280630 IOPS ]


▶ [Analyzing Enterprise Workload] External-[USB_HDD02]
  (Path: /Volumes/USB_HDD02)
  📊 [連続I/O(シーケンシャル性能)- キャッシュ無効状態(物理層実効レート)]
    - インライン圧縮データ (書き込み): 平均   104.36 MB/s | 最高   104.83 MB/s | 最低   103.48 MB/s
    - 非圧縮リアルランダム (書き込み): 平均   104.01 MB/s | 最高   104.41 MB/s | 最低   103.69 MB/s
    - インライン圧縮データ (読み込み): 平均   107.70 MB/s | 最高   108.20 MB/s | 最低   107.08 MB/s
    - 非圧縮リアルランダム (読み込み): 平均   108.12 MB/s | 最高   108.28 MB/s | 最低   107.83 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ無効状態(物理層実効レート)]
    - マルチスレッド並行上書き   (並列4): 平均     0.34 MB/s | 最高     0.41 MB/s | 最低     0.28 MB/s [    87 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均     0.42 MB/s | 最高     0.50 MB/s | 最低     0.35 MB/s [   108 IOPS ]


  📊 [連続I/O(シーケンシャル性能)- キャッシュ有効状態(インメモリレート)]
    - インライン圧縮データ (書き込み): 平均   102.69 MB/s | 最高   105.34 MB/s | 最低   100.50 MB/s
    - 非圧縮リアルランダム (書き込み): 平均   104.51 MB/s | 最高   105.22 MB/s | 最低   104.00 MB/s
    - インライン圧縮データ (読み込み): 平均 15557.39 MB/s | 最高 15686.28 MB/s | 最低 15299.61 MB/s
    - 非圧縮リアルランダム (読み込み): 平均 15802.42 MB/s | 最高 15908.05 MB/s | 最低 15652.73 MB/s
  📊 [ランダムI/O(分散シーク性能)- キャッシュ有効状態(インメモリレート)]
    - マルチスレッド並行上書き   (並列4): 平均     1.66 MB/s | 最高     1.69 MB/s | 最低     1.64 MB/s [   425 IOPS ]
    - マルチスレッド並行読み込み (並列4): 平均  1192.45 MB/s | 最高  1195.50 MB/s | 最低  1188.19 MB/s [ 305266 IOPS ]


==================================================
🔍 M2 Ultra Mac 測定結果の詳細解説

M2 Ultra環境でのデータは、192GBという圧倒的な物量がOSのバッファ管理と結びついたとき、どのような性能データが描かれるのかを鮮明に可視化しています。

  • RAMDiskが示す生速度の頂点
    キャッシュ無効(物理)状態での RAMDisk は、非圧縮読み込みにおいて平均 8,107.48 MB/s、ランダムアクセスで67,259 IOPSという極めて高いハードウェア性能をマーク。多チャンネル構成の内蔵SSD(5,856.31 MB/s・35,052 IOPS)に対して、メモリドライブ本来の圧倒的な低遅延・広帯域が綺麗にグラフ化されています。
  • 外付けHDD(USB_HDD02)に宿る「15.8GB/s」の実効レート
    最下部の USB_HDD02(外付けHDD)に、このマシンのバッファ構造が顕著に現れています。キャッシュ無効状態のランダムアクセスはわずか 108 IOPS(平均0.42 MB/s)。ヘッドが物理駆動するHDDとしての正確な限界値を捉えています。
    しかし、キャッシュを有効にした瞬間、非圧縮読み込みは平均 15,802.42 MB/s(約15.8GB/s、最高は15,908.05 MB/s)、ランダムアクセスは305,266 IOPSという、今回の全検証中でトップの数値へ変貌しました。

これは前述の通り、192GBという超巨大なメモリ空間を誇るUnified Memoryを、macOSのUBC(統合バッファキャッシュ)が低速な外付けHDDのバッファリング用に割り当てた結果です。2回目以降のアクセスはストレージへのアクセスを完全にスルーし、超広帯域なシステムメモリ上だけでデータが処理されているため、このような極端な差がデータとして視覚化されます。

5. まとめ:データから見るmacOSのI/Oプロファイリング

今回のスクリプトを用いた検証によって、以下の結果が明確になりました。

  1. キャッシュを除外した物理層の測定では、新世代M5のコントローラーやコア設計による効率的なランダムアクセス(43,340 IOPS)が確認できる。
  2. キャッシュを活かした状態の測定では、搭載メモリ量とメモリバス幅(M2 Ultraの広帯域)が全体の転送速度(約15.8GB/s)に直接影響を与える。

グラフィカルな測定ソフトウェアの数値を見るだけでなく、測定の前提条件(キャッシュの有無)を仕分けてシステムを観察することで、ハードウェア単体の素の特性と、OSのメモリ管理による実用的な速度の双方が客観的に視覚化されます。

外部のアプリケーションをシステムに追加することなく、標準のターミナル環境とPOSIX準拠の道具だけでここまで詳細なプロファイリングができる点に、シェルスクリプトを活用した計測の面白さがあります。

mac_storage_benchmark.sh
#!/bin/zsh

# =======================================================================
# Script Name : mac_storage_benchmark.sh
# Version     : 1.0.0 (Enterprise Refined Edition)
# License     : MIT License (Copyright (c) 2026 ohllengeapplication.com 管理人)
# Description : macOS専用 ストレージ物理・キャッシュ体感性能プロファイラー
#  【免責事項】
#   本スクリプトの使用によるデータ損失やシステム不具合等の損害について、
#   作者は一切の責任を負いません。すべて自己責任でご利用ください。
# =======================================================================
#
# 【概要 / Description】
#   本ツールは、プロセス生成によるオーバーヘッドやOSキャッシュの影響をコード
#   レベルで排除、あるいはコントロールしてストレージ本来の物理的な実効性能を
#   客観的に捉えるために設計された、macOS専用の高精度な性能プロファイラーです。
# 
#   連続I/O(シーケンシャル)およびマルチスレッド分散シークI/O(ランダム4K)の
#   負荷テストを通じ、macOSの強力なバッファリング効果(インメモリレート)と、
#   それらを完全にパージしたストレージ本来のハードウェア特性(物理層実効レート)を
#   厳密に仕分け、システム全体の協調動作の構造を正確に可視化します。
#
# 【本スクリプトの特徴 / Key Features】
#   1. デュアルフェーズ測定 : OSキャッシュを強制解放した「物理層の素の速度」と、
#                            キャッシュを効かせた「実用上の体感速度」を分離して測定可能。
#   2. CUI/CLI自動化対応    : 画面操作が不要なため、複数パスの連続測定や定期バッチ処理、
#                            SSH経由で遠隔地にあるMac mini等の測定にも使えます。
#   3. 非圧縮データテスト   : コントローラーによるインライン圧縮が効かない擬似乱数データを
#                            低レイヤ(Perl内製I/O)で高速処理し、純粋な物理限界を正確に評価。
#
# 【使用方法 / Usage】
#   本スクリプトは、正確なディスクキャッシュ強制解放(purge)および
#   システム情報の完全な抽出を行うため、管理者権限(sudo)と環境変数の引き継ぎ(-E)が必要です。
#
#   $ sudo -E ./mac_storage_benchmark.sh <測定対象パス1> [測定対象パス2] ...
#
#   (例) 内蔵SSDのホームディレクトリと外付けSSDを同時に測定する場合:
#   $ sudo -E ./mac_storage_benchmark.sh ~/ /Volumes/ExternalSSD
#
# 【出力および処理仕様 / Outputs & Specifications】
#   1. 統計データの永続化 : 実行毎に詳細テキストログ(.log)と、時系列比較用のCSV(.csv)を自動生成。
#   2. 高精度な統計抽出   : 指定したサンプリング回数の平均・最高・最低レートを自動算出。
#   3. 安全容量ガード     : 安全マージンを計算し、ストレージ溢れを未然に防ぐ自動スキップ。
#   4. 自動クリーンアップ : 測定に用いた一時ファイル・ディレクトリは処理終了時に自動消去。
# =======================================================================

# ーー 測定エンジン静的パラメータ設定(マジックナンバー徹底排除) ーー
typeset -r -i BLOCK_SIZE_KB=4          # ランダムI/Oのブロックサイズ (KB)
typeset -r -i RANDOM_TEST_BLOCKS=1000  # 1スレッドあたりのI/O実行回数 (4000回フォークバグ対策済)
typeset -r -i THREADS=4                # マルチスレッド並列実行数
typeset -r -i LARGE_FILE_SIZE=1024     # シーケンシャルテストファイルサイズ (MB)
typeset -r -i ITERATIONS=3             # サンプリング合計測定回数
typeset -r -i ENABLE_CACHE_TEST=1      # 1=キャッシュ有効テストも実行、0=解放ありのみ
typeset -r    SAFETY_MULTIPLIER="2.5"  # 必要容量の安全マージン係数

# ーー 環境パスの安全なクリーン復旧 ーー
export PATH="/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:$PATH"

# ーー 実行権限および引数バリデーション ーー
if [ "$#" -lt 1 ]; then
  echo "Error: 測定対象パスを指定してください。"
  echo "Usage: sudo -E $0 <測定対象パス1> [測定対象パス2] ..."
  exit 1
fi

if [ "$EUID" -ne 0 ]; then
  echo "Error: 権限が不足しています。sudo -E $0 <パス> で実行してください。"
  exit 1
fi

# ーー ログファイル・CSVファイルの動的パス設定 ーー
typeset -r LOG_DIR=$(dirname "${0:a}")
typeset -r CURRENT_DATE=$(date +%Y%m%d)
typeset -r LOG_FILE="${LOG_DIR}/benchmark_${CURRENT_DATE}.log"
typeset -r CSV_FILE="${LOG_DIR}/benchmark_${CURRENT_DATE}.csv"

# 出力ディレクトリの作成
mkdir -p "$LOG_DIR" 2>/dev/null

# CSVヘッダーの永続化初期化
if [ ! -f "$CSV_FILE" ]; then
  echo "Timestamp,Model,TargetLabel,Path,State,SeqWrite_Zero_Avg(MB/s),SeqWrite_Zero_Max(MB/s),SeqWrite_Zero_Min(MB/s),SeqWrite_Rand_Avg(MB/s),SeqWrite_Rand_Max(MB/s),SeqWrite_Rand_Min(MB/s),SeqRead_Zero_Avg(MB/s),SeqRead_Zero_Max(MB/s),SeqRead_Zero_Min(MB/s),SeqRead_Rand_Avg(MB/s),SeqRead_Rand_Max(MB/s),SeqRead_Rand_Min(MB/s),RandWrite_Avg(MB/s),RandWrite_Max(MB/s),RandWrite_Min(MB/s),RandWrite_IOPS,RandRead_Avg(MB/s),RandRead_Max(MB/s),RandRead_Min(MB/s),RandRead_IOPS" > "$CSV_FILE"
fi
# =======================================================================
# ーーー macOSシステムスペック動的抽出 ーーー
# =======================================================================
typeset -r mac_model=$(sysctl -n hw.model 2>/dev/null)
typeset -r mac_cpu=$(sysctl -n machdep.cpu.brand_string 2>/dev/null || sysctl -n hw.brand_string 2>/dev/null)
typeset -r mac_total_cores=$(sysctl -n hw.ncpu 2>/dev/null)
typeset -r mac_p_cores=$(sysctl -n hw.perflevel0.physicalcpu 2>/dev/null)
typeset -r mac_e_cores=$(sysctl -n hw.perflevel1.physicalcpu 2>/dev/null)
typeset -r mac_mem_gb=$(( $(sysctl -n hw.memsize 2>/dev/null) / 1024 / 1024 / 1024 ))
typeset -r mac_os_name=$(sw_vers -productName 2>/dev/null)
typeset -r mac_os_ver=$(sw_vers -productVersion 2>/dev/null)
typeset -r mac_os_build=$(sw_vers -buildVersion 2>/dev/null)

typeset val_smart
val_smart=$(diskutil info / 2>/dev/null | awk -F': ' '/SMART Status/ {print $2; exit}' | xargs)
[[ -z "$val_smart" ]] && val_smart="Verified"

typeset mac_trim_status
mac_trim_status=$(system_profiler SPStorageDataType 2>/dev/null | awk -F': ' '/TRIM Support/ {print $2; exit}' | xargs)
[[ -z "$mac_trim_status" ]] && mac_trim_status="Unknown / N/A"

typeset mac_controller_name
mac_controller_name=$(system_profiler SPNVMEDataType 2>/dev/null | awk -F': ' '/Device Name|Model/ {print $2; exit}' | xargs)
[[ -z "$mac_controller_name" ]] && mac_controller_name="Apple NVMe Controller Native"

# レポートヘッダー出力関数
print_header() {
  echo "=================================================="
  echo " STORAGE BENCHMARK REPORT (Dual-Pattern Full Stats)"
  echo "--------------------------------------------------"
  echo " [Execution Date] : $(date '+%Y-%m-%d %H:%M:%S')"
  echo " [System Information]"
  echo "   Model    : ${mac_model}"
  echo "   CPU      : ${mac_cpu}"
  echo "   Topology : Performance ${mac_p_cores:-0} Cores / Efficiency ${mac_e_cores:-0} Cores (Total ${mac_total_cores} Threads)"
  echo "   Memory   : ${mac_mem_gb} GB Unified Memory"
  echo "   OS       : ${mac_os_name} ${mac_os_ver} (Build ${mac_os_build})"
  echo "--------------------------------------------------"
  echo " [Hardware Specs]"
  echo "   S.M.A.R.T. Health: ${val_smart}"
  echo "   TRIM Support     : ${mac_trim_status}"
  echo "   Controller Type  : ${mac_controller_name}"
  echo "--------------------------------------------------"
  echo " [Execution Specs]"
  echo "   Large File Size  : ${LARGE_FILE_SIZE} MB"
  echo "   Random Block     : ${BLOCK_SIZE_KB} KB"
  echo "   Concurrency      : ${THREADS} Parallel Threads"
  echo "   Sampling Engine  : Iterations=${ITERATIONS}"
  echo "   Mode Switching   : Cache-Test Execution = ${ENABLE_CACHE_TEST}"
  echo "=================================================="
}

# キャッシュ解放関数(物理性能測定用)
clear_cache() {
  sync && sync && purge
  sleep 3
}
# =======================================================================
# ーーー コア計測パイプライン(計測エンジン) ーーー
# =======================================================================
execute_core_loop() {
  local target_path="$1"
  local test_dir="$2"
  local target_label="$3"
  local state_label="$4"       
  local -i trigger_purge="$5"  

  local file_seq_zero="${test_dir}/seq_zero_${state_label}.dat"
  local file_seq_rand="${test_dir}/seq_rand_${state_label}.dat"

  local -F total_rnd_mb
  (( total_rnd_mb = (BLOCK_SIZE_KB * RANDOM_TEST_BLOCKS * THREADS) / 1024.0 ))

  local -F sum_w_zero=0.0 sum_w_rand=0.0 sum_r_zero=0.0 sum_r_rand=0.0 sum_rw=0.0 sum_rr=0.0
  local -F peak_w_zero=0.0 peak_w_rand=0.0 peak_r_zero=0.0 peak_r_rand=0.0 peak_rw=0.0 peak_rr=0.0
  local -F bot_w_zero=999999.0 bot_w_rand=999999.0 bot_r_zero=999999.0 bot_r_rand=999999.0 bot_rw=999999.0 bot_rr=999999.0

  local -i loop
  for (( loop=1; loop<=ITERATIONS; loop++ )); do
printf "\r\033[K  ⏱️  プロファイリング中... %s (%d/%d)\033[K" "$state_label" "$loop" "$ITERATIONS" >/dev/tty
    
    # 1-A. Sequential Write (Zero埋め)
    (( trigger_purge == 1 )) && clear_cache
    local t_start=$(perl -MTime::HiRes=time -e 'print time')
    dd if=/dev/zero of="$file_seq_zero" bs=1M count=$LARGE_FILE_SIZE conv=sync status=none 2>/dev/null && sync
    local t_end=$(perl -MTime::HiRes=time -e 'print time')
    local -F cur_w_zero=$(( (LARGE_FILE_SIZE * 1.0) / (t_end - t_start) ))
    sum_w_zero+=$cur_w_zero
    (( cur_w_zero > peak_w_zero )) && peak_w_zero=$cur_w_zero
    (( cur_w_zero < bot_w_zero )) && bot_w_zero=$cur_w_zero

    # 1-B. Sequential Write (非圧縮リアルランダム)
    (( trigger_purge == 1 )) && clear_cache
    t_start=$(perl -MTime::HiRes=time -e 'print time')
    perl -e '
      open(my $fh, ">", "'"${file_seq_rand}"'") or die $!;
      my $buf = pack("C*", map { int(rand(256)) } 1..1048576);
      for (1..'"${LARGE_FILE_SIZE}"') { syswrite($fh, $buf); }
      close($fh);
    ' && sync
    t_end=$(perl -MTime::HiRes=time -e 'print time')
    local -F cur_w_rand=$(( (LARGE_FILE_SIZE * 1.0) / (t_end - t_start) ))
    sum_w_rand+=$cur_w_rand
    (( cur_w_rand > peak_w_rand )) && peak_w_rand=$cur_w_rand
    (( cur_w_rand < bot_w_rand )) && bot_w_rand=$cur_w_rand

    # 2-A. Sequential Read (Zeroファイル)
    (( trigger_purge == 1 )) && clear_cache
    t_start=$(perl -MTime::HiRes=time -e 'print time')
    dd if="$file_seq_zero" of=/dev/null bs=1M count=$LARGE_FILE_SIZE status=none 2>/dev/null
    t_end=$(perl -MTime::HiRes=time -e 'print time')
    local -F cur_r_zero=$(( (LARGE_FILE_SIZE * 1.0) / (t_end - t_start) ))
    sum_r_zero+=$cur_r_zero
    (( cur_r_zero > peak_r_zero )) && peak_r_zero=$cur_r_zero
    (( cur_r_zero < bot_r_zero )) && bot_r_zero=$cur_r_zero

    # 2-B. Sequential Read (純ストレージリード)
    (( trigger_purge == 1 )) && clear_cache
    t_start=$(perl -MTime::HiRes=time -e 'print time')
    dd if="$file_seq_rand" of=/dev/null bs=1M count=$LARGE_FILE_SIZE status=none 2>/dev/null
    t_end=$(perl -MTime::HiRes=time -e 'print time')
    local -F cur_r_rand=$(( (LARGE_FILE_SIZE * 1.0) / (t_end - t_start) ))
    sum_r_rand+=$cur_r_rand
    (( cur_r_rand > peak_r_rand )) && peak_r_rand=$cur_r_rand
    (( cur_r_rand < bot_r_rand )) && bot_r_rand=$cur_r_rand

    # 3. Random Write (マルチスレッド分散シーク書き込み)
    (( trigger_purge == 1 )) && clear_cache
    local -i max_blocks=$(( LARGE_FILE_SIZE * (1024 / BLOCK_SIZE_KB) ))
    t_start=$(perl -MTime::HiRes=time -e 'print time')
    local -i t
    for (( t=1; t<=THREADS; t++ )); do
      perl -e '
        open(my $fh, "+<", "'"${file_seq_rand}"'") or die $!;
        my $block = pack("C*", map { int(rand(256)) } 1..'"$((BLOCK_SIZE_KB * 1024))"');
        for (1..'"${RANDOM_TEST_BLOCKS}"') {
            my $seek_pos = int(rand('"${max_blocks}"')) * '"$((BLOCK_SIZE_KB * 1024))"';
            sysseek($fh, $seek_pos, 0);
            syswrite($fh, $block);
        }
        close($fh);
      ' &
    done
    wait && sync
    t_end=$(perl -MTime::HiRes=time -e 'print time')
    local -F cur_rw=$(( total_rnd_mb / (t_end - t_start) ))
    sum_rw+=$cur_rw
    (( cur_rw > peak_rw )) && peak_rw=$cur_rw
    (( cur_rw < bot_rw )) && bot_rw=$cur_rw

    # 4. Random Read (マルチスレッド分散シーク読み込み)
    (( trigger_purge == 1 )) && clear_cache
    t_start=$(perl -MTime::HiRes=time -e 'print time')
    for (( t=1; t<=THREADS; t++ )); do
      perl -e '
        open(my $fh, "<", "'"${file_seq_rand}"'") or die $!;
        my $buf;
        for (1..'"${RANDOM_TEST_BLOCKS}"') {
            my $skip_pos = int(rand('"${max_blocks}"')) * '"$((BLOCK_SIZE_KB * 1024))"';
            sysseek($fh, $skip_pos, 0);
            sysread($fh, $buf, '"$((BLOCK_SIZE_KB * 1024))"');
        }
        close($fh);
      ' &
    done
    wait
    t_end=$(perl -MTime::HiRes=time -e 'print time')
    local -F cur_rr=$(( total_rnd_mb / (t_end - t_start) ))
    sum_rr+=$cur_rr
    (( cur_rr > peak_rr )) && peak_rr=$cur_rr
    (( cur_rr < bot_rr )) && bot_rr=$cur_rr

    rm -f "${test_dir}"/*.dat 2>/dev/null
  done

  printf "\r\033[K" >/dev/tty
  
  local -F avg_w_zero=$(( sum_w_zero / ITERATIONS ))
  local -F avg_w_rand=$(( sum_w_rand / ITERATIONS ))
  local -F avg_r_zero=$(( sum_r_zero / ITERATIONS ))
  local -F avg_r_rand=$(( sum_r_rand / ITERATIONS ))
  local -F avg_rw=$(( sum_rw / ITERATIONS ))
  local -F avg_rr=$(( sum_rr / ITERATIONS ))

  local -i iops_avg_w=$(( (avg_rw * 1024) / BLOCK_SIZE_KB ))
  local -i iops_avg_r=$(( (avg_rr * 1024) / BLOCK_SIZE_KB ))

  {
    echo "  📊 [連続I/O(シーケンシャル性能)- ${state_label}]"
    printf "    - インライン圧縮データ (書き込み): 平均 %8.2f MB/s | 最高 %8.2f MB/s | 最低 %8.2f MB/s\n" $avg_w_zero $peak_w_zero $bot_w_zero
    printf "    - 非圧縮リアルランダム (書き込み): 平均 %8.2f MB/s | 最高 %8.2f MB/s | 最低 %8.2f MB/s\n" $avg_w_rand $peak_w_rand $bot_w_rand
    printf "    - インライン圧縮データ (読み込み): 平均 %8.2f MB/s | 最高 %8.2f MB/s | 最低 %8.2f MB/s\n" $avg_r_zero $peak_r_zero $bot_r_zero
    printf "    - 非圧縮リアルランダム (読み込み): 平均 %8.2f MB/s | 最高 %8.2f MB/s | 最低 %8.2f MB/s\n" $avg_r_rand $peak_r_rand $bot_r_rand
    echo "  📊 [ランダムI/O(分散シーク性能)- ${state_label}]"
    printf "    - マルチスレッド並行上書き   (並列%d): 平均 %8.2f MB/s | 最高 %8.2f MB/s | 最低 %8.2f MB/s [ %5d IOPS ]\n" $THREADS $avg_rw $peak_rw $bot_rw $iops_avg_w
    printf "    - マルチスレッド並行読み込み (並列%d): 平均 %8.2f MB/s | 最高 %8.2f MB/s | 最低 %8.2f MB/s [ %5d IOPS ]\n" $THREADS $avg_rr $peak_rr $bot_rr $iops_avg_r
    echo ""
  } | tee -a "$LOG_FILE"

  local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
  printf "%s,%s,%s,\"%s\",\"%s\",%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d,%.2f,%.2f,%.2f,%d\n" \
    "$timestamp" "$mac_model" "$target_label" "$target_path" "$state_label" \
    $avg_w_zero $peak_w_zero $bot_w_zero \
    $avg_w_rand $peak_w_rand $bot_w_rand \
    $avg_r_zero $peak_r_zero $bot_r_zero \
    $avg_r_rand $peak_r_rand $bot_r_rand \
    $avg_rw $peak_rw $bot_rw $iops_avg_w \
    $avg_rr $peak_rr $bot_rr $iops_avg_r >> "$CSV_FILE"
}

# =======================================================================
# ーーー パスターゲットオーケストレーター ーーー
# =======================================================================
measure_target() {
  local target_path="$1"
  local target_label="$2"
  
  {
    echo "\n▶ [Analyzing Enterprise Workload] ${target_label}"
    echo "  (Path: ${target_path})"
  } | tee -a "$LOG_FILE"
  
  local test_dir="${target_path}/.m5_iobench_tmp_$(date +%H%M%S)"
  mkdir -p "$test_dir" 2>/dev/null
  if [ ! -w "$test_dir" ]; then
    echo "  [INFO] 書き込み権限がないため測定をスキップします。" | tee -a "$LOG_FILE"
    return 1
  fi

  # ーー 空き容量ガード(Zshのint関数エラーを完全修正) ーー
  local -i avail_mb=$(( $(df -kP "$target_path" | awk 'NR==2 {print $4}') / 1024 ))
  local -i multiplier=1
  (( ENABLE_CACHE_TEST == 1 )) && multiplier=2
  
  # 浮動小数点を整数変数に代入することで、Zshネイティブに安全な型丸め(キャスト)を実行
  local -F required_mb_float=$(( LARGE_FILE_SIZE * multiplier * SAFETY_MULTIPLIER ))
  local -i required_mb_int=$required_mb_float
  local -i required_mb=$(( required_mb_int + 100 ))

  if (( avail_mb < required_mb )); then
    {
      echo "  ❌ [ERROR] ストレージの空き容量が不足しているため、測定をスキップします。"
      echo "    - 測定対象パス : ${target_path}"
      echo "    - 現在の空き容量: ${avail_mb} MB"
      echo "    - 必要最小容量  : ${required_mb} MB"
    } | tee -a "$LOG_FILE"
    rm -rf "$test_dir" 2>/dev/null
    return 1
  fi

  # パターン1: キャッシュ解放あり (物理性能)
  execute_core_loop "$target_path" "$test_dir" "$target_label" "キャッシュ無効状態(物理層実効レート)" 1

  # パターン2: キャッシュ解放なし (メモリ性能)
  if (( ENABLE_CACHE_TEST == 1 )); then
    echo "" | tee -a "$LOG_FILE"
    execute_core_loop "$target_path" "$test_dir" "$target_label" "キャッシュ有効状態(インメモリレート)" 0
  fi

  rm -rf "$test_dir" 2>/dev/null
}

# =======================================================================
# ーーー エントリポイント (メイン実行ルーチン) ーーー
# =======================================================================
print_header | tee -a "$LOG_FILE"

typeset -i target_idx=1
typeset arg

for arg in "$@"; do
  if [ -d "$arg" ]; then
    local abs_arg="${arg:a}"
    local label="Target-${target_idx}"

    # 精密な前方一致・完全一致の分類条件式
    if [[ "$abs_arg" =~ "RAMDisk" ]]; then
      label="RAMDisk"
    elif [[ "$abs_arg" == "$(cd ~ 2>/dev/null && pwd)" ]]; then
      label="Internal-SSD(Home)"
    elif [[ "$abs_arg" == "/" ]]; then
      label="Internal-Macintosh_HD"
    elif [[ "$abs_arg" == "$(pwd)" ]]; then
      label="Internal-SSD(Current)"
    elif [[ "$abs_arg" =~ "^/Volumes/" ]]; then
      label="External-[${abs_arg#/Volumes/}]"
    fi

    measure_target "$abs_arg" "$label"
    target_idx=$((target_idx + 1))
  else
    echo "\n[WARNING] 無効なディレクトリパスをスキップしました: $arg" | tee -a "$LOG_FILE"
  fi
done

{
  echo "\n=================================================="
  echo " フルスタッツ・性能プロファイリング診断が完了しました。"
  echo " [LOG SAVED] : ${LOG_FILE}"
  echo " [CSV SAVED] : ${CSV_FILE}"
  echo "=================================================="
} | tee -a "$LOG_FILE"

exit 0

# =======================================================================
# ーーー エントリポイント (メイン実行ルーチン) ーーー
# =======================================================================
print_header | tee -a "$LOG_FILE"

typeset -i target_idx=1
typeset arg

for arg in "$@"; do
  if [ -d "$arg" ]; then
    local abs_arg="${arg:a}"
    local label="Target-${target_idx}"

    # 精密な前方一致・完全一致の分類条件式
    if [[ "$abs_arg" =~ "RAMDisk" ]]; then
      label="RAMDisk"
    elif [[ "$abs_arg" == "$(cd ~ 2>/dev/null && pwd)" ]]; then
      label="Internal-SSD(Home)"
    elif [[ "$abs_arg" == "/" ]]; then
      label="Internal-Macintosh_HD"
    elif [[ "$abs_arg" == "$(pwd)" ]]; then
      label="Internal-SSD(Current)"
    elif [[ "$abs_arg" =~ "^/Volumes/" ]]; then
      label="External-[${abs_arg#/Volumes/}]"
    fi

    measure_target "$abs_arg" "$label"
    target_idx=$((target_idx + 1))
  else
    echo "\n[WARNING] 無効なディレクトリパスをスキップしました: $arg" | tee -a "$LOG_FILE"
  fi
done

{
  echo "\n=================================================="
  echo " フルスタッツ・性能プロファイリング診断が完了しました。"
  echo " [LOG SAVED] : ${LOG_FILE}"
  echo " [CSV SAVED] : ${CSV_FILE}"
  echo "=================================================="
} | tee -a "$LOG_FILE"

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