TL

プログラマブルシェーダとGPU実行モデル

頂点・フラグメント・コンピュートの各シェーダが何を担い、なぜ分岐発散でGPU性能が落ちるのか。パイプラインの入出力とSIMT実行を原理から押さえ、遅いシェーダの原因を自力で切り分けられるようになります。

応用GPUシェーダSIMTレンダリングパイプライン分岐発散コンピュートシェーダ最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.グラフィックスパイプラインは頂点シェーダ(頂点ごと・座標変換)→ラスタライザ(固定機能・三角形をピクセルに分解)→フラグメントシェーダ(画素候補ごと・色決定)と流れ、ステージ間は補間された varying で受け渡される。ユニフォームとテクスチャは全スレッド共通の読み取り入力。
  • 2.GPUは1命令を多数スレッドへ一斉適用するSIMT実行で、32/64スレッドをまとめたワープ(warp/wavefront)が最小スケジュール単位。同じワープ内は常に同一命令を実行するため、演算器を埋め切る並列度がなければピークに届かない。
  • 3.if文でワープ内のスレッドが別々の道に分かれると、両方の分岐を順に実行し不要側をマスクで無効化する(分岐発散)。最悪でスループットが分岐の数だけ低下するため、条件はワープ境界に揃えるかデータで分ける設計が要になる。

シェーダは「並列に走る小さなカーネル」

シェーダ(shader)とは、GPU 上で膨大な要素に対して並列実行される小さなプログラムです。頂点1個ごと、あるいは画素1個ごとに「同じコード」が独立して走る——この一斉並列こそが GPU の本質で、CPU のようにスレッドを賢く分岐させる設計とは前提が真逆です。プログラマブルシェーダが登場する前、GPU は変換・ライティング・テクスチャ適用が配線で固定された「固定機能パイプライン」でした。これを任意のコードに置き換えられるようにしたのがプログラマブルシェーダで、リアルタイム CG の表現力を一変させました。

現代の GPU では役割の異なる複数のシェーダステージが連なります。まず描画(グラフィックス)用のパイプラインを追い、その後に描画から独立した汎用計算用のコンピュートシェーダを見ていきます。GPU がなぜこの形の並列演算器になったかというハードウェア背景は /hardware-components/ の各記事も参照してください。

グラフィックスパイプライン ── ステージと入出力

三角形メッシュを画面に描く標準的な流れは、プログラマブルなステージと固定機能のステージが交互に並びます。中核となる2つのシェーダと、その間を埋めるラスタライザの関係を押さえるのが第一歩です。

ステージ種別実行の粒度主な仕事
頂点シェーダプログラマブル頂点ごとに1回モデル座標をクリップ空間へ変換。法線やUVを次段へ出力
ラスタライザ固定機能三角形ごと三角形を覆う画素候補(フラグメント)を生成し、頂点出力を各画素へ補間
フラグメントシェーダプログラマブルフラグメントごとに1回テクスチャ・ライティングを計算して画素の色を決める
出力合成固定機能フラグメントごと深度テスト・ステンシル・ブレンドで最終ピクセルを確定

頂点シェーダの主目的は座標変換です。モデル空間の頂点にモデル・ビュー・射影の各行列を掛けてクリップ空間の同次座標へ移します。ライティングに必要な法線ベクトルや、テクスチャ参照用の UV 座標もここで計算し、出力として次段へ渡します。頂点シェーダは頂点の位置を動かせますが、頂点を増やしたり減らしたりはできません(それはジオメトリ/テッセレーションといった別ステージの役目です)。

ラスタライザは固定機能で、プログラムでは書き換えられません。三角形が画面上で覆う画素の集合を求め、各画素に対応する「フラグメント」を生成します。重要なのは、頂点シェーダが出力した値を三角形の内部で 重心座標にもとづき補間 して各フラグメントへ渡す点です。この補間された入力を varying(あるいは補間子)と呼びます。

ステージ間の受け渡し ── varying の補間

頂点シェーダの出力がフラグメントシェーダの入力になるとき、両者は1対1では対応しません。三角形は3頂点しか持たないのに、その内部は数百・数千の画素で埋まるからです。ラスタライザはこの隙間を線形補間で埋めます。

// 頂点シェーダ: 頂点ごとに色を出力(out = varying)
out vec3 vColor;
void main() {
    gl_Position = uMVP * vec4(aPos, 1.0);
    vColor = aColor;              // 3頂点それぞれの色
}

// フラグメントシェーダ: 補間済みの色を受け取る(in = varying)
in vec3 vColor;                   // 三角形内部では頂点色が滑らかに混ざる
out vec4 fragColor;
void main() {
    fragColor = vec4(vColor, 1.0);
}

