Apple Silicon 上の CPU カウンタ: 記事 + ツール

2026/01/07 20:39

Apple Silicon 上の CPU カウンタ: 記事 + ツール

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

Apple の新しい「Lauka」ツールは、M1–M4 チップ上のパフォーマンスモニタリングユニット(PMU)を調査できるようにし、利用可能なすべてのカウンタとその設定を明らかにします。リバースエンジニアリングされた

kperf
フレームワーク(
kpep_event
kpep_db
)をベースに構築され、Lauka はイベント名・マスク・別名を一覧表示し、それらが固定カウンタかどうかを示します。固定カウンタは「Cycles」と「Instructions」の 2 つです。

コマンドラインインターフェイスでは、最大 10 個のカウンタ(カウンタマスクフィールドは 10 ビット幅)を選択でき、サンプリング前にウォームアップステップが含まれます。Lauka は競合を検出します:一部のカウンタグループは同時に実行できず、追加順序が重要です。たとえば、Group M カウンタは同じマスク(

0b0010000000
)を共有し、ペアワイズで互いに非互換です;Group G カウンタは 4 個以上選択すると相互排他になります。カウンタの順序を入れ替える(例:
ST_UNIT_UOP
INST_LDST
をスワップ)ことでこれらの競合が解消され、将来のツールで順序処理を自動化できる可能性を示唆しています。

また、ツールはスロット割り当てルールにも従います:広いマスクは最低ビットから開始して複数のスロットを占有し、最初に追加すると「特殊」カウンタをブロックすることがあります。Lauka の軽量設計は、既存の macOS インストゥルメントと比べて測定時間を約 78 % 削減します(ベンチマーク参照)。

現在、Lauka は Apple Silicon のみをサポートしていますが、将来的には Linux への拡張や根本原因分析の深化が期待されます。PMU データを Apple Silicon 上で利用可能にすることで、本プロジェクトはクロスプラットフォームのパフォーマンスツール開発を促進し、これらのチップ向けコード最適化手法に影響を与える可能性があります。

本文

以前、Apple Silicon での Zig におけるプロファイリングについて書いた際に、PMU カウンタを利用した手法に触れました。
今回はさらに踏み込み、M1・M2 以降の Apple Silicon プロセッサが持つすべてのカウンタを取得できる独自ツールを作成することにしました。


PMU カウンタの簡単な説明

PMU(Performance Monitoring Unit)カウンタは、CPU 内で発生するマイクロアーキテクチャイベントを追跡するハードウェアイベントカウンタです。
例としては、実行された命令数・退避した操作数・分岐回数・キャッシュミス数などがあります。

CPU は通常、固定カウンタとプログラマブルカウンタの両方を公開します。

  • 固定カウンタ:あらかじめ定義されたイベント(多くはサイクル数や命令数)を追跡します。
  • プログラマブルカウンタ:任意のイベントセットに設定できます。

PMU カウンタを利用することで、開発者はアプリケーションの性能特性(キャッシュミス数・分岐誤判定数・命令混合比など)をより詳細に把握できるようになります。


動機

これらカウンタを取得する手段として、Andrew Kelly が作成した poop ツールと、tensorush によって Apple Silicon で CPU カウンタを取得できる PR を追加したものがあります。
しかし Andrew によりこの PR は丁寧に却下されました。実装が増えるほどサポートが難しくなるためです。

そこで私はフォークを作成し、必要なときに複数の事前定義済み PMU カウンタを取得できるソリューションとして提供します。
しかしさらに進めて Apple Silicon Mac で利用可能なすべてのカウンタを取得できる別ツールを実装することにしました。その過程で Apple の非公開 kperf API を理解し、研究プロジェクトへと発展させました。
この記事はその研究の旅路です。


実験環境

M2 Pro 搭載 MacBook(macOS 15.6.1)を使用しました。

