SIMD型の内部構造
名前空間SIMD
にはSIMD.jsが取り扱うデータ型オブジェクトが定義されており, これらを総じてSIMD型と呼びます. SIMD型はCPUレジスタに対応する概念で, いずれも内部に128bitのデータを保持しています. この128bitを(均等に)分割したものをレーンと呼び, SIMD型に含まれるレーンは全て同じ数値型であるものとして扱います. なお, SIMD型の各レーンでは表面上little endianの順にbyte値が管理されています.
このようにSIMD型は「サイズ固定の型付き配列」によく似た構造をしています. が, 型付き配列と異なりSIMD型そのものはイミュータブルであり中身を操作するための機能を一切持ちません.
SIMD型の分類
128bitレジスタをレーンに区切る際, その区切り方(レーン数)と各レーンの取り扱い(データ型)によって異なるSIMD型が定義されています. まとめると次のように分類出来ます.
SIMD型の分類
SIMD型 | データ型 | bit数 | レーン数 | 値範囲(概要) | 対応する型付き配列 |
SIMD.Float64x2† | 浮動小数点数型 | 64bit | 2 | -(253-1) 〜 253-1 | Float64Array |
SIMD.Float32x4 | 浮動小数点数型 | 32bit | 4 | -(224-1) 〜 224-1 | Float32Array |
SIMD.Int32x4 | 整数型 | 32bit | 4 | -2,147,483,648 ~ 2,147,483,647 | Int32Array |
SIMD.Int16x8 | 整数型 | 16bit | 8 | -32,768 〜 32,727 | Int16Array |
SIMD.Int8x16 | 整数型 | 8bit | 16 | -128 〜 127 | Int8Array |
SIMD.Uint32x4 | 非負整数型 | 32bit | 4 | 0 ~ 4,294,967,295 | Uint32Array |
SIMD.Uint16x8 | 非負整数型 | 16bit | 8 | 0 〜 65,535 | Uint16Array |
SIMD.Uint8x16 | 非負整数型 | 8bit | 16 | 0 〜 255 | Uint8Array UintClampedArray |
SIMD.Bool64x2† | 真偽値型 | (64bit) | 2 | true/false | - |
SIMD.Bool32x4 | 真偽値型 | (32bit) | 4 | | - |
SIMD.Bool16x8 | 真偽値型 | (16bit) | 8 | | - |
SIMD.Bool8x16 | 真偽値型 | (8bit) | 16 | | - |
†優先度が低いことから初期仕様から除去 |
- 浮動小数点数型
- 各レーンを浮動小数点数として扱います. 128bitレジスタの区切り方によりSIMD.Float64x2とSIMD.Float32x4の2種類があります.
- 整数型
- 各レーンを整数として扱います. 128bitレジスタの区切り方によりSIMD.Int32x4, SIMD.Int16x8, SIMD.Int8x16の3種類があります.
- 非負整数型
- 各レーンを非負整数として扱います. 128bitレジスタの区切り方によりSIMD.Uint32x4, SIMD.Uint16x8, SIMD.Uint8x16の3種類があります.
- 真偽値型
- 各レーンを真偽値として扱います. この型は条件判定処理に用いられる特殊なもので, SIMD.Bool64x2, SIMD.Bool32x4, SIMD.Bool16x8, SIMD.Bool8x16の4種類があります.
これらは名称こそ違えど, 基本的な使い方は全く同じです. そこで以降の説明ではこれらをまとめてSIMD.%type%と記述することにします.
SIMD型オブジェクトの生成
SIMD型オブジェクトの生成には型ごとに専用の生成関数†を用います.
- SIMD.%type% SIMD.%type%(s0〜s15)
- SIMD.%type%オブジェクトを生成します.
このとき, SIMD.%type%
はnew演算子と組み合わせて利用することは出来ません.
なお, SIMD.%type%
関数にはSIMD.%type%
オブジェクトを操作するためのSIMD操作関数が多数定義されています.
生成関数のリネーム
SIMD.%type%
をいちいちタイプするのは可読性の面からも効率的ではありません. そのため, SIMD型の生成関数は一旦別の変数に格納(エイリアスを付与)した上で使うと良いでしょう.
範囲外の値の指定
JavaScriptでは数値を倍精度浮動小数点数形式で扱うため, SIMD.%type%
型内部に保持できない値が渡された場合, 次のように動作します.
- 32bit浮動小数点型の場合
パラメータはMath.fround
関数が適用されたものとして扱われます.
- 整数型・非負整数型の場合
指定した数(を2進数で表した際)の下位bitを切り取ったものがSIMD型オブジェクトに格納されます.
補足)特殊な値のbyte列表現
Infinity
やNaN
等の特殊な値は特定のbyte列として表されます. SIMD.Float32x4型オブジェクトの内容をbyte(bit列)値として扱う場合に利用します.
特殊な値のbyte列表現
特殊値 | byte配列 | 備考 |
0 | 0x00000000 | |
-0 | 0x80000000 | |
Infinity | 0x7f800000 | |
-Infinity | 0xff800000 | |
NaN | 0bs11111111xxxxxxxxxxxxxxxxxxxxxx | sとxは不定 |
特にNaN値のbit列表現はNaN値の判定に利用します.
パラメータ未指定時の挙動
生成関数にパラメータを渡さなかった場合, SIMD型オブジェクトはNaN/0/false
値で初期化されます.
単一値によるSIMD型オブジェクトの生成
全てのレーンが同じ値となるSIMD型オブジェクトを生成する場合はsplat関数を用います.
- SIMD.%type% SIMD.%type%.splat(value)
- 単一値から構成されるSIMD.%type%オブジェクトを生成します.
SIMD型オブジェクトの挙動
SIMD型オブジェクト特有の動作について示します.
SIMD型と型判定
得られたSIMD型オブジェクトはSIMD.%type%型として判定されます.
また, 型判定のための専用のSIMD.%type%.checkメソッドが用意されています.
- SIMD.%type% SIMD.%type%.check(a)
- 指定したパラメータが当該SIMD型であるかを確認します. もし異なるSIMD型, オブジェクト等が与えられた場合はエラーを発生します.
SIMD型と比較演算子
SIMD型オブジェクトはObject型です. つまりSIMD型は新たなプリミティブ型を与えるものではありません.
そのためSIMD型オブジェクトに対する比較演算子「==
」は, 厳密等価演算子として振る舞います.
生成関数の取得
SIMD型オブジェクトから対応する生成関数を取得するには__proto__.constructor
プロパティを参照します.
SIMDジェネリック関数の定義
SIMD.jsにおけるメソッドはジェネリック関数のように複数のSIMD型に対して共通に定義されているため, この特徴を用いると実際のSIMD型を意識しないコードを記述するも出来ます.
toStringメソッドによる内部状態の確認
SIMD型オブジェクトの型や内容はtoString
メソッドを適用することで確認出来ます. なお, toString
メソッドはオブジェクトを文字列化する際に暗黙的に呼びだされます.
valueOfメソッドの挙動
SIMD型オブジェクトに対してvalueOfメソッドを実行すると, 常にエラーを発生します.
データ操作関数
SIMD型オブジェクトの内部データを操作するには専用のSIMD操作関数を呼び出す必要があります. 下記にデータ操作に関わるものを示します.
データ操作関数の分類
| 値の設定 | 値の取得 |
単一値 | replaceLane | extractLane |
複数値 | load load1 load2 load3 | store store1 store2 store3 |
SIMD型とSIMD操作関数
SIMD.%type%で定義されたSIMD操作関数を異なるSIMD型オブジェクトに適用しようとすると殆どのケースに於いてエラーが発生します.
なおSIMD.%type%.fromInt32x4Bits(型変換関数)と言った例外もあります.
値の取得
SIMD型オブジェクトの中身はSIMD.%type%.extractLaneで取得出来ます.
- number/boolean SIMD.%type%.extractLane(vec, index, value)
- 与えたSIMD型オブジェクトの一部を指定した値で置き換えたものを新たに生成する.
なお, このメソッドは最終的な計算結果を求めるために利用すべきであり, 新たなSIMD型を生成する用途に於いては専用のメソッドを用いるべきです.
値の変更
SIMD型オブジェクトはイミュータブルなため, 一度生成すると中身を操作することは出来ません. そのためレーン値の一部を変更するにはSIMD.%type%.replaceLaneメソッドを用いて新たなSIMD型オブジェクトを生成します.
- SIMD.%type% SIMD.%type%.replaceLane(vec, index, value)
- 与えたSIMD型オブジェクトの一部を指定した値で置き換えたものを新たに生成する.
なおメソッド呼び出しの都度新たなオブジェクトが生成されてしまうため, 複数のレーン値を書き換えるのであれば下記に示すstore/loadメソッドを使うべきです. また, レーン値の並べ替えを行う場合は後述するswizzleメソッドを用います.
型付き配列との連携
数値を扱うSIMD型オブジェクトは型付き配列との間でデータの授受が可能です. これはcanvas要素やaudio APIで得たバイナリデータをSIMD.jsで編集する際に重要な役割を果たします.
データ授受に関わる基本動作
SIMD型と型付き配列との間におけるデータ授受は, 型付き配列内部のArrayBufferの内容(bit列)を転写することで行われます. そのため, SIMD型の各レーンのデータ型と型付き配列のデータ型とを一致させると間違いがありません.
型付き配列との対応
SIMD型 | データ型 | bit数 | レーン数 | 対応する型付き配列 |
SIMD.Float64x2 | 浮動小数点数型 | 64bit | 2 | Float64Array |
SIMD.Float32x4 | 浮動小数点数型 | 32bit | 4 | Float32Array |
SIMD.Int32x4 | 整数型 | 32bit | 4 | Int32Array |
SIMD.Int16x8 | 整数型 | 16bit | 8 | Int16Array |
SIMD.Int8x16 | 整数型 | 8bit | 16 | Int8Array |
SIMD.Uint32x4 | 非負整数型 | 32bit | 4 | Uint32Array |
SIMD.Uint16x8 | 非負整数型 | 16bit | 8 | Uint16Array |
SIMD.Uint8x16 | 非負整数型 | 8bit | 16 | Uint8Array, UintClampedArray |
さもないと元の値のbit列が転写に伴い異なる値と解釈されてしまいます.
なお真偽値型については対応する型付き配列が存在しません.
エンディアンの取り扱い
SIMD.jsによるArrayBuffer(システムメモリ)の読み込み・書き込みはシステムのエンディアンを基準に行われます. そのため意図的に値の再解釈を引き起こさせる場合は, 厳密にはこのエンディアンを加味したコードを記述する必要があります. さもないと(稀に)型付き配列の内容が正しいものとなりません.
エンディアン毎に異なる整数解釈
8bitInt列としての値
| 32bitIntとしての値 |
big endian | little endian |
|
0x1a2b3c4d |
0x4d3c2b1a |
システムのエンディアンは次のコードで判定出来ます.
とは言え, 現在主流のCPUの殆どはlittle endianを採用しているため, SIMD.jsを使う分にはさほど気にする必要はないでしょう.
型付き配列からSIMD型オブジェクトへの転写
SIMD.%type%.loadメソッドは型付き配列を元にSIMD型オブジェクトを生成します.
- SIMD.%type% SIMD.%type%.load(typedArray, index)
- 指定した型付き配列からSIMD型オブジェクトを生成します. indexにはデータの抽出を開始する位置を指定します.
なお, 型付き配列の指定したインデックスから後ろに128bit分のデータが存在しなかった場合はエラーが発生します.
そのため, 型付き配列の内容を逐次SIMD型オブジェクトに転写する処理を行う場合は, 予め型付き配列のサイズをSIMD型のレーン数倍の値にしておきます.
64/32bit型専用の関数
64/32bit型(Float64x2, Float32x4, Int32x4, Uint32x4)については, 4(2)レーンのうち1〜3レーンのみを読み込む為のメソッドが提供されています.
- SIMD.%type% SIMD.%type%.load1(typedArray, index)
- 指定した型付き配列からSIMD型オブジェクトを生成します. indexから1レーン分のデータを読み込みます.
- SIMD.%type% SIMD.%type%.load2(typedArray, index)
- 指定した型付き配列からSIMD型オブジェクトを生成します. indexから2レーン分のデータを読み込みます.
- SIMD.%type% SIMD.%type%.load3(typedArray, index)
- 指定した型付き配列からSIMD型オブジェクトを生成します. indexから3レーン分のデータを読み込みます.
型付き配列への書き戻し
SIMD.%type%.storeメソッドはSIMD型オブジェクトの内容を型付き配列に書き込みます.
- SIMD.%type% SIMD.%type%.store(typedArray, index, value)
- 指定した型付き配列にSIMD型オブジェクトの内容を転写します. indexにはデータの転写を開始する位置を指定します. メソッドの実行結果としてvalueに渡したSIMD型オブジェクトが返されます.
なお, 型付き配列の指定したインデックスから後ろに128bit分のバッファが存在しなかった場合はエラーが発生します.
64/32bit型専用の関数
64/32bit型(Float64x2, Float32x4, Int32x4, Uint32x4)については, 4(2)レーンのうち1〜3レーンのみを書き込む為のメソッドが提供されています.
- SIMD.%type% SIMD.%type%.store1(typedArray, index, value)
- 指定した型付き配列にSIMD型オブジェクトの内容を転写します. indexから1レーン分のデータが書き込まれます. メソッドの実行結果としてvalueに渡したSIMD型オブジェクトが返されます.
- SIMD.%type% SIMD.%type%.store2(typedArray, index, value)
- 指定した型付き配列にSIMD型オブジェクトの内容を転写します. indexから2レーン分のデータが書き込まれます. メソッドの実行結果としてvalueに渡したSIMD型オブジェクトが返されます.
- SIMD.%type% SIMD.%type%.store3(typedArray, index, value)
- 指定した型付き配列にSIMD型オブジェクトの内容を転写します. indexから3レーン分のデータが書き込まれます. メソッドの実行結果としてvalueに渡したSIMD型オブジェクトが返されます.
レーンの選択
load1〜3/store1〜3メソッドは通常先頭レーンのみが読み込み・書き込み対象となりますが, swizzleメソッドと組み合わせることで任意のレーンを処理対象とすることが出来ます.