SIMD.jsの基本的な使い方・まとめ

written by DEFGHI1977.

本文書は次期ECMAScriptへの導入が見込まれているSIMD.js仕様について, その動作を確認しつつ効率的な利用を探るために作ってみたものです. SIMD.jsが提唱され数年が経ちましたが, いまだ確定した資料に乏しくその動作を追うのは意外に面倒です. そのため, 内容については(筆者の思い込みを含め)いささか怪しいところがありますが, SIMS.jsの理解の一助となれば幸いです. なお, 文書作成時のSIMD.jsバージョンは0.9です.

残念ながらBlink系ブラウザでのSIMD.jsサポートは見込み薄のようです. 逆にWebAssemblyにおけるSIMDサポートの動きもあり最終的な方向性が見えません.

残念ながら本仕様の検討は当初よりも大幅に優先度が下げられました. 当面はWebAssemblyにおけるSIMDサポートに注力する模様です.

目次

更新履歴

前提知識

SIMDとSIMD.js

SIMDとは

SIMD(シームディ)とはsingle instruction multi dataの頭文字を取ったもので, 単一の命令を複数のデータに並列に適用する手法を指します. 例えば4つの足し算を行う場合, 通常の方法(SISD:single instruction single data)では1度の足し算を4回繰り返すところを, SIMDを用いると1度の足し算で4つの演算結果が得られます. このようなSIMD手法に則った並列演算をベクトル演算と呼び, 繰り返し制御等が必要ない分高速な動作が期待されます.

SISDとSIMDの比較

今日のパーソナルコンピュータやスマートフォンを構成するCPUはこのベクトル演算為の機能(SSEやNEON)を備えており, 多量のデータ操作を要する場面に於いて上手く活用することでアプリケーションの動作速度が向上します.

SIMD.jsの立ち位置

ベクトル演算を利用できる環境にはc/c++言語や.NET等がありますが, WEB環境においてもSIMD.jsと呼ばれるJavaScript向けのAPIセットが検討されており, 将来的にECMAScript仕様として組み込まれることを目標としています.

なおSIMD.jsはその仕組み上, チップセット毎に異なるSIMD機能を抽象化し, 統一したAPIとしてJavaScriptに公開しています. そのため, コード内容や動作環境によってはSIMD.jsによる高速化の効きやすさが変わります.

SIMD.js仕様の参照先

SIMD.jsの仕様は現在下記において公開されています.

この他MDN等でもSIMD.jsについて解説記事が公開されていますが, 実運用前の技術であるためメンテナンスが滞っています. 概要を確認する上では便利なものの, 最新の内容を反映したものでない点に注意して下さい.

SIMD.jsとJavaScriptを高速化する技術

JavaScriptの歴史はスクリプトの実行速度改善の歴史でもあります. 日々高性能化していくHTML5仕様に伴い, JavaScriptに求められる速度性能は年々厳しいものとなっています. この要求はCPUやメモリバスの性能向上だけでは解決しなかったため, JavaScriptそのものの機能改善という形で結実してきました. 代表的なものを列挙してみましょう.

  1. JavaScriptインタープリタの性能向上
    かつてはインタープリタとして動作していたJavaScriptも, 今日では構文解析技術の向上などによりJITコンパイルを施すことで効率的に動作するようになっています.
  2. 型付き配列の導入
    数値を扱う配列に対しては専用の型付き配列が用意されました. これらは物理メモリを直接参照するように設計されているため, 通常のArrayオブジェクトよりも高速に動作します.
  3. asm.js/WebAssemblyの導入
    数値演算専用のuse asm環境ではJavaScript言語特有の仕組みを極力廃し, より機械語に近い形にコードを解釈(コンパイル)することで動作を高速化します. また, WebAssemblyはコンパイル済みのバイトコードをブラウザ上で動作させるもので, asm.jsにおける事前コンパイル処理を省きます.
  4. SIMD.jsの導入
    CPUレジスタに対応するオブジェクトを用意し, CPUにおけるSIMD演算機能を利用可能とします. 多量のデータを処理する必要がある場合に効果的です.

これらはJavaScriptをよりハードウェアに近い部分で動作させるためのもので, 中でもSIMD.jsはHTML5で強化されたマルチメディア(オーディオ, グラフィック)と言った多量の数値演算を要する用途において切り札となり得ます.

asm.jsとSIMD.jsとの関係

asm.jsとSIMD.jsはバイナリデータ(型付き配列)に対する操作を高速化する点で共通点を持ちますが, そのアプローチが異なります. asm.jsでは数値演算に伴う型判定のコストを排除することでバイナリデータに対する操作そのものを高速化します. そのため, シーケンシャルな配列操作や隣接する値を参照する場合に有効です. 一方のSIMD.jsはバイナリデータに対する操作を並列化することで高速化します. そのため, 配列内の個々のデータに共通の操作を行う場合に効果的です.

高速化アプローチの比較

このようにasm.jsとSIMD.jsとは相補的な関係にあり, 組み合わせて利用することで効果が最大化します. そのため, SIMD.jsをasm.jsから呼び出すことも考えられています.

SIMD.jsの利用

SIMD.jsの適用場面

SIMD.jsの主眼は計算処理の並列化にあるため, 同じような処理を複数のデータに適用する(Array.forEachで記述できる)ケースに効果的と言えます. 具体的には次のようなものが挙げられます.