Apple Instruments と奇妙な制限

  • Instruments → 「CPU Counters」テンプレートを開き、カウンタを追加/削除しようと試みました。
  • 10 個以上のカウンタは取得できず(場合によっては 8 個程度)、
    <SOME_COUNTER>
    が既に追加されたイベントと衝突するといったエラーが頻繁に発生しました。
  • 最大で取得可能なのは 10 個です。

結論:取得できるカウンタ数には上限があり、ある種のカウンタは互いに不整合があります。

10 と 8 の違いは、2 つのカウンタが固定(CPU が常に監視)であることから説明できます。 Instruments 上では特別なエイリアスを持ちます。
以下は利用可能な 60 個のうち 5 個だけを抜粋した表です。

カウンタエイリアスマスク
INST_ALL0b0010000000
INST_BARRIER0b0011100000
Cycles (FIXED_CYCLES)Cycles0b0000000001
L1D_CACHE_MISS_LD0b0011100000
Instructions (FIXED_INSTRUCTIONS)Instructions0b0000000010

Cycles と Instructions は固定カウンタで、

FIXED_
プレフィックスとエイリアスを持ちます。 Instruments 上では 2 つだけです → Apple Silicon には 固定カウンタが 2 個 存在すると仮定できます。


kperf の逆解析と最初の実験

次に参照したコードは、macOS の非公開フレームワーク kperf を利用して CPU カウンタを取得する逆解析済みコードです(ibireme による)。
公式ドキュメントは存在しないため、逆解析コードを読むか自ら逆解析するしかありません。ツールは

sudo
権限が必要です。

Zig 版の kperf コードを利用して最初の実験として、すべてのカウンタペアを列挙し、監視リストに追加して単純関数でカウンタ値を取得しました。

結果:グループ M に属する 6 個のカウンタがペアで不整合(同時に追加できない)ことが判明。

グループ M (6 カウンタ)
INST_ALL
INST_INT_ALU
INST_INT_ST
INST_LDST
INST_SIMD_ALU
RETIRE_UOP

8 個の互いに不整合でないように見えるカウンタを追加しようとしたが、エラー「cannot be added together」が発生。ペア以外にも何か別の制約があることが分かりました。

さらに実験を続け、3, 4 個…最大 8 個まで全組み合わせを生成して調べました。


組合せ解析で分かったこと

集合サイズ発見結果
ペアグループ M の 6 カウンタのみ
トリプレット新たな不整合はなし(グループ M を除く)
クワッドレプトグループ G (18 カウンタ) が追加で発見。グループ M と組み合わせると 3 個までしか選べない。
5–6 個新たな不整合はなし(既知のものを除く)
7 個多数の新規不整合 が出現

グループ G (18 カウンタ) は以下です。

BRANCH_CALL_INDIR_MISPRED_NONSPEC
BRANCH_COND_MISPRED_NONSPEC
BRANCH_INDIR_MISPRED_NONSPEC
BRANCH_MISPRED_NONSPEC
BRANCH_RET_INDIR_MISPRED_NONSPEC
INST_BARRIER
INST_BRANCH
INST_BRANCH_CALL
INST_BRANCH_COND
INST_BRANCH_INDIR
INST_BRANCH_RET
INST_BRANCH_TAKEN
INST_INT_LD
INST_SIMD_LD
INST_SIMD_ST
L1D_CACHE_MISS_LD_NONSPEC
L1D_CACHE_MISS_ST_NONSPEC
L1D_TLB_MISS_NONSPEC

最終的に 18 673 166 個の 7 要素集合で不整合が発生。
C(55, 7) = 202 927 725 通りの組み合わせを調べ、Apple のガイドと実際のカウンタ数とのずれから 60 個中 55 個だけを検証しました。


順序が重要

最後に気づいたことは 追加順序が結果に影響する という点です。
以下の順序で追加するとエラー(赤枠)になります。

L1D_TLB_ACCESS
L1D_TLB_MISS
L1D_CACHE_MISS_ST
L1D_CACHE_MISS_LD
LD_UNIT_UOP
ST_UNIT_UOP
INST_LDST   ← error (red circle)