補間には注意が要ります。画面上で等間隔な線形補間(スクリーン空間補間)をそのまま UV に使うと、遠近がついた面でテクスチャが歪みます。これを防ぐため実際には パースペクティブ補正補間 を行い、各頂点値を w で割って補間し最後に戻すことで、3D 空間で正しい比率になるよう補正します。深度値や法線も同様に、何を「正しく」補間したいかで補正の要否が変わります。

フラグメントは「まだ画素ではない」

フラグメントはピクセル候補であって確定した画素ではありません。フラグメントシェーダが色を出しても、その後の深度テストで手前の物体に隠れていれば破棄されます。だからこそ、隠れて見えなくなる画素にも計算コストがかかる「オーバードロー」が問題になります。手前の不透明物体から先に描く、あるいは深度だけを先に書き込む Z プリパスで、無駄なフラグメントシェーダ実行を減らすのが定石です。

ユニフォームとテクスチャ ── 全スレッド共通の入力

シェーダへの入力は2種類に大別できます。頂点属性や varying が「要素ごとに異なる」入力なのに対し、ユニフォーム(uniform)とテクスチャは 1回の描画呼び出しの間、全スレッドで共通の読み取り専用データ です。

  • ユニフォーム: 変換行列・光源の位置・時刻など、フレーム内で一定の少量パラメータ。全スレッドが同じ値を読むため定数キャッシュに乗り、極めて高速に参照できる。
  • テクスチャ: 画像やルックアップテーブル。座標を指定して読み出し、専用のテクスチャユニットがフィルタリング(バイリニア補間・ミップマップ選択)をハードウェアで行う。

テクスチャアクセスは「どこを読むか」がスレッドごとに違うため、メモリの効き方がユニフォームとは大きく異なります。近い座標を読むスレッドはキャッシュを共有できて速いのに対し、飛び飛びの座標を読むと DRAM 待ちで性能が落ちます。ミップマップは縮小時のエイリアシングを防ぐと同時に、遠景で小さなテクスチャを読ませることでキャッシュ効率も上げる仕掛けです。

SIMT ── 1命令を多数スレッドへ一斉適用

ここからが GPU 実行モデルの核心です。GPU は多数のスレッドを SIMT(Single Instruction, Multiple Threads) で走らせます。CPU の SIMD が「1スレッドが幅の広いベクトル演算をする」のに対し、SIMT は「多数の独立したスレッドが、実は同一の命令を歩調を合わせて実行する」モデルです。

その最小単位が ワープ(warp、NVIDIA では32スレッド/AMD では wavefront で32または64スレッド) です。ワープ内の全スレッドは常に同じ命令を、同じプログラムカウンタで実行します。ハードウェアのスケジューラはスレッドを1本ずつではなくワープ単位で扱い、あるワープがメモリ待ちで止まると即座に別のワープへ切り替えて演算器を遊ばせません。この 大量のワープを高速に切り替えて遅延を隠す 方式をレイテンシ隠蔽と呼び、GPU が巨大なレジスタファイルを持つ理由でもあります。

SIMT のスケジュール単位(例: 1ワープ = 32スレッド)

  1命令 ──┬─→ lane 0  ┐
          ├─→ lane 1  │ 32本が同じ命令を
          ├─→ ...     │ 同一サイクルで実行
          └─→ lane 31 ┘

  各 lane は自分のレジスタとインデックスを持つが、
  実行する命令列(プログラムカウンタ)はワープで共通。

この構造から、GPU の性能を出すには 同種の処理を大量に並べて演算器を埋める ことが絶対条件だと分かります。要素数が数個しかない処理や、スレッドごとに全く違うコードを走らせたい処理は、SIMT とは根本的に相性が悪いのです。

分岐発散 ── if 文がスループットを削る理由

SIMT の最大の落とし穴が 分岐発散(branch divergence) です。ワープ内のスレッドは同一命令しか実行できないのに、if 文の条件がスレッドごとに真偽で割れると、進みたい道が分かれてしまいます。ハードウェアはこれを、両方の分岐を順番に実行し、その分岐に該当しないスレッドをマスクで無効化する ことで辻褄を合わせます。

ワープ内で条件が割れた場合(発散)

  if (cond) {  A }  →  cond が真の lane だけ有効、偽の lane は待機
  else      {  B }  →  cond が偽の lane だけ有効、真の lane は待機

  結果: A の時間 + B の時間 が両方かかる(本来は片方だけのはず)
       → 有効に働く lane の割合が下がり、実効スループット低下

つまり分岐そのものが遅いのではなく、同じワープ内で結果が割れる ことがコストです。ワープ内の全スレッドが揃って真、または揃って偽なら、片側の分岐だけを実行して終わり、発散は起きません。最悪ケースは全 lane がばらばらの経路をたどる場合で、スループットは分岐の数に反比例して落ち込みます。ループの回転数がスレッドごとに違う場合も同種の問題(最も長いスレッドに全体が引きずられる)が生じます。

分岐発散を抑える設計

条件分岐は「データの並び」で決まることが多いので、発散を避ける鍵はデータ側にあります。第一に、分岐条件が ワープ境界(連続する32/64要素)で揃う ようデータを並べ替える。第二に、軽い条件なら分岐せず両方計算して選ぶ(プレディケーション)ほうが速い場合がある。第三に、種類の違う仕事は別々のカーネル/描画呼び出しに分けて、各カーネル内を均質に保つ。「賢い早期リターン」で枝刈りする CPU の直感は、GPU ではしばしば逆効果になります。

コンピュートシェーダ ── 描画から切り離した並列計算

頂点/フラグメントは描画パイプラインに組み込まれた特化ステージですが、コンピュートシェーダ(compute shader) はラスタライザや固定機能を通さず、GPU の演算資源を汎用計算に直接使うためのステージです。物理シミュレーション、パーティクル更新、画像フィルタ、機械学習の前処理などに用いられ、GPGPU の中核をなします。

コンピュートシェーダは実行を スレッドグループ(ワークグループ) の格子として起動します。開発者が (x, y, z) のグループ数とグループ内スレッド数を指定し、各スレッドは自分の一意なインデックスを頼りに担当データを処理します。ワープが並列実行の物理単位(ハードウェア都合)であるのに対し、スレッドグループは論理的な協調単位である点が重要です。

  • 共有メモリ: 同一グループ内のスレッドだけがアクセスできる高速オンチップメモリ。DRAM を介さずデータを融通でき、畳み込みやリダクションで再利用を効かせて帯域を節約する。
  • バリア同期: グループ内の全スレッドが特定地点まで到達するのを待ち合わせる命令。共有メモリへの書き込みを他スレッドが読む前に、バリアで順序を保証する必要がある。
試験・面接で問われる勘所

「頂点/フラグメントシェーダとコンピュートシェーダの違いは」と問われたら、パイプライン上の位置で答えるのが正確です。頂点/フラグメントは描画パイプラインに固定機能ステージと一体で組み込まれ、入出力(頂点属性・レンダーターゲット)が描画に紐づきます。コンピュートは描画パイプラインの外にあり、任意のバッファを読み書きし、スレッドグループと共有メモリ・バリアで協調計算を組めます。並列実行が SIMT/ワープ単位である点は3者に共通で、分岐発散の影響も等しく受けます。

まとめ

  • グラフィックスパイプラインは 頂点シェーダ(座標変換)→ ラスタライザ(固定機能・フラグメント生成)→ フラグメントシェーダ(色決定)→ 出力合成 と流れ、ステージ間は補間された varying で受け渡す。UV や深度は歪みを避けるためパースペクティブ補正補間される。
  • ユニフォームとテクスチャは全スレッド共通の読み取り入力。ユニフォームは定数キャッシュで高速、テクスチャはアクセス局所性で速度が大きく変わる。
  • GPU は SIMT 実行で、ワープ(32/64スレッド)が最小スケジュール単位。同一命令を一斉適用するため、演算器を埋める並列度と、レイテンシ隠蔽のための大量ワープが性能の前提になる。
  • 分岐発散 は同一ワープ内で条件が割れると両分岐を順に実行して起こる性能低下で、条件をワープ境界に揃える・プレディケーション・カーネル分割で抑える。
  • コンピュートシェーダ は描画から独立した汎用計算ステージで、スレッドグループ・共有メモリ・バリア同期で協調計算を組む。並列の物理単位がワープである点は描画系と共通。

グラフィックス Article

プログラマブルシェーダとGPU実行モデルを実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

GPU

比較で見る軸

難易度: advanced / カテゴリ: グラフィックス / タグ数: 6

導入後に効く点

GPUは1命令を多数スレッドへ一斉適用するSIMT実行で、32/64スレッドをまとめたワープ(warp/wavefront)が最小スケジュール単位。同じワープ内は常に同一命令を実行するため、演算器を埋め切る並列度がなければピークに届かない。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
グラフィックス
タグ数
6

判断チェックリスト

  • 自社の用途が「GPU / シェーダ」に近いか確認する。
  • 強みである「グラフィックスパイプラインは頂点シェーダ(頂点ごと・座標変換)→ラスタライザ(固定機能・三角形をピクセルに分解)→フラグメントシェーダ(画素候補ごと・色決定)と流れ、ステージ間は補間された varying で受け渡される。ユニフォームとテクスチャは全スレッド共通の読み取り入力。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

GPUシェーダSIMTレンダリングパイプライン分岐発散GPUシェーダSIMT
参考: 公式情報