
2026/08/13 21:44
90年代の SIMD:Intel の Pentium MMX をプログラミングする
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
インテルの MMX テクノロジーは、1997 年に Pentium MMX プロセッサと同時に出荷され、パックされた整数演算および固定点数学のための新しい 57 の命令を追加することでマルチメディア性能を強化することを目的として設計されました。これは、既存の x87 フローティングポイントレジスタの下位半分を再利用して作成された 8 つの 64 ビットレジスタ(MM0–MM7)を利用していました。この設計により、MMX 操作と標準的なフローティングポイントコードの間で厳格な排他性が強制され、MMX の使用後にプログラマーがこれらの共有リソースをクリアし、通常の計算に戻る前にバグを防ぐために
EMMS 命令を実行する必要がありました。独自のベクトルレジスタを備えた純粋な SIMD アーキテクチャとは異なり、MMX は共有された FP ステートへの依存関係により、重大な制約を招きました。
AMD などの競合他社は、SIMD アプローチを用いてインテルに対抗しようとしましたが(3DNow!、1998 年)、最終的にはインテルの優位性と、CPU 側のマルチメディアタスクの多くを引き継ぐ専用グラフィックハードウェア(例:Voodoo カード)の急成長により市場での牽引力を失いました。MMX は、128 ビットレジスタとネイティブなフローティングポイントサポートをもたらした、より優れた SSE 命令セットによって 1999 年および 2000 年に置き換えられました。その限界と最終的な時代遅れに尽管如此、MMX はベクトル化演算とデータ並列プログラミングのための基礎概念を確立し、現代的な CPU アーキテクチャへの道を整えました。
本文
Intel Pentium with MMX:90 年代後半の SIMD 革命とその後
はじめに
90 年代後半の PC プログラミングをされた方は、「Intel Inside Pentium with MMX」というラベルをご存知でしょう。MMX は単なるマーケティング用語ではなく、Intel がメインストリームのデスクトップ CPU にSIMD(Single Instruction, Multiple Data)指令セットをもたらすための重大な試みでした。
現代では AVX-512 や GPU 搭載など、豊富な歴史と互換性のノイズを伴う技術が存在します。しかし、MMX はその技術が始まった当時の状況から振り返るには最適な対象です。多くのプログラマーが当時、手書きアセンブリを使用して Pentium MMX から数フレーム分のパフォーマンスを引き出す方法を学んでいました。
Intel Pentium with MMX テクノロジー
1997 年へタイムトラベルし、MMX をプログラミングする様子を探っていきましょう。
MMX とは何だったのか?
MMX(MultiMedia eXtensions)は、1997 年に Pentium MMX プロセッサと共に導入されました。グラフィックやオーディオ処理などのマルチメディア体験向上を目的としており、Pentium チップに以下のような追加指令セットをもたらしました。
- 追加指令: 57 条の新しい指令
- レジスタ変換: CPU が MMX モードに切り替わる際、x86 の最初の 64 ビットを 80 ビットの浮動小数点レジスタからMMX レジスタへと変換する仕組み
8 つの MMX レジスタ(長さ 64 ビット)
MMX の核となるアイデアはシンプルです:CPU が一度に 1 つではなく、複数の整数を同時に処理することです。
- 構造: 1 つの 64 ビットのレジスタに複数の整数を詰め込みます。
- 効果: マルチメディアソフトウェア(画像処理、オーディオミキシング、動画再生など)において大幅なスピードアップをもたらしました。
具体的な例:並列加算
個別に 8 つのバイトを加算する場合の比較です。
通常の演算:
10 + 20 30 + 40 50 + 60 70 + 80 ...
※各値を個別に処理する必要があります。
MMX を用いた演算:
- 上記のすべての加算を1 つの指令で実行可能です。
- これが「並列処理」であり、SIMD の本質です。
SIMD とは何か?
「Single Instruction, Multiple Data(1 つの指令、複数のデータ)」は、以下の定義を持つ並列処理技術です。
定義: 複数のデータポイントに対して同時に同じ演算を行う複数の処理要素を備えたコンピュータにおける技術。
- 実装方法: ハードウェア設計として内部に実装され、ISA(命令セットアーキテクチャ)を通じて直接アクセス可能です。
- MMX の役割: Intel が x86 指令セットを拡張し、新しい SIMD 指令を含めることで対応しました。
MMX は初の SIMD であったか?
いいえ、MMX は決して最初の SIMDではありません。ただし、SIMD をx86 PC では主流にする上で極めて重要な役割を果たしました。
歴史的背景:ILLIAC IV
- 時代: 1960 年代
- プロジェクト: ILLIAC IV スーパーコンピュータ
- 特徴: 最初の massively parallel(大量並列)コンピュータの一種
- 256 つの 64 ビット浮動小数点演算単位(FPU)を備える
- 1 秒間に 10 億回の演算処理が可能
- 4 つの CPU を搭載
ILLIAC IV の写真(Sascha Pohlflepp 氏による CC-BY-2.0)
また、1970〜80 年代の CDC や Cray などからアレイ処理マシンも登場しました。
SIMD とアレイプロセッサの違い
ベクトル処理と SIMD は完全に同一ではありません。主な違いは以下の通りです。
- 提示方法: データ要素がプロセッサに与えられる方法
- 実行方法: ハードウェアによる実装の違い
- SIMD の特性: レジスタの幅と密接に関連している
Cray-1 との比較
1976 年のベクトルプロセッサである Cray-1 は、それぞれ 64 ビットの値を保持する 8 つのベクトルレジスタを持っていました。
Cray-1 スーパーコンピュータ
SIMD と同様に、Cray-1 は個別の ADD 指令を 64 回発する必要はありませんでした。しかし、それを「SIMD」と呼ばない理由はアーキテクチャ的な区別にあります。
| 特徴 | SIMD (MMX) | Cray-1 (ベクトルプロセッサ) |
|---|---|---|
| 動作原理 | 固定幅のレジスタ上で動作 | コンテナのような構造 |
| 制御 | ベクトル機能ユニットは単一のサイクルで処理 | 指令あり、複数のサイクルにわたって処理可能 |
注釈: Cray-1 は VL(ベクトル長)レジスタを持っており、最大 64 要素を処理できました。VL=0 は「すべての要素(64 個)」として解釈されました。
MMX の真の重要性
MMX の真価は、比較的手頃な SIMD 命令セットをメインストリームの x86 PC に持ち込んだ点にあります。
- レトロコンピューティングにおいて注目を集める理由の一つです。
- SIMD が専門分野から通常のデスクトップソフトウェア開発へと移行した瞬間の技術と言えます。
MMX レジスタとデータ形式
レジスタの再利用
Intel は新しいレジスタを追加するのではなく、既存のx87 浮動小数点スタックレジスタを再利用しました。
- MMX レジスタ:
〜MM0
(計 8 つ)MM7 - 構造: 内部では x87 レジスタのエイリアスとして機能
- 幅: 実質的に 64 ビットのみアクセス可能(元は 80 ビット)
この設計はシリコンを節約しましたが、重要な制限も生じました。
排他的な実行環境
SIMD と浮動小数点演算は互いに排他的です。 MMX は x87 のレジスタを「占有」するため、両者は同時に動作できません。
- 重要ルール: MMX コードと x87 浮動小数点コードを混ざらせないこと。
- 対策: MMX 処理前後に必ず状態を整理する必要があります(後述する
で解決)。EMMS
データのレイアウト(パックされた整数)
1 つの MMX レジスタには以下の 4 つのレイアウトを持たせることができました:
| レイアウト | 名称 | 内容 |
|---|---|---|
| MMI | MultiMedia Integer | 8 つの 8 ビット整数 |
| MPI | MultiMedia Packed Integer | 4 つの 16 ビット整数 |
| QPI | Quad Packed Integer | 2 つの 32 ビット整数 |
| LPI | Long Packed Integer | 1 つの 64 ビット整数 |
初めての MMX プログラム:実践例
単純な加算の最適化
8 ピクセルを加算する場合(コンパイラ最適化なし)での比較です。
スカラー版(通常の C コード)
for (int i = 0; i < 8; i++) { dest[i] = src1[i] + src2[i]; }
MMX 版(アセンブリコード)
movq mm0, [src1] ; 8 バイトをロード movq mm1, [src2] ; 8 バイトをロード paddb mm0, mm1 ; 8 つのバイト加算を同時に実行 movq [dst], mm0 ; 結果を格納
- 効果: 1 つの指令で 8 つの個別な加算が置き換えられます。
MMX の強力な特徴:飽和演算(Saturation)
通常の整数演算ではオーバーフローすると値が「巻き戻ります」(例:
250 + 20 = 270 → 14)。グラフィック作業では望ましくありません。MMX は飽和(最大値にクリップ)機能を提供します。
飽和加算指令
paddusb mm0, mm1
- us: Unsigned Saturation(符号なし飽和)
- 動作:
の結果を250 + 20
にクリップします。255 - 効果: ピクセルの輝度調整やブレンド操作が容易になります。
パーフェクトな比較処理
すべての値よりも大きなものを白に変えるマスクを作成できます。
pcmpgtb mm0, mm1
- 結果: 各バイトが
(真)または0xFF
(偽)になります。0x00 - 用途: しきい値フィルタ、スプライトの透明度判定、衝突ロジックなど。
パックされた値のシフト
ビットシフトは乗算・除算の高速な代替手段です。MMX はパックされた値を同時にシフトできます。
psllw mm0, 1 ; すべての 16 ビット値を 2 倍にする(左シフト) psrlw mm0, 1 ; すべてを 2 で割る(右シフト)
パックされた乗算
定点数計算などで 2 の倍数以外の数での乗算が必要です。MMX はこれをサポートします。
pmullw mm0, mm1
- 動作: 各ペアの 16 ビット整数を独立して乗算。
- 用途: オーディオボリュームスケール、画像畳み込み、色変換など。
実践的な例:画像明るくするルーチン
以下のコードは、画像全体を一定値(20)だけ明るくし、最大値 255 で飽和させる処理です。
スカラー版(C)
int intensity = 20; for (int i = 0; i < WIDTH * HEIGHT; i++) { pixels[i] += intensity; if (pixels[i] > 255) { pixels[i] = 255; } }
MMX 版(アセンブリ)
一度に8 ピクセルを処理します。
section .data intensity: times 8 db 20 ; 20 を 8 つ重複させる (0x14) section .text ; esi = ピクセルへのポインタ ; ecx = 8 ピクセルのチャンクの数 loop: movq mm0, [esi] ; 8 ピクセルをロード movq mm1, [intensity] ; 8 つの 20 をロード paddusb mm0, mm1 ; 飽和加算(自動クリップ) movq [esi], mm0 ; 結果を格納 add esi, 8 ; 次の 8 ピクセルへ dec ecx jnz loop emms ; MMX ステートをクリアして終了
重要な注意点:EMMS 指令
MMX は x86 の浮動小数点レジスタから「借入れ」しているため、使用終了時に状態をリセットする必要があります。
emms
- 機能: MMX ステートをクリアし、x87 浮動小数点指令の正常動作を再開します。
- 注意点:
を呼び忘れると、後の浮動小数点計算で奇妙なバグが発生します。必ず使用後(および浮動小数点コード前)に挿入してください。EMMS
ゲームにおける MMX の活用状況
使われたのか?
MMX が直接ゲーム処理に使われるのは限定的でした。1997 年当時、GPU やアクセラレーションカード(S3 ViRGE, Voodoo など)の登場により、CPU 側だけで負荷を担う必要性が減っていました。ただし、以下の用途で MMX は有益でした。
- ソフトウェアテクスチャマッピング
- スプライト合成
- アルファブレンド
- 画像スケーリング
- MP3 デコーディング
- モデムソフトウェア
- MPEG 動画再生
- オーディオミキシング
具体例:Comanche 3
Novalogic の「Comanche 3」開発者の Kyle Freeman は、MMX がオーディオとグラフィックの両方で多用されたことを証言しています。
Kyle Freeman のツイート:Comanche 3 で MMX が使われたこと
その他ゲームとの関係
- Eraser Turnabout, POD, Extreme Assault: ソフトウェアレンダラーで MMX 拡張を要求・利用した例。
- Tomb Raider: 噂にはあるが、信頼できるソースでの実装は不明。
- Quake / Quake II:
- Quake (1996) は Pentium MMX (1997) のリリースより前であり、MMX を利用していない。
- レジスタ割り当てやループ最適化による性能向上が主因。
対応戦略
MMX コードパスはオプションとして提供されました。数百万人のユーザーが古い Pentium を使用していたため、非-MMX バージョンも維持する必要がありました。
MMX サポートの検出方法
ソフトウェアは CPU が MMX をサポートしているかを
CPUID 指令で確認する必要があります。
mov eax, 1 cpuid ; CPU の機能情報を取得 test edx, 1 << 23 ; EDX レジスタのビット 23 をチェック(MMX 支援フラグ) jz NoMMX ; 0 の場合は MMX なし処理へジャンプ
他のサポートも確認可能
- Bit 25: SSE サポート
- Bit 26: SSE2 サポート
AMD の 3DNow! とその行方
概要
1998 年、AMD は**3DNow!**という独自技術をリリースしました。
- 概念: MMX に類似した SIMD 指令(ただし浮動小数点対応)を追加。
- 搭載チップ: AMD K6-2 (1998 年)
3DNow! を搭載した AMD K6-2
Quake II (3.19) 以降のソースコードには、別途ビルドされた 3DNow! 最適化版(v3.20)が提供されました。
なぜ成功しなかったのか?
- 市場シェア: Intel が x86 市場を支配しており、MMX の普及率が高かったため。
- 互換性問題: Intel CPU と AMD CPU で動作保証の難しさ。
- Intel の反攻: 1999 年 Pentium III にSSEが導入され、決定的な打撃を受けました。
SSE による転換点
Intel の SSE は以下の理由で MMX および 3DNow! を上回りました:
- 浮動小数点 SIMD: MMX の最大の制限(SIMD と FPU が共存できない)を解消。
- 汎用性: スカラーおよびパックされた浮動小数点指令の両方をサポート。
- 業界標準: ベンダーに依存しない共通規格として定着。
ただし、MMX は整数 SIMD(ピクセル処理など)において優れていたため、すぐに死にはしませんでした。SSE が 128 ビット XMM レジスタを導入したことで、MMX の機能がより強力なアーキテクチャに統合されました。
なぜ MMX は消えてしまったのか?
MMX レジスタが不要になった主な理由は、Intel SSE2(2000 年)の導入です。
- SSE2: 128 ビット XMM レジスタを持ち、広範な整数 SIMD 操作を追加。
- 比較例:
- MMX: 一度に 8 ピクセル処理 (
)paddusb mm0, mm1 - SSE2: 一度に 16 ピクセル処理 (
)paddusb xmm0, xmm1
- MMX: 一度に 8 ピクセル処理 (
; MMX (旧) movq mm0, [pixels] paddusb mm0, mm1 movq [pixels], mm0 emms ; SSE2 (新) movdqu xmm0, [pixels] paddusb xmm0, xmm1 movdqu [pixels], xmm0
結論:MMX の評価と意義
「魔法のターボボタン」ではない
Pentium MMX チップ自体が速かったのは事実ですが、MMX コードによってすべての処理が速くなるわけではありませんでした。キャッシュサイズ(16KB→32KB)などの要因も大きく寄与していました。
技術的移行として成功
- ゲームやレンダリングには限界がありましたが、デジタル信号処理(オーディオ・モデム)では有効でした。
- 「失敗」とは呼べない、PC プログラミングにおける重要なマイルストーンです。
今日に残される教訓
MMX は現代の SIMD 技術(AVX など)へと系譜を引いています:
- パックされたデータの扱い方
- ベクトル化算術の実践
- 飽和操作の重要性
- データ並列プログラミングの考え方
90 年代後半のプログラマーにとって、MMX は「アルゴリズムを個別値ではなくベクトルとして見る」視覚化の第一歩でした。
この記事は Intel Pentium with MMX テクノロジーの歴史と技術的な意義を振り返るものです。レトロプログラミングの魅力を再発見してください。