逆に最後の 2 個を入れ替えるとエラーは解消します。

L1D_TLB_ACCESS
L1D_TLB_MISS
L1D_CACHE_MISS_ST
L1D_CACHE_MISS_LD
LD_UNIT_UOP
INST_LDST
ST_UNIT_UOP   ← works fine

したがって、Apple Instruments で不整合エラーが出た場合はカウンタの順序を変えてみると解決する可能性があります。


kpep_event と kpep_db の重要性

逆解析コードには次の構造体が登場します。

typedef struct kpep_event {
    const char *name;          // イベント名(例:"INST_RETIRED.ANY")
    const char *description;   // 説明
    const char *errata;        // エラー情報(NULL が多い)
    const char *alias;         // 別名(例 "Instructions", "Cycles")
    const char *fallback;      // 固定カウンタ用フォールバックイベント名
    u32 mask;
    u8 number;
    u8 umask;
    u8 reserved;
    u8 is_fixed;
} kpep_event;

typedef struct kpep_db {
    const char *name;              // データベース名(例 "haswell")
    const char *cpu_id;            // plist 名(例 "cpu_7_8_10b282dc")
    const char *marketing_name;    // マーケティング名
    void *plist_data;
    void *event_map;               // イベントマップ(CFDict<CFSTR(event_name), kpep_event *>)
    kpep_event *event_arr;         // すべてのイベント構造体配列
    kpep_event **fixed_event_arr;  // 固定カウンタ配列
    void *alias_map;
    usize reserved_1, reserved_2, reserved_3;
    usize event_count;
    usize alias_count;
    usize fixed_counter_count;
    usize config_counter_count;
    usize power_counter_count;
    u32 architecture;
    u32 fixed_counter_bits;
    u32 config_counter_bits;
    u32 power_counter_bits;
} kpep_db;

kpep_event
mask
フィールドが鍵です。
例えば、以下のように表示します。

#NameAliasMask
1FIXED_CYCLESCycles0b0000000001
2FIXED_INSTRUCTIONSInstructions0b0000000010
0b1111111100 / 0b0010000000 など

アルゴリズムの解説

  • 最大カウンタ数 = 10(マスク幅 10 ビット)。
  • 固定カウンタ(Cycles・Instructions)はユニークで、他のカウンタと互換性があります。
  • グループ M の 6 カウンタはペアで不整合。全て同じマスク (0b0010000000) を持ち、同一スロットを競合します。
  • グループ G の 19 個のカウンタも同様に特定マスクを共有し、グループ M と重複します。3 個までなら各スロットが空きますが、4 番目以降はスロット不足で失敗します。

順序が重要 な理由:カウンタ追加時に、マスクの低位ビットから最初に利用可能なスロットを割り当てるためです。
広いマスクを持つイベントを先に追加すると、そのスロットを占有し、後で「特殊」なマスクを持つカウンタが必要になったときに衝突します。従って、マスクの昇順で追加する と予測可能です。


Instruments でエラーになる例

ステップカウンタマスク結果(✓=free, 🔴=conflict)
0初期状態🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢
1L1D_TLB_ACCESS1111111100🟢🟢🟢🟢🟢🟢🟢🟡🟢🟢
2L1D_TLB_MISS1111111100🟢🟢🟢🟢🟢🟢🡇🟡🟢🟢
6ST_UNIT_UOP1111111100🟢🟢🟡🟡🟡🟡🟡🟡🟢🟢
7INST_LDST0010000000🔴 (conflict)

最後の二つを入れ替えると:

5 LD_UNIT_UOP
6 INST_LDST
7 ST_UNIT_UOP

すべてが空きで衝突は発生しません。


まとめ

私は “Lauka” というツールを作成しました。

  • 元の poop リポジトリと scoop ライブラリをベースに構築。
  • CLI を書き直し、イベント選択・全カウンタ表示・ウォームアップ機能など拡張。
  • Linux と Intel のサポートは除外 – Apple Silicon Mac でのみ動作します。

GitHub の README に完全な使用方法を記載していますのでご覧ください。

サンプル実行

$ lauka -- "./build-old" './build-new -O2'

Benchmark 1 (9 runs): ./build-old
measurement                 mean ± σ          min … max            outliers
wall_time                   591ms ± 7.6ms     583ms … 605ms        0 (0%)
peak_rss                    137MB ± 0.3MB     136.6MB … 137.4MB    0 (0%)
core_active_cycle           2.51G ± 22.1M     2.48G … 2.54G        0 (0%)
inst_all                    3.62G ± 23.9M     3.53G … 3.69G        0 (0%)
l1d_cache_miss_ld_nonspec   3.58M ± 31.7K     3.54M … 3.63M        0 (0%)
branch_mispred_nonspec      21.4M ± 58.2K     21.3M … 21.5M        0 (0%)

Benchmark 2 (9 runs): ./build-new -O2
measurement                 mean ± σ          min … max            outliers    delta
wall_time                   130ms ± 8.3ms     125ms … 141ms        0 (0%)      ⚡ −78.0% ± 0.5%
peak_rss                    91.9MB ± 0.09MB   91.8MB … 92.1MB      0 (0%)      −32.9% ± 0.1%
core_active_cycle           507M ± 2.35M      503M … 511M          0 (0%)      −79.8% ± 0.1%
inst_all                    796M ± 10.7M      781M … 809M          0 (0%)      −78.0% ± 0.1%
l1d_cache_miss_ld_nonspec   352K ± 7.7K       318K … 355K          0 (0%)      −90.2% ± 0.1%
branch_mispred_nonspec      4.52M ± 11.5K     4.51M … 4.57M        2 (5%)      −78.9% ± 0.0%

Summary

最後に

  • Apple Silicon のみを対象に検索 していたので、Linux を先に調べていればもっと情報が得られたかもしれません。
  • 逆解析コードはざっと見ただけで十分だったのですが、早めに深掘りすれば時間を節約できました。
  • 最終的な不整合数に時間を費やし過ぎて、本質的な原因解明が遅れた点は残念です。

それでもこの経験を通じて得られた知見は貴重で、共有できて嬉しいです。ご読了ありがとうございました!

同じ日のほかのニュース

一覧に戻る →

2026/01/11 10:50

**Show HN:Ferrite – Rustで作られたマークダウンエディタ、ネイティブMermaid図描画機能付き**

## Japanese Translation: Ferrite は、egui で構築された軽量でネイティブな Rust テキストエディタで、Markdown、JSON、YAML、および TOML ファイルをサポートします。主な編集機能には、WYSIWYG Markdown 編集、ライブプレビュー、クリックで編集できる書式設定ボタン、40 以上の言語に対応した構文ハイライト、正規表現検索&置換、タブごとの Undo/Redo、およびインライン編集が可能な階層データ用トリービューがあります。 表示モードは Raw エディタ、レンダリングビュー、分割ビュー(可変サイズの区切り線付き)、Zen モード、Raw とレンダリングビュー間の双方向同期スクロールから構成されます。 MermaidJS ダイアグラム描画は完全に統合されており、11 種類のダイアグラムをサポートします;バージョン 0.2.1 では高度なシーケンス制御フローブロックとネストされた状態が追加されました。 ワークスペース機能:フォルダーをファイルツリーで開く、クイックスイッチャー(`Ctrl+P`)、検索‑イン‑ファイル(`Ctrl+Shift+F`)、Git 統合(ステータスアイコン、ステージング、コミット、プッシュ/プル、競合解決)およびセッション永続化により、タブ、カーソル位置、およびスクロールオフセットが再起動時に復元されます。 追加の UI オプションには、実行時切替可能なライト/ダークテーマ、ドキュメントアウトラインパネル、テーマ付き HTML へのエクスポートまたは HTML としてコピー、書式設定ツールバー、JSON/YAML をシェルコマンドでパイプするライブパイプライン、およびカスタム境界なしウィンドウモードがあります。 インストールはプリビルトバイナリ(Windows zip、macOS tar.gz、Linux .deb または tar.gz)またはソースビルド(`cargo build --release`)で利用可能です。Rust 1.70+ とプラットフォーム固有の依存関係が必要です。主なショートカット:ファイル操作は `Ctrl+N/O/S/W`、タブは `Ctrl+Tab/Shift+Tab`、クイックスイッチャーは `Ctrl+P`、フルスクリーンは `F11`、設定は `Ctrl+,` などです。 Ferrite は MIT ライセンスの下でオープンソースであり、Rust 1.70+、egui 0.28、comrak 0.22(Markdown パーシング)、syntect 5.1(構文ハイライト)、git2 0.19(Git 統合)に依存しています。

2026/01/11 3:58

**GhostTyの最大メモリリークを発見し修正する**

## Japanese Translation: Ghostty の長時間にわたるセッションは、`mmap`(スクロールバックバッファに使用される)で割り当てられたページが解放されないため、最大 37 GB の RAM をリークしていました。アプリはターミナルコンテンツを **PageList** に保存します。これは「標準」(プールから取得したもの)または「非標準」(`mmap`)のメモリページで構成される双方向リンクリストです。スクロールバックの削減時に、Ghostty は誤って最も古いページを新しいページとして再利用します:そのメタデータだけを「標準サイズ」にリサイズし、大きな `mmap` 割り当てはそのまま残します。この再利用されたページが後で解放されると、Ghostty はそれを標準とみなし、`munmap` を呼び出す代わりにプールへ返却してしまい、メモリブロックがリークしたままとなります。 このバグは Ghostty 1.0 から存在しましたが、大量のスクロールバックバッファ(例:多くの絵文字とハイパーリンクを含む Claude Code など)を生成する CLI アプリでのみ顕在化し、非標準ページ割り当てをトリガーします。既存のリーク検出器は特定の実行時条件下で発生するため、検知できませんでした。 新しいテストが問題を再現しリークを確認しました。統合された修正では、削減中に **非標準ページを破棄**(`self.destroyNode(first)`)し、プールから新しい標準サイズのページで置き換えるようになっています。この修正は Ghostty 1.3(3 月)に組み込まれます。既に Nightly リリースにはパッチが含まれています。 さらに、macOS のメモリタグ付け(`mach.taggedPageAllocator(.application_specific_1)`)を追加し、PageList 割り当てにタグを付与して修正の検出と確認を簡素化しました。この更新により、長時間ターミナルセッションを実行するユーザー—特に重い CLI ワークロードを扱う開発者は――メモリ使用量が急増する問題が解消され、個人およびプロダクションでアプリに依存している組織の両方に対し、より安定かつ信頼性の高い Ghostty エクスペリエンスを提供します。

2026/01/11 1:56

**HNの投稿:** 「Claude Code を使って100冊の本との関連性を発見しました」

## Japanese Translation: **概要** 本文は、スタートアップのピボットが巧妙な洞察よりもむしろ絶望感から動かされることが多いと主張しています。後知恵バイアスがこれらの反応的シフトを事後的に戦略的計画として見せかけ、意図的な天才像を与える仕方を説明しています。代表例として、Odeo が新しいベンチャーへと変貌するケースが挙げられ、ピボット手法の実践的なテキストブック例として機能します。著者は将来のピボットも短期的圧力によって促される反応的なものに留まる可能性が高いと予測し、このパターンを認識することで、創業者・投資家・チームがスタートアップエコシステム内で戦略やリスクについて考える方法を再構築し、企業が方向転換する理由をより現実的に評価できるよう促すと述べています。

Apple Silicon 上の CPU カウンタ: 記事 + ツール | そっか~ニュース