SIMD.jsでは最大16データを同時に処理できるため, 直感的には演算速度は最大16倍となるはずです. が, SIMDの実行にはデータ複写等の処理を要するため実際の速度の向上度合いは控えめになります.

また複雑な条件分岐を要するケースや隣接するデータを参照するようなものについては, 強引なSIMD.js適用に伴う副作用により目立った効果が得られないこともあります. また, 上記ケースであってもSIMD.jsの利用に誤りがあれば効果はありません.

SIMD.jsのための準備

SIMD.jsは将来ECMA Script仕様に組み込まれる前提であるため, 利用に際しては特に準備は必要ありません. ですが, 2017年現在では未だ実験的な意味合いが濃く, SIMD.jsを利用可能な環境は極限られています.

SIMD.jsを使うべきか

現状動作する環境や適応可能なユースケースが限られているとは言え, 適切なSIMD.js導入によるパフォーマンスの改善効果は絶大です. そのためSIMD.jsは手作業でのスクリプト構築に於いても大いに魅力的です. APIについても見た目ほど難しいものではないため, 内容の理解そのものは比較的容易です.

しかし繰り返し処理のベクトル演算化は, 本来コンパイラなどの機械的な最適化処理が得意とするところであり, 本機能もemscripten等による他言語から変換されたJavaScript(asm.js/WebAssembly)コード向けに設計されていることは明らかです.

そのため現時点でSIMD.jsを採用するとなると, 並列処理に対する発想の転換(専用のアルゴリズムの導入)が必要である上, 既存のコードに対しても「ハンドアセンブル」に近い作業を強いられます. この点でSIMD.jsはasm.jsによく似ており, とりわけノウハウの蓄積に乏しい現状では作業効率面で大きな苦痛を伴います.

従って, 盲目的に採用するよりはもう少し技術的な成熟を待ち, 何らかのフレームワークやラッパーライブラリの登場を待つのが上策と考えられます.

SIMD.js概観

SIMD.jsの全体像

SIMD.jsに関わるオブジェクトは全てSIMDオブジェクト(名前空間)に登録されています. 以下にその構成をまとめます.

SIMD.jsの構成図

SIMD.jsのサポート判定

WEBブラウザにおけるSIMD.jsのサポート有無を判定するには, self.SIMDの内容を確認します.

SIMD.jsのポリフィルライブラリ

SIMD.jsに対するポリフィルライブラリecmascript_simdが公開されています. 但しネイティブのSIMD.jsと異なり, SIMDによる演算結果をSISD(通常のループ処理)で再現しているだけであり, 処理速度の向上と言った効果は得られません. なお, 次のコードでSIMD.jsがポリフィルによるものかどうかを判定出来ます.

SIMD型

SIMD型の内部構造

名前空間SIMDにはSIMD.jsが取り扱うデータ型オブジェクトが定義されており, これらを総じてSIMD型と呼びます. SIMD型はCPUレジスタに対応する概念で, いずれも内部に128bitのデータを保持しています. この128bitを(均等に)分割したものをレーンと呼び, SIMD型に含まれるレーンは全て同じ数値型であるものとして扱います. なお, SIMD型の各レーンでは表面上little endianの順にbyte値が管理されています.

SIMD型の内部構造

このようにSIMD型は「サイズ固定の型付き配列」によく似た構造をしています. が, 型付き配列と異なりSIMD型そのものはイミュータブルであり中身を操作するための機能を一切持ちません.

SIMD型の分類

128bitレジスタをレーンに区切る際, その区切り方(レーン数)と各レーンの取り扱い(データ型)によって異なるSIMD型が定義されています. まとめると次のように分類出来ます.

SIMD型の分類
SIMD型データ型bit数レーン数値範囲(概要)
SIMD.Float64x2浮動小数点数型64bit2-(253-1) 〜 253-1
SIMD.Float32x432bit4-(224-1) 〜 224-1
SIMD.Int32x4整数型32bit4-2,147,483,648 ~ 2,147,483,647
SIMD.Int16x816bit8-32,768 〜 32,727
SIMD.Int8x168bit16-128 〜 127
SIMD.Uint32x4非負整数型32bit40 ~ 4,294,967,295
SIMD.Uint16x816bit80 〜 65,535
SIMD.Uint8x168bit160 〜 255
SIMD.Bool64x2真偽値型(64bit)2true/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%型内部に保持できない値が渡された場合, 次のように動作します.

補足)特殊な値のbyte列表現

InfinityNaN等の特殊な値は特定のbyte列として表されます. SIMD.Float32x4型オブジェクトの内容をbyte(bit列)値として扱う場合に利用します.

特殊な値のbyte列表現
特殊値byte配列備考
00x00000000
-00x80000000
Infinity0x7f800000
-Infinity0xff800000
NaN0bs11111111xxxxxxxxxxxxxxxxxxxxxxsと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操作関数を呼び出す必要があります. 下記にデータ操作に関わるものを示します.

データ操作関数の分類
値の設定値の取得
単一値replaceLaneextractLane
複数値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浮動小数点数型64bit2Float64Array
SIMD.Float32x432bit4Float32Array
SIMD.Int32x4整数型32bit4Int32Array
SIMD.Int16x816bit8Int16Array
SIMD.Int8x168bit16Int8Array
SIMD.Uint32x4非負整数型32bit4Uint32Array
SIMD.Uint16x816bit8Uint16Array
SIMD.Uint8x168bit16Uint8Array, UintClampedArray
型付き配列とのデータ授受

さもないと元の値のbit列が転写に伴い異なる値と解釈されてしまいます.

型付き配列とのデータ授受(データ型が異なるケース)

なお真偽値型については対応する型付き配列が存在しません.

エンディアンの取り扱い

SIMD.jsによるArrayBuffer(システムメモリ)の読み込み・書き込みはシステムのエンディアンを基準に行われます. そのため意図的に値の再解釈を引き起こさせる場合は, 厳密にはこのエンディアンを加味したコードを記述する必要があります. さもないと(稀に)型付き配列の内容が正しいものとなりません.

エンディアン毎に異なる整数解釈
8bitInt列としての値
32bitIntとしての値
big endianlittle endian
1a2b3c4d
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メソッドと組み合わせることで任意のレーンを処理対象とすることが出来ます.

SIMD型の合成

新たなSIMD型の生成

単独もしくは2つの同じ型のSIMD型が与えられた際に, 何らかの条件のもとで新たなSIMD型を生成することが出来ます. SIMD.jsを用いる上で極めて重要な機能であり, 他のメソッドと組み合わせることで多彩な処理を行えます.

以下にこのSIMD型の合成を行う関数を示します. なおこれらは数値型に対してのみ有効です.

SIMD型を合成する関数
関数名操作内容
SIMD.%type%.selectレーン毎の選択条件を元にSIMD型を生成する.
SIMD.%type%.swizzle指定したインデックスの下でレーンの並び順を変更する.
SIMD.%type%.shuffle指定したインデックスの下で2つのSIMD型を混合する.

selectメソッド

SIMD.%type%.selectメソッドは2つのSIMD型に選択条件にあたる真偽値型を与え, レーン毎に参照する値を選択することで新たなSIMD型を生成します. なお, 与える真偽値型は, 元のSIMD型のレーン数と一致させる必要があります. このメソッドを用いると後述する条件判定関数と組み合わせて分岐処理を一括で行うことが可能となります.

selectメソッドによるレーン値の選択
SIMD.%type% SIMD.%type%.select(condition, a, b)
真偽値型conditionの内容を元にレーン毎にa, bのどちらのSIMD型の値を採用するかを判定し, 新たなSIMD型を生成します. 真偽値型には元のSIMD型とレーン数が一致したものを与えます. 真偽値型の値がtrueの場合はaのレーンが falseの場合はbのレーンが選択されます.

例を示します.

swizzleメソッド

SIMD.%type%.swizzleメソッドはSIMD型のレーン並びを変更し, 新たなSIMD型を生成します.

swizzleメソッドによるレーン値の並べ替え
SIMD.%type% SIMD.%type%.swizzle(a, s0〜s16)
SIMD型のレーンをs0〜s16に指定したインデックス値(0〜レーン数-1)で並び替え, 新たなSIMD型を生成します. なお, インデックスパラメータの数がレーン数に満たない場合やインデックス値に対応するレーンが存在しない場合はエラーとなります.

例を示します.

インデックス値は重複することが出来ます.

このメソッドは何れかのレーンの値から新たなSIMD型を生成する際に利用します.

shuffleメソッド

SIMD.%type%.shuffleメソッドは2つのSIMD型のレーンを混合し, 新たなSIMD型を生成します.

shuffleメソッドによるレーン値の混合
SIMD.%type% SIMD.%type%.shuffle(a, b, s0〜s16)
2つのSIMD型のレーンをs0〜s16に指定したインデックス値(0〜レーン数×2-1)で並び替え, 新たなSIMD型を生成します. なお, インデックスパラメータの数がレーン数に満たない場合やインデックス値に対応するレーンが存在しない場合はエラーとなります.

例を示します.

インデックス値は重複することが出来ます.

SIMD型に対する型の変換

型変換の種類

SIMD型に対する型変換には大きく分けて次の2種類が存在します.

以下に型変換関数の一覧を示します.

SIMD型の変換対応
変換種メソッド名
(%type%に変換先の型が入る)
入力型(変換元)対応する型(変換先)
FlIntUint
323216832168
SIMD.%type%.fromFloat32x4 SIMD.Float32x4××××
SIMD.%type%.fromInt32x4 SIMD.Int32x4 ××××
SIMD.%type%.fromInt16x8 SIMD.Int16x8 ×××××
SIMD.%type%.fromInt8x16 SIMD.Int8x16 ×××××
SIMD.%type%.fromUint32x4 SIMD.Uint32x4 ××××
SIMD.%type%.fromUint16x8 SIMD.Uint16x8 ×××××
SIMD.%type%.fromUint8x16 SIMD.Uint8x16 ×××××
bitSIMD.%type%.fromFloat32x4BitsSIMD.Float32x4
SIMD.%type%.fromInt32x4Bits SIMD.Int32x4
SIMD.%type%.fromInt16x8Bits SIMD.Int16x8
SIMD.%type%.fromInt8x16Bits SIMD.Int8x16
SIMD.%type%.fromUint32x4Bits SIMD.Uint32x4
SIMD.%type%.fromUint16x8Bits SIMD.Uint16x8
SIMD.%type%.fromUint8x16Bits SIMD.Uint8x16

等値変換

同じbit型のSIMD型については各レーンの値を維持しつつ別のSIMD型に変換出来ます.

等値変換可能な型
SIMD.%type% SIMD.%type%.fromFloat32x4(a)
SIMD.Float32x4型のデータを元に同じ値を持つSIMD型を生成します.
SIMD.%type% SIMD.%type%.fromInt32x4(a)
SIMD.Int32x4型のデータを元に同じ値を持つSIMD型を生成します.
SIMD.%type% SIMD.%type%.fromInt16x8(a)
SIMD.Int16x8型のデータを元に同じ値を持つSIMD.Uint16x8型を生成します.
SIMD.%type% SIMD.%type%.fromInt8x16(a)
SIMD.Int8x16型のデータを元に同じ値を持つSIMD.Uint8x16型を生成します.
SIMD.%type% SIMD.%type%.fromUint32x4(a)
SIMD.Uint32x4型のデータを元に同じ値を持つSIMD型を生成します.
SIMD.%type% SIMD.%type%.fromUint16x8(a)
SIMD.Uint16x8型のデータを元に同じ値を持つSIMD.Int16x8型を生成します.
SIMD.%type% SIMD.%type%.fromUint8x16(a)
SIMD.Uint8x16型のデータを元に同じ値を持つSIMD.Int8x16型を生成します.

以下に例を示します.

型変換に伴うルール

型変換に際し, 次のルールが定められています.

以下にエラーとなる例を示します.

等bit変換

SIMD型は等bit変換を行うことで別のSIMD型に変換出来ます.

SIMD.%type% SIMD.%type%.fromFloat32x4Bits(a)
SIMD.Float32x4型のデータを元に同じbit並びを持つSIMD型を生成します.
SIMD.%type% SIMD.%type%.fromInt32x4Bits(a)
SIMD.Int32x4型のデータを元に同じbit並びを持つSIMD型を生成します.
SIMD.%type% SIMD.%type%.fromInt16x8Bits(a)
SIMD.Int16x8型のデータを元に同じbit並びを持つSIMD型を生成します.
SIMD.%type% SIMD.%type%.fromInt8x16Bits(a)
SIMD.Int8x16型のデータを元に同じbit並びを持つSIMD型を生成します.
SIMD.%type% SIMD.%type%.fromUint32x4Bits(a)
SIMD.Uint32x4型のデータを元に同じbit並びを持つSIMD型を生成します.
SIMD.%type% SIMD.%type%.fromUint16x8Bits(a)
SIMD.Float16x8型のデータを元に同じbit並びを持つSIMD型を生成します.
SIMD.%type% SIMD.%type%.fromUint8x16Bits(a)
SIMD.Uint8x16型のデータを元に同じbit並びを持つSIMD型を生成します.

等bit変換ではSIMD型内部の128bitレジスタ値を維持するように動作するため, 変換前と変換後とでは必ずしも同じ値とはなりません. なお, SIMD型は内部でsmall endianの順にbyte値を格納しているため, 32bit型を16bit型や8bit型に変換した場合は, 上位レーンに元データの上位byte値が設定されます.

等bit変換による値の変化

例を示します.

それ以外の型変換

SIMD.js標準の型変換処理で対応出来ないものについては, 型変換関数と別の関数とを組み合わせて対処します.

8/16bit整数型から16/32bit整数型への変換

整数型の桁数を増やすにはlessThan,select,shuffle関数を用いて元となるSIMD型を変形し, 等bit変換を行います. なお何れのケースに於いても変換の過程で128bitレジスタの範囲から桁が溢れるため, 必要に応じてSIMD型を複数に分解することになります.

非負整数型の場合

まずshuffleメソッドを用いてレーン間に0を挿入した後, 等bit変換します.

非負整数型のアップキャスト

整数型の場合

整数型の場合は元の値が負値かどうかでレーン間に挿入する値が0もしくは-1となります.

整数型のアップキャスト

8/16bit整数型からFloat32x4への変換

8/16bit型をFloat32x4型に変換するには, 上記に示した方法で一旦32bit型に変換した後Float32x4型へ等値変換します.

SIMD型に対する演算

算術関数の適用

SIMD操作関数には数値演算のための必要最小限度の関数が含まれています. 以下にその一覧を示します.

SIMD算術関数の一覧
分類関数名演算内容対応する型
FloatIntUintBool
64323216832168
四則演算SIMD.%type%.add加算
SIMD.%type%.addSaturate飽和加算
SIMD.%type%.sub減算
SIMD.%type%.subSaturate飽和減算
SIMD.%type%.mul乗算
SIMD.%type%.div除算
SIMD.%type%.reciprocalApproximation逆数
SIMD.%type%.neg符号の反転
数学関数SIMD.%type%.abs絶対値(Math.abs)
SIMD.%type%.max最大値(Math.max)
SIMD.%type%.min最小値(Math.min)
SIMD.%type%.maxNum非数を除いた最大値
SIMD.%type%.minNum非数を除いた最小値
SIMD.%type%.sqrt平方根(Math.sqrt)
SIMD.%type%.reciprocalSqrtApproximation平方根の逆数
ビット演算SIMD.%type%.not論理否定(!)
SIMD.%type%.and論理積(&)
SIMD.%type%.or論理和(|)
SIMD.%type%.xor排他的論理和(^)
SIMD.%type%.shiftLeftByScalar左ビットシフト(<<)
SIMD.%type%.shiftRightByScalar右ビットシフト(>>/>>>)
†32bit整数に対する飽和演算をサポートする環境が存在しないため仕様から省かれています.

これらは次のような特徴を持ちます.

通常の算術演算子やMath関数等を使えないため, 複雑な計算を行うと記述が冗長になりがちな点に注意して下さい.

NaN値の取り扱い

SIMD型にNaN値が含まれていた場合, 通常の演算と同様に結果はNaN値になります.

四則演算

基本的な数値計算を行う関数群です.

加算

SIMD.%type%.addメソッドは2つのSIMD型に対してレーン毎の足し算を行います.

SIMD.%type% SIMD.%type%.add(a, b)
指定したSIMD型オブジェクトの内容を加算し, 新たなSIMD型を返します.

減算

SIMD.%type%.subメソッドは2つのSIMD型に対してレーン毎の引き算を行います.

SIMD.%type% SIMD.%type%.sub(a, b)
指定したSIMD型オブジェクトの内容を減算し, 新たなSIMD型を返します.

飽和演算

(非負)整数型に対してadd,subメソッドを実行した場合, データ型が格納可能な値の範囲を超過(桁溢れ)すると演算結果が循環します. 例えば非負整数型では0-1の演算結果はその型の最大値となります. それに対し, SIMD.%type%.addSaturate/subSaturateメソッドを用いると演算結果がその数値型の取りうる値の範囲に切り詰められます. これを飽和演算と呼びます.

演算結果の比較
計算式
8bit非負整数条件下
演算結果
通常の演算飽和演算
255+1
(0xff+0x01)
0
(0x00)
255
(0xff)
0-1
(0x00-0x01)
255
(0xff)
0
(0x00)
SIMD.%type% SIMD.%type%.addSaturate(a, b)
指定したSIMD型オブジェクトの内容について飽和加算し, 新たなSIMD型を返します.
SIMD.%type% SIMD.%type%.subSaturate(a, b)
指定したSIMD型オブジェクトの内容について飽和減算し, 新たなSIMD型を返します.

例を示します.

なお飽和演算はSIMD.Int32x4, SIMD.Uint32x4型には定義されていません. なぜなら現行のCPUにおいてこれらの型に対する飽和演算をサポートするものが存在しないからです.

乗算

SIMD.%type%.mulメソッドは2つのSIMD型に対してレーン毎の掛け算を行います.

SIMD.%type% SIMD.%type%.mul(a, b)
指定したSIMD型オブジェクトの内容を乗算し, 新たなSIMD型を返します.

浮動小数点値の掛け算についてはMath.froundと同等の丸め処理が為されます. 整数値の掛け算についてはMath.imulと同等の整数乗算が適用されます. そのため, 掛け算の結果が桁溢れをした際は下位bitのみが残ります.

除算

SIMD.%type%.divメソッドは2つのSIMD型に対してレーン毎の割り算を行います. なおこのメソッドは浮動小数点数型でのみ定義されています.

SIMD.%type% SIMD.%type%.div(a, b)
指定したSIMD型オブジェクトの内容で除算し, 新たなSIMD型を返します.

0,Infinityで除算した場合, 結果は0,Infinity,NaNの何れかになります.

逆数

SIMD.%type%.reciprocalApproximationメソッドはSIMD型に対してレーン毎の逆数(1/x)を算出します. 0,Infinityに対する逆数はInfinity, 0となります. なおこのメソッドは浮動小数点数型でのみ定義されています.

SIMD.%type% SIMD.%type%.reciprocalApproximation(a)
レーン毎の逆数を算出し, その結果から新たなSIMD型を返します.

符号の反転

SIMD.%type%.negメソッドはSIMD型に対してレーン毎の符号を反転します. なおこのメソッドは非負整数型には定義されていません.

SIMD.%type% SIMD.%type%.neg(a)
レーン毎の符号を反転し, その結果から新たなSIMD型を返します.

数学関数

下記に示す関数はいずれも浮動小数点型にのみ適用可能です.

絶対値

SIMD.%type%.absメソッドはSIMD型に対してレーン毎の絶対値を算出します.

SIMD.%type% SIMD.%type%.abs(a)
レーン毎の絶対値を算出し, その結果から新たなSIMD型を返します.

最大値と最小値

SIMD.%type%.maxメソッドとSIMD.%type%.minメソッドは2つのSIMD型オブジェクトをレーン毎に比較し, 値の大きい/小さい方を算出します.

SIMD.%type% SIMD.%type%.max(a, b)
レーン毎の最大値を算出し, その結果から新たなSIMD型を返します.
SIMD.%type% SIMD.%type%.min(a, b)
レーン毎の最小値を算出し, その結果から新たなSIMD型を返します.

なお, 比較対象の値にNaN値が含まれていると, 比較結果はNaN値となります. これは数値比較が出来なかったことを表します.

NaN値の除外

SIMD型にNaN含まれていた場合, 論理的には数値比較の対象と出来ません. NaN値を比較対象から除くにはメソッドSIMD.%type%.maxNum / SIMD.%type%.minNumを用います.

SIMD.%type% SIMD.%type%.maxNum(a, b)
レーン毎の最大値を算出し, その結果から新たなSIMD型を返します. その際にNaN値は無視します.
SIMD.%type% SIMD.%type%.minNum(a, b)
レーン毎の最小値を算出し, その結果から新たなSIMD型を返します. その際にNaN値は無視します.

平方根とその逆数

SIMD.%type%.sqrtメソッドはSIMD型の各レーンに対して平方根を計算します. 同様にSIMD.%type%.reciprocalSqrtApproximationメソッドは平方根の逆数を計算します.

SIMD.%type% SIMD.%type%.sqrt(a)
レーン毎の平方根を算出し, その結果から新たなSIMD型を返します.
SIMD.%type% SIMD.%type%.reciprocalSqrtApproximation(a)
レーン毎の平方根の逆数を算出し, その結果から新たなSIMD型を返します.

これらはベクトルの長さを求める場合に用います. 例えばベクトル\(v = (a, b, c, d)\)に対し, ベクトルの長さ\(\|v\|\)は\(\sqrt{a^2 + b^2 + c^2 + d^2}\)で表されます.

ビット演算

下記に示すメソッドは何れも値をbit列として扱うもので, (非負)整数型と真偽値型を演算対象とします.

論理否定

SIMD.%type%.notメソッドはSIMD型の各レーンに対して論理否定演算を施します. bit毎に値を反転するため, 整数型の値は必ずしも直感的なものとはなりません.

SIMD.%type% SIMD.%type%.not(a)
レーン毎に論理否定値を算出し, その結果から新たなSIMD型を返します.

論理和・論理積・排他的論理和

SIMD.%type%.or, SIMD.%type%.and, SIMD.%type%.xorメソッドはSIMD型の各レーンに対してそれぞれ論理和・論理積・排他的論理和演算を施します. bit毎に値を求めるため, 整数型の値は必ずしも直感的なものとはなりません.

SIMD.%type% SIMD.%type%.or(a)
レーン毎に論理和値を算出し, その結果から新たなSIMD型を返します.
SIMD.%type% SIMD.%type%.and(a)
レーン毎に論理積値を算出し, その結果から新たなSIMD型を返します.
SIMD.%type% SIMD.%type%.xor(a)
レーン毎に排他的論理値を算出し, その結果から新たなSIMD型を返します.

ビットシフト

(非負)整数型についてはSIMD.%type%.shiftLeftByScalarとSIMD.%type%.shiftRightByScalarメソッドを使ってビットシフト演算が行えます. なお, 右ビットシフト演算は対象とするSIMD型によって基本動作が異なります.

SIMD.%type% SIMD.%type%.shiftLeftByScalar(a, bits)
レーン毎にbitsの分だけ左シフトした値を求め, その結果から新たなSIMD型を返します.
SIMD.%type% SIMD.%type%.shiftRightByScalar(a, bits)
レーン毎にbitsの分だけ右シフトした値を求め, その結果から新たなSIMD型を返します. なお, 処理対象のSIMD型によりシフト方法が異なります.
  • 整数値型の場合, 符号を維持した右シフトを行います. これは演算子「>>」で得られる結果と等価です.
  • 非負整数型の場合, 0埋め右シフトを行います. これは演算子「>>>」で得られる結果と等価です.

レーンを跨いだ演算

合算等のSIMD型のレーンを跨いだ演算を行う場合, swizzleメソッドを用いてレーンを交換したものを逐次適用するようにします. こうすると, レーン毎に計算するよりも少ない演算回数で結果が得られます.

レーンを跨いだ演算

例を示します.

なお, この操作を適用できるメソッドは交換可能である必要があります. つまり加算(\(a+b=b+a\))等のようにパラメータの順番を交換しても得られる結果が等しくなければなりません. この条件を充たすものとしては, add, mul, or, and, xor, max, min等があります.

SIMD型と条件判定

2段階の条件判定と真偽値型

SIMD型は内部に複数の値を保持していることから, SIMD型に対する条件判定には次のような2段階の処理が発生します.

  1. レーン毎の条件判定
    何らかの条件(大小比較等)をレーン毎に適用し, その成否を判定するものです. この操作によりレーン数に対応した真偽値のリストが得られます.
  2. SIMD型全体での条件判定
    上記で得られた真偽値のリストを元にSIMG型が条件を充たすかどうかを判定します.
SIMD型に対する条件判定

この用途のため, SIMD型には論理演算専用の真偽値型(SIMD.Bool64x2, SIMD.Bool32x4, SIMD.Bool16x8, SIMD.Bool8x16)が用意されています. 本質的にレーン数が重要なので, 名称上32や16等の(bit)値が含まれていますが特に意味はありません.

真偽値型を生成する関数

レーン毎に条件判定を行う関数には次のものがあり, いずれも判定結果として真偽値型を生成します. なおここで得られた真偽値型はSIMD.%type%.select関数と組み合わせて, レーン毎に異なる値を取得するために利用します.

真偽値型を生成する条件判定関数
関数名関数の内容
SIMD.%type%.equal等しい(==)
SIMD.%type%.notEqual等しくない(!=)
SIMD.%type%.greaterThanより大きい(>)
SIMD.%type%.greaterThanOrEqual以上(>=)
SIMD.%type%.lessThanより小さい(<)
SIMD.%type%.lessThanOrEqual以下(<=)

これらの関数は比較対象のSIMD型が持つレーン数に応じた真偽値型を生成します.

真偽値型を生成する条件判定関数
SIMD型得られる真偽値型
SIMD.Float64x2SIMD.Bool64x2
SIMD.Float32x4SIMD.Bool32x4
SIMD.Int32x4
SIMD.Uint32x4
SIMD.Int16x8 SIMD.Bool16x8
SIMD.Uint16x8
SIMD.Int8x16 SIMD.Bool8x16
SIMD.Uint8x16

以下に例を示します.

equalメソッド

SIMD.%type% SIMD.%type%.equal(a, b)
aとbとをレーン毎に比較し, その結果から真偽値型を返します. 値が等しい場合にtrueと判定します.

notEqualメソッド

SIMD.%type% SIMD.%type%.notEqual(a, b)
aとbとをレーン毎に比較し, その結果から真偽値型を返します. 値が等しくない場合にtrueと判定します.

greaterThanメソッド

SIMD.%type% SIMD.%type%.greaterThan(a, b)
aとbとをレーン毎に比較し, その結果から真偽値型を返します. 前者の値が後者よりも大きい場合にtrueと判定します.

greaterThanOrEqualメソッド

SIMD.%type% SIMD.%type%.greaterThanOrEqual(a, b)
aとbとをレーン毎に比較し, その結果から真偽値型を返します. 前者の値が後者以上の場合にtrueと判定します.

lessThanメソッド

SIMD.%type% SIMD.%type%.lessThan(a, b)
aとbとをレーン毎に比較し, その結果から真偽値型を返します. 前者の値が後者よりも小さい場合にtrueと判定します.

lessThanOrEqualメソッド

SIMD.%type% SIMD.%type%.lessThanOrEqual(a, b)
aとbとをレーン毎に比較し, その結果から真偽値型を返します. 前者の値が後者以下の場合にtrueと判定します.

NaN値の比較

Float32x4型においてNaN値を含む値の比較は常に一定の値となります. これはNaN値が数値としての確定した値を持たないため, 論理的には値の比較が出来なかったことを表します.

NaN値を含む値の比較結果
メソッド比較結果
notEqualtrue
notEqual以外false

NaN値の判定

レーンの値がNaNであることの判定にはFloat32x4型をUint32x4型に等bit変換し, ビット列の内容を比較することで行います. 但しNaN値は特定のビット列表現を持たないため, 先頭2〜9bitの位が全て1かどうかで判定します.

真偽値型に対する判定関数

条件判定関数から得た真偽値型を元に全体としての判定を行う関数には次のものがあります

真偽値型に対する判定関数
関数名関数の内容
SIMD.%type%.allTrueレーンの値が全てtrueであるかを判定する.
SIMD.%type%.anyTrueレーンの値にtrueが含まれているかを判定する.

これらのメソッドは数値SIMD型には適用できません.

以下に例を示します.

allTrueメソッド

boolean SIMD.%type%.allTrue(a)
(真偽値型の)レーンの値が全てtrueかどうかを判定します.

anyTrueメソッド

boolean SIMD.%type%.anyTrue(a)
(真偽値型の)レーンの値にtrueが含まれているかどうかを判定します.

条件に合致したレーン数の算出

条件判定の結果, trueと判定されたレーンの数を求めるには, selectメソッドとswizzleメソッドを組み合わせて利用します. レーン毎の真偽値を比較するよりも計算回数が少なくて済みます.

真偽値型によるレーン別の演算適用

条件判定関数で得られた真偽値型をSIMD.%type%.select関数に渡すことで2つのSIMD型を混合することが可能です. そのため, select関数で得たSIMD型を算術関数に渡すことでレーン毎に異なる値を適用することが可能です.

例えば次のようなコードがあったとします.

これをSIMD.jsを使って書き換えると次のようになります.

元のコードにあったループ処理やif文による分岐処理がSIMD.jsにより全て一度の演算に置き換えられています. その結果, 全体としての計算回数が圧縮されるためスクリプトの動作が高速化されるのです.

SIMD.jsの活用

主なユースケースの分類

SIMD.jsの利用が効果的なケースは主に次の2つに分類できます.

行列・ベクトル演算への利用

行列やベクトルに対する操作は成分毎に似た操作を行うことが多いため, 比較的容易にSIMD.jsで記述できます. グラフィック処理等の一度に多量の座標値を処理する場合に効果的です.

行列計算への応用

行列演算は行・列単位で同じ演算を行うため, 処理の並列化が容易です. そのためSIMD.jsを適用しやすい分野と言え, 3Dグラフィックを描く際に必要となる4次行列は4つのSIMD.Float32x4で表現出来ます.

例えばベクトル\(v=(x, y, z, w)\)を行列\(M\)で\(u=(x',y',z',w')\)に変換する場合, 次の式で表されます.

\begin{matrix} u & = & \begin{pmatrix} x' \\ y' \\ z' \\ w' \end{pmatrix}& = & \begin{pmatrix} m_{11} & m_{21} & m_{31} & m_{41}\\ m_{12} & m_{22} & m_{32} & m_{42}\\ m_{13} & m_{23} & m_{33} & m_{43}\\ m_{14} & m_{24} & m_{34} & m_{44} \end{pmatrix} \begin{pmatrix} x \\ y \\ z \\ w \end{pmatrix} & = & Mv \end{matrix}

これをSIMD.jsに適合するようにベクトル値のスカラー倍と足し算で書き換えると次のようになります.

\begin{matrix} \begin{pmatrix} m_{11} & m_{21} & m_{31} & m_{41}\\ m_{12} & m_{22} & m_{32} & m_{42}\\ m_{13} & m_{23} & m_{33} & m_{43}\\ m_{14} & m_{24} & m_{34} & m_{44} \end{pmatrix} \begin{pmatrix} x \\ y \\ z \\ w \end{pmatrix} & = & x \begin{pmatrix} m_{11} \\ m_{12} \\ m_{13} \\ m_{14} \end{pmatrix} + y \begin{pmatrix} m_{21} \\ m_{22} \\ m_{23} \\ m_{24} \end{pmatrix} + z \begin{pmatrix} m_{31} \\ m_{32} \\ m_{33} \\ m_{34} \end{pmatrix} + w \begin{pmatrix} m_{41} \\ m_{42} \\ m_{43} \\ m_{44} \end{pmatrix} \end{matrix}

この関係式を元にベクトル変換を実装すると次のようになります.

通常, 行と列方向の数え上げが発生するところSIMD.jsを用いることで列方向の数え上げだけで処理が完了しています.

アフィン変換の並列化

先ほどの例に似たものとして2次元アフィン変換が挙げられ, SIMD.Float32x4型を用いると一度に2座標に対して座標変換を適用できます. 点\((x, y)\)を\((x', y')\)に写す2次元アフィン変換はパラメータ\((a, b, c, d, e, f)\)を用いて次のように表せます.

\begin{cases} x'&=& ax &+& cy &+& e \\ y'&=& bx &+& dy &+& f \end{cases}

これを縦に2つ並べるとSIMD型で表すことが可能となります.

\begin{cases} x'_1 &=& ax_1 &+& cy_1 &+& e \\ y'_1 &=& bx_1 &+& dy_1 &+& f \\ x'_2 &=& ax_2 &+& cy_2 &+& e \\ y'_2 &=& bx_2 &+& dy_2 &+& f \end{cases}

複素数・四元数計算への応用

複素数・四元数(クォータニオン)はベクトルとして表現出来るため, SIMD.jsで扱うことが可能です. 下記は複素数に対する四則演算を表した式です. 通常真ん中の関係式を用いて演算しますが, SIMD型を使うと実部と虚部を同時に計算出来る点に注意します.

\begin{eqnarray*} 加算) \hspace{20px}& (a+bi)+(c+di) &=& (a+c)+(b+d)i &=& \begin{pmatrix}a\\b\end{pmatrix}+\begin{pmatrix}c\\d\end{pmatrix}\\ 減算) \hspace{20px}& (a+bi)-(c+di) &=& (a-c)+(b-d)i &=& \begin{pmatrix}a\\b\end{pmatrix}-\begin{pmatrix}c\\d\end{pmatrix}\\ 乗算) \hspace{20px}& (a+bi)\times(c+di) &=& (ac-bd)+(bc+ad)i &=& \begin{pmatrix}a\\b\end{pmatrix}\times\begin{pmatrix}c\\c\end{pmatrix} + \begin{pmatrix}-b\\a\end{pmatrix}\times\begin{pmatrix}d\\d\end{pmatrix} \\ 除算) \hspace{20px}& (a+bi)\div(c+di) &=& \cfrac{ac+bd}{c^2+d^2}+\cfrac{bc-ad}{c^2+d^2}i &=& \left(\begin{pmatrix}a\\b\end{pmatrix}\times\begin{pmatrix}c\\c\end{pmatrix} + \begin{pmatrix}b\\-a\end{pmatrix}\times\begin{pmatrix}d\\d\end{pmatrix}\right)\div\begin{pmatrix}c^2+d^2\\c^2+d^2\end{pmatrix} \\ \end{eqnarray*}

これをSIMD.jsコードで記述すると次のようになります.

配列処理の並列化

巨大な配列に対する操作はSIMD.jsを適用することで容易に高速化が可能です. 但し, 計算結果の内容によっては並列処理専用のアルゴリズムを発案せねばならず, 必ずしも単純なコード書き換えで済むわけではありません.

配列走査とサイズの調整

SIMD.jsを利用した配列処理の効率化には, loadメソッドによるデータ読み込み(SIMD型オブジェクトの生成)が効果的です. が, SIMD型で逐次データを読み込んでいくと配列末尾に4,8,16要素(128bit長)に満たない端数が生まれるため, この部分にどう対処するかが課題となります. 以下, 配列値の合算を例にその解決策について見てみましょう. なお, 同様の処理で平均値, 分散値の算出や配列値の検索が行えます.

処理対象がArrayオブジェクトの場合

通常の配列に対してSIMD.jsを適用する場合, 配列の内容を型付き配列に複写してから処理を開始します. その際に128bitの倍数となるように配列のサイズを調整しておきます.

なお上記ではforループ構文における終了条件を指定していません. なぜなら, 型付き配列からSIMD型を生成する際にloadメソッドがエラーを発することで配列読み込みの終了が判るからです. こうすると, 条件判定に伴う演算を省くことが出来ます.

処理対象が型付き配列の場合

計算対象が既に型付き配列だった場合は, 上記のloadメソッドがエラーを発したタイミングで別途端数となった数値群を読み込みます.

なお同じ処理を2箇所に記述するのは無駄ですから, ループ処理部から計算処理を別関数として切り出しておくと良いでしょう.

canvas要素を用いた画像処理への適用

canvas要素に描いた内容はピクセル情報の配列(Uint8ClampedArray)として取り出すことが出来ます. ピクセル毎に処理を行う用途, 例えば色調補正等であればSIMD.jsを適用できる可能性があります. 以下に例を示します.

ここでは4ピクセル毎に処理を行っています. こうすることで一般的な1ピクセル毎に処理する方法よりも理論上4倍の速度で動作します.

SIMD.jsを使いこなすコツ

以下, 実際のSIMD.jsコードを記述するうえで筆者が気付いた点についてまとめます.