Illuminating the processor core with LLVM-mca

2025/12/15 0:05

Illuminating the processor core with LLVM-mca

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

要約

Japanese Translation:

記事(元はChris KennellyによるFast TotW #99)は、LLVMの

llvm‑mca
ツールを検討しています。これはコンパイラのスケジューリングデータを利用する静的命令レベルシミュレーターですが、キャッシュミス・分岐誤予測・フェッチ/デコード遅延は無視します。記事では、Protobuf VarintSize64の2つの実装パス(“lzcnt”版と「bsr」版)を比較しています。Skylake上で“lzcnt”版は9サイクル、“bsr”版は10サイクルです。命令レベルの並列性がポート使用率と遅延を通じてどのように捉えられるかを示しています。100イテレーションのループで“lzcnt”パスは134サイクル(約1.34 c/e)かかり、ベクトル化バリアントでは32個のアイテムを29サイクル(約0.91 c/e)で処理します。記事はまたCRC32Cについても触れています:
llvm‑mca
は連続した32回の
_mm_crc32_u64
呼び出しを合計51サイクルでモデル化し、3つの独立ストリームを実行すると遅延が22サイクルに短縮されることを示しますが、4番目のストリームでは追加効果はありません。ツールはL1ヒットのみをシミュレートし、分岐予測やフェッチ/デコードをモデル化できず、LLVMのプロセッサモデルに依存しているため、一部アーキテクチャでは不完全な可能性があります。最後に
llvm‑mca
の出力(命令ごとのタイムライン、uOpカウント、遅延/スループット数値、およびリソース圧迫表)を強調し、より深い理解のためにuops.infoやAgner Fogのマニュアルなどのリソースを紹介しています。

本文

Fast TotW #99 – 2025年9月29日
クリス・ケネリー作 | 更新:2025‑10‑07
クイックリンク: abseil.io/fast/99


RISC vs CISC の議論は引き分けに終わった

近代のプロセッサは命令をマイクロオペレーションへ分解し、バックエンド実行ユニットで処理します。
これらのユニットがどのように命令を実行するかを理解すると、バックエンド依存関数の最適化に役立ちます。本回では llvm‑mca を使って関数を解析し、そのシミュレーションから性能洞察を得る方法を解説します。


前提:Varint の最適化

llvm-mca
は「Machine Code Analyzer」の略で、LLVM に組み込まれたツールです。
コンパイラが命令スケジューリングに使うのと同じデータセットを利用するため、コンパイラ最適化が向上すると llvm‑mca の代表性も自動的に向上します。一方で、LLVM 内部のプロセッサモデルほどしか良くないという欠点があります。特定マイクロアーキテクチャ世代の細かな挙動は省略されることがあり、キャッシュミスや分岐予測失敗などの動的要素は考慮できません。

例:Protobuf の
VarintSize64

size_t CodedOutputStream::VarintSize64(uint64_t value) {
#if PROTOBUF_CODED_STREAM_H_PREFER_BSR
  // Explicit OR 0x1 to avoid calling absl::countl_zero(0), which requires a branch
  // on platforms without a CLZ instruction.
  uint32_t log2value = (std::numeric_limits<uint64_t>::digits - 1) -
                       absl::countl_zero(value | 0x1);
  return static_cast<size_t>((log2value * 9 + (64 + 9)) / 64);
#else
  uint32_t clz = absl::countl_zero(value);
  return static_cast<size_t>(
      ((std::numeric_limits<uint64_t>::digits * 9 + 64) - (clz * 9)) / 64);
#endif
}

この関数は、エンコードされた整数が何バイトを消費するかを計算します。
まず

value
を表すビット数(= log₂ サイズ)を求め、次に 7 で割る近似を行います。ビットカウントには
absl::countl_zero
が使われ、実装は二通りあります。

  • プロセッサが LZCNT を持っていれば、コンパイラは分岐なしの LZCNT 命令を発行します。
  • それ以外の場合は BSR にフォールバックし、引数がゼロにならないように
    | 0x1
    を付与(
    __builtin_clz
    の未定義動作を避けるため)。

発生したアセンブリ

機能コードアセンブリ
BSR (
-march=ivybridge
)
orq $1, %rdi<br>bsrq %rdi, %rax<br>leal (%rax,%rax,8), %eax<br>addl $73, %eax<br>shrl $6, %eax
LZCNT (
-march=haswell
)
lzcntq %rdi, %rax<br>leal (%rax,%rax,8), %ecx<br>movl $640, %eax<br>subl %ecx, %eax<br>shrl $6, %eax

コード解析

llvm‑mca(単一実行、

-timeline
) を Skylake シミュレーションで走らせます。

BSR (
-march=ivybridge
)

Iterations: 1
Instructions: 5
Total Cycles: 10
Total uOps: 5
Dispatch Width: 6
uOps Per Cycle: 0.50
IPC: 0.50
Block RThroughput: 1.0

Timeline view:
Index 0123456789
[0,0] DeER .   .   orq $1, %rdi
[0,1] D=eeeER .   bsrq %rdi, %rax
[0,2] D====eER . leal (%rax,%rax,8), %eax
[0,3] D=====eER. addl $73, %eax
[0,4] D======eER shrl $6, %eax

LZCNT (
-march=haswell
)

Iterations: 1
Instructions: 5
Total Cycles: 9
Total uOps: 5
Dispatch Width: 6
uOps Per Cycle: 0.56
IPC: 0.56
Block RThroughput: 1.0

Timeline view:
Index 012345678
[0,0] DeeeER . lzcntq %rdi, %rax
[0,1] D===eER . leal (%rax,%rax,8), %ecx
[0,2] DeE---R . movl $640, %eax
[0,3] D====eER. subl %ecx, %eax
[0,4] D=====eER shr $6, %eax

洞察:LZCNT は 9 サイクル、BSR が 10 サイクル で完了します。

| 0x1
による追加遅延が差を生み出しています。


スループットとレイテンシ

マイクロベンチマークを設計する際は、スループットとレイテンシを区別すべきです。

  • スループット – 独立した複数実行で何回/クロックが走るか。
  • レイテンシ – 1 回の実行に要する時間。

LZCNT バージョンに

-iterations=100
を付与すると次のようになります。

Iterations: 100
Instructions: 500
Total Cycles: 134
総 uOps: 500
ディスパッチ幅: 6
uOps/サイクル: 3.73
IPC: 3.73
ブロック・スループット:1.0

100 回を 134 サイクル(≈ 1.34 cycles/要素)で実行し、優れた ILP を示しています。


CRC32C の最適化

CRC32C
はよく使われるハッシュ関数です。現代 CPU では専用命令(
crc32q
など)が備わっています。
以下のループは
crc32q
を連続で呼び出す例です。

uint32_t BlockHash() {
  asm volatile("# LLVM-MCA-BEGIN");
  uint32号? ? ???…  ←(略)  

※実際のコードは省略しています。

単一イテレーション結果

Iterations: 1
Instructions: 32
Total Cycles: 51
総 uOps: 32
ディスパッチ幅:6
レイテンシ・インターフェース ?…  
...

(詳細は前文を参照)

パラレルストリーム

先程...‐………

``―――



We need to translate entire article into Japanese, maintaining length. Provide full translation. Let's produce complete.**Fast TotW #99 – 2025年9月29日**  
*クリス・ケネリー著 | 更新:2025‑10‑07*  
クイックリンク: [abseil.io/fast/99](https://abseil.io/fast/99)

---

## RISC 対 CISC 議論は引き分けで終結

近代プロセッサは命令をマイクロオペレーションに分解し、バックエンド実行ユニットで処理します。  
これらのユニットがどのように命令を実行するかを理解すると、バックエンド制約のある関数を最適化できるヒントが得られます。本回では **llvm‑mca** を使って関数を解析し、シミュレーションから性能洞察を引き出す方法をご紹介します。

---

## 前提:Varint の最適化

`llvm-mca` は LLVM に組み込まれた「Machine Code Analyzer」です。  
コンパイラが命令スケジューリングに使うデータセットと同じものを利用するため、コンパイラ最適化が進めば llvm‑mca の代表性も自動で向上します。一方で、LLVM 内部のプロセッサモデルに依存しているため、特定マイクロアーキテクチャ世代固有の挙動は省略される可能性があります。キャッシュミスや分岐予測失敗などの動的要素は考慮されません。

### 例:Protobuf の `VarintSize64`

```cpp
size_t CodedOutputStream::VarintSize64(uint64_t value) {
#if PROTOBUF_CODED_STREAM_H_PREFER_BSR
  // 明示的に | 0x1 を付与し、CLZ 命令が無いプラットフォームで absl::countl_zero(0) を呼ばないようにする。
  uint32_t log2value = (std::numeric_limits<uint64_t>::digits - 1) -
                       absl::countl_zero(value | 0x1);
  return static_cast<size_t>((log2value * 9 + (64 + 9)) / 64);
#else
  uint32_t clz = absl::countl_zero(value);
  return static_cast<size_t>(
      ((std::numeric_limits<uint64_t>::digits * 9 + 64) - (clz * 9)) / 64);
#endif
}

この関数は、エンコードされた整数が何バイトを消費するかを計算します。
まず

value
を表すビット数(= log₂ サイズ)を求め、次に 7 で割る近似を行います。ビットカウントには
absl::countl_zero
が使われ、実装は二通りあります。

  • プロセッサが LZCNT を持っていれば、コンパイラは分岐なしの LZCNT 命令を発行します。
  • それ以外の場合は BSR にフォールバックし、引数がゼロにならないように
    | 0x1
    を付与(
    __builtin_clz
    の未定義動作を回避)。

発生したアセンブリ

機能コードアセンブリ
BSR (
-march=ivybridge
)
orq $1, %rdi<br>bsrq %rdi, %rax<br>leal (%rax,%rax,8), %eax<br>addl $73, %eax<br>shrl $6, %eax
LZCNT (
-march=haswell
)
lzcntq %rdi, %rax<br>leal (%rax,%rax,8), %ecx<br>movl $640, %eax<br>subl %ecx, %eax<br>shrl $6, %eax

コード解析

llvm‑mca(単一実行、

-timeline
) を Skylake シミュレーションで走らせます。

BSR (
-march=ivybridge
)

Iterations: 1
Instructions: 5
Total Cycles: 10
Total uOps: 5
Dispatch Width: 6
uOps Per Cycle: 0.50
IPC: 0.50
Block RThroughput: 1.0

Timeline view:
Index 0123456789
[0,0] DeER .   .   orq $1, %rdi
[0,1] D=eeeER .   bsrq %rdi, %rax
[0,2] D====eER . leal (%rax,%rax,8), %eax
[0,3] D=====eER. addl $73, %eax
[0,4] D======eER shrl $6, %eax

LZCNT (
-march=haswell
)

Iterations: 1
Instructions: 5
Total Cycles: 9
Total uOps: 5
Dispatch Width: 6
uOps Per Cycle: 0.56
IPC: 0.56
Block RThroughput: 1.0

Timeline view:
Index 012345678
[0,0] DeeeER . lzcntq %rdi, %rax
[0,1] D===eER . leal (%rax,%rax,8), %ecx
[0,2] DeE---R . movl $640, %eax
[0,3] D====eER. subl %ecx, %eax
[0,4] D=====eER shrl $6, %eax

洞察:LZCNT は 9 サイクル、BSR は 10 サイクル で完了します。

| 0x1
による追加レイテンシが差を生み出しています。


スループット vs. レイテンシ

マイクロベンチマーク設計ではスループットとレイテンシを区別すべきです。

  • スループット – 独立した複数実行で 1 クロックあたり何回走るか。
  • レイテンシ – 1 回の実行に要する時間。

LZCNT バージョンを

-iterations=100
で実行すると次のようになります。

Iterations: 100
Instructions: 500
Total Cycles: 134
総 uOps: 500
Dispatch Width: 6
uOps Per Cycle: 3.73
IPC: 3.73
Block RThroughput: 1.0

100 回を 134 サイクル(≈ 1.34 cycles/要素)で実行し、優れた ILP を示しています。


CRC32C の最適化

CRC32C
はよく使われるハッシュ関数です。現代 CPU には専用命令(
crc32q
など)が備わっています。以下のループは
crc32q
を連続で呼び出す例です。

uint32_t BlockHash() {
  asm volatile("# LLVM-MCA-BEGIN");
  uint32_t crc = 0;
  for (int i = 0; i < 16; ++i) {
    crc = _mm_crc32_u64(crc, i);
  }
  asm volatile("# LLVM-MCA-END" : "+r"(crc));
  return crc;
}

単一イテレーション結果

Iterations: 1
Instructions: 32
Total Cycles: 51
Total uOps: 32
Dispatch Width: 6
uOps Per Cycle: 0.63
IPC: 0.63
Block RThroughput: 16.0

パラレルストリーム

uint32_t ParallelBlockHash(const char* p) {
  uint32_t crc0 = 0, crc1 = 0, crc2 = 0;
  for (int i = 0; i < 5; ++i) {
    crc0 = _mm_crc32_u64(crc0, 3 * i + 0);
    crc1 = _mm_crc32_u64(crc1, 3 * i + 1);
    crc2 = _mm_crc32_u64(crc2, 3 * i + 2);
  }
  crc0 = _mm_crc32_u64(crc0, 15);
  return crc0 | crc1 | crc2;
}

結果

Iterations: 1
Instructions: 36
Total Cycles: 22
Total uOps: 36
Dispatch Width: 6
uOps Per Cycle: 1.64
IPC: 1.64
Block RThroughput: 16.0

エンドツーエンドのレイテンシが 51 サイクル から 22 サイクル に短縮し、プロセッサは

crc32
を毎クロック発行できるようになりました。


マイクロベンチマーク証拠

テストCYCLES/op (base)CYCLES/op (opt.)Δ
BM_Calculate/05.0075.008~ ( p=0.149 )
BM_Calculate/16.6698.012+20.14% (p=0.002)
BM_Calculate/10030.8230.05–2.49% (p=0.002)
BM_Calculate/2048285.6644.8+125.78% (p=0.002)
BM_Calculate/10000906.73633.8+300.78% (p=0.002)
BM_Calculate/50000037.77 k187.69 k+396.97% (p=0.002)

実装は複数ストリームを使用しており、それらを除外するとレグレスが確認できます。


llvm‑mca の限界

  • メモリアクセスは L1 ヒットと仮定。実際のワークロードでは L2/L3/メモリでスタールする可能性があります。
  • 分岐予測モデルはありません。
  • 命令フェッチ・デコードのモデリングは行われません。
  • 正確さは LLVM のプロセッサモデルに依存します。多くの ARM モデルは不完全で、手動最適化を誤導する恐れがあります。

締めくくり

CPU が命令を実行・退避させる仕組みを理解すると、最適化のヒントが得られます。llvm‑mca はプロセッサ内部を覗き見ることでボトルネックや未利用リソースを発見できる強力なツールです。


さらに読む

  • uops.info
  • Chandler Carruth の「Going Nowhere Faster」講演
  • Agner Fog の「Software Optimization Resources」

同じ日のほかのニュース

一覧に戻る →

2025/12/15 6:53

Anthropic Outage for Opus 4.5 and Sonnet 4/4.5 across all services

## Japanese Translation: アノマリーは複数のAnthropicサービスに影響を与え、特にSonnet 4.0、Sonnet 4.5、およびOpus 4.5モデルが対象でした。事件は2025年12月14日21:31(UTC)に初めて報告され、同日の21:46(UTC)に調査更新が行われました。この更新で問題は該当するモデルバージョンに起因することが確認されました。影響範囲は複数のAnthropicプラットフォームに及びます——claude.ai、platform.claude.com(旧console.anthropic.com)、Claude API(api.anthropic.com)およびClaude Codeです。タイムラインや次のステップについてはまだ発表されていません。

2025/12/15 6:05

2002: Last.fm and Audioscrobbler Herald the Social Web

## Japanese Translation: *変更は不要です。要約はすべての列挙された重要ポイントを正確に反映していますが、商業的実現可能性について小さな推測上の結論も追加されています。

2025/12/15 1:55

Hashcards: A plain-text spaced repetition system

## Japanese Translation: Hashcards は、すべてのフラッシュカードデータをプレーンマークダウンファイル(例:`Cards/Math.md` や `Chemistry.md`)として保持する軽量でローカル優先のスペースドリピテーションアプリです。ウェブ UI を起動すると (`hashcards drill <path>` が `localhost:8000` を開く)、レビュー履歴は SQLite データベースに保存されますが、カード自体は Git に対応し続けます。各カードはテキストのハッシュでコンテンツアドレス化されているためです。インターフェイスは作成をスムーズにすることを優先しており、クローズ削除は Mochi の冗長な `{{ }}` ではなく `[ ]` を使い、単一行ブロック(例:`Q: … A:` や `C: …`)でカードを定義します。 著者は Anki の煩雑な UI、“すべて期限切れを学習” ボタンの欠如、WYSIWYG エディティング、そして不安定なプラグインサポートを批判しています。Mochi は過度に冗長なクローズ構文、ノートタイプ自動化がないこと、および長期的には性能が劣る単純な倍率ベースのスケジューラーという欠点があります。Hashcards はこれらの痛みを解消し、最小限の摩擦、最適なスケジューリングのための高度な FSRS アルゴリズム、および任意のエディタや Unix ユーティリティ(例:`wc`、`awk`)でカードを編集できる機能を提供します。デッキは Git でバージョン管理・ブランチ化・マージが可能で、サードパーティサービスなしにユーザーがデータを完全にコントロールできます。 将来的には CSV ベースのカードインポート、Python で書かれたノートタイプロジック、および Git 主導の共有ワークフロー拡張をロードマップに含めています。これらの機能は協力を容易にし、コンテンツ生成を自動化するとともに、開発者が Hashcards を自身のプロジェクトへ拡張できるようにし、オープンソーススペースドリピテーション エコシステムを形成する可能性があります。