行列
行列(ぎょうれつ、Matrix)とは、数字や文字を長方形(縦横)に並べたものです。横の並びを「行」、縦の並びを「列」と呼び、主にデータの構造化や計算に用いられます。
プログラミングにおける行列の表現
多次元配列で表現する
プログラミング言語では多次元配列で簡単に表現できます。
var matrix = new float[2,2];
データの格納や単純なループ処理に向いています。
ちなみに最近流行りの人工知能はこの方式を内部DSLでカチッとしたデータ構造に変換するという方式が主流です。 変換処理というコンピュートシェーダー実行前の準備で初速は遅くなりますが、コンピュートシェーダーが考えている時間が非常に長いので問題になりません。
構造体で表現する
一方、高速化を狙う場合は構造体で表現する方式が一般的です。
public struct Float2x2
{
public float m11, m12;
public float m21, m22;
public Float2x2(float m11, float m12, float m21, float m22)
{
this.m11 = m11; this.m12 = m12;
this.m21 = m21; this.m22 = m22;
}
// 行列の掛け算を演算子で定義
public static Float2x2 operator *(Float2x2 a, Float2x2 b)
{
return new Float2x2(
a.m11 * b.m11 + a.m12 * b.m21, a.m11 * b.m12 + a.m12 * b.m22,
a.m21 * b.m11 + a.m22 * b.m21, a.m21 * b.m12 + a.m22 * b.m22
);
}
}
- 高速なアクセス
構造体はメモリ上で連続した領域に配置され、クラスのようにヒープへのアクセスを伴わないため、高速に読み書きできます。
- 値型(Value Type)
変数そのものがデータを持つため、メモリ管理のオーバーヘッド(ガベージコレクション)が少ないです。
- SIMD演算の利用
structで定義された行列は、CPUの機能(SIMD)を活用して、4つの数値を同時に計算する最適化が行いやすくなります。
ゲームでは「ゲームループ」という限られた時間(たとえば60fpsであれば16ms以内)でレンダリングシェーダーを細かく何度も回さないといけないのでこちらの方式が主流です。
行優先と列優先
行列のメモリ配置には行優先(Row-Major)と列優先(Column-Major)があり、業界や分野によって採用される標準が明確に分かれます。
行優先(Row-major)と列優先(Column-major)は、行列の“メモリ上の並び順”の違いであり、数学的な意味は変わらない。
なぜ2種類あるかというと完全に「宗教上の理由」です。「インデントはタブかスペースか」の論争よりも被害の範囲が広い醜い争いです。
これを把握せずに計算式をコピペすると残念な結果になります。行優先と列優先のデータが混ざると意図せず行列が転置(行と列が入れ替わる)されます。不具合の温床となるゴミみたいな争いです。
行優先 (Row-major)
public struct Float2x2 {
public float m11, m12;
public float m21, m22;
}
「メモリ配置が簡単なため高速」という理由でプログラミング界隈で人気のある形式です。主に先にコンピューターおよびプログラミングを学び、その過程で仕方なく算数が必要になり学んだ人はこちらを推しています。
- Direct3D (HLSL)
- Python = NumPy
- 列優先 (Column-major)
public struct Float2x2 {
public float m11, m21;
public float m12, m22;
}
古くから「算数の教科書」の世界で採用されてきた形式です。主に先に算数を学び、その過程で仕方なくコンピューターを学んだ人はこちらを推しています。
- Fortran
- MATLAB
- OpenGL (GLSL)