やったこと
SIMD.jsでどのくらい高速化できるのか、簡単な画像処理で試してみました。
やることは単純で、Canvasから取得したRGBAをYUVに変換した後にガンマ補正を掛け、YUVからRGBAに戻すだけです。ガンマ補正はLUTを当てるだけでSIMDに乗せられないので、RGBA/YUV変換がSIMD化の対象となりますが、とりあえずRGBAからYUVへの変換だけSIMD化しました。
Windows 10 + Firefox Nightly 47.0a1で動作確認しています。Edgeでも設定を変えるとSIMDが使えるのですが、ビットシフトに対応していないようなので動かないです。
ソース: https://github.com/arenahito/simdjs-example
デモ: http://arenahito.github.io/simdjs-example/
SIMD化前
まずは、SIMDなしで作ります。
RGBAからYUVに変換するだけですが、YUVを加工しやすいようにPlaner形式としています。RGBA→RGBA→……からYYY……→UUU……→VVV……となります。
あと、Alphaはいらないので捨てています。YUVからRGBAに戻すときは固定値を設定する感じで。
function convertRgbaToYuv(rgba, width, height, output) {
var imageSize = width * height;
for (var y = 0; y < height; y++) {
var rgbaOffsetY = y * width * 4;
var yuvOffsetBaseY = y * width;
for (var x = 0; x < width; x++) {
var rgbaOffset = rgbaOffsetY + x * 4;
var yuvOffsetBase = yuvOffsetBaseY + x;
var r = rgba[rgbaOffset + 0];
var g = rgba[rgbaOffset + 1];
var b = rgba[rgbaOffset + 2];
output[yuvOffsetBase + imageSize * 0] = (306 * r + 601 * g + 117 * b) >> 10;
output[yuvOffsetBase + imageSize * 1] = (-173 * r - 339 * g + 512 * b + 131072) >> 10;
output[yuvOffsetBase + imageSize * 2] = (512 * r - 429 * g - 83 * b + 131072) >> 10;
}
}
}
SIMD化後
SIMD化してみます。
Int32x4にRGBの3バイトを乗せてYUVに変換します。1バイト分余っている上に、3並列だと性能に期待できないんじゃ・・・とか言ってはいけません。
RGB→Y、RGB→U、RGB→Vと計算するのでは無く、RGBそれぞれについて計算した後に足し込みます。こうしないと加算処理にSIMDを使えないので。
function convertRgbaToYuvSimd(rgba, width, height, output) {
var imageSize = width * height;
var arrayYUV = new Int32Array(4);
var vR0 = SIMD.Int32x4(306, -173, 512, 0);
var vG0 = SIMD.Int32x4(601, -339, -429, 0);
var vB0 = SIMD.Int32x4(117, 512, -83, 0);
var vUVOffset = SIMD.Int32x4(0, 131072, 131072, 0);
for (var y = 0; y < height; y++) {
var rgbaOffsetY = y * width * 4;
var yuvOffsetBaseY = y * width;
for (var x = 0; x < width; x++) {
var rgbaOffset = rgbaOffsetY + x * 4;
var yuvOffsetBase = yuvOffsetBaseY + x;
var r = rgba[rgbaOffset + 0];
var g = rgba[rgbaOffset + 1];
var b = rgba[rgbaOffset + 2];
var vR1 = SIMD.Int32x4.mul(SIMD.Int32x4.splat(r), vR0);
var vG1 = SIMD.Int32x4.mul(SIMD.Int32x4.splat(g), vG0);
var vB1 = SIMD.Int32x4.mul(SIMD.Int32x4.splat(b), vB0);
var vRGB1 = SIMD.Int32x4.add(vR1, vG1);
var vRGB2 = SIMD.Int32x4.add(vRGB1, vB1);
var vRGB3 = SIMD.Int32x4.add(vRGB2, vUVOffset);
var vRGB4 = SIMD.Int32x4.shiftRightByScalar(vRGB3, 10);
SIMD.Int32x4.store(arrayYUV, 0, vRGB4);
output[yuvOffsetBase + imageSize * 0] = arrayYUV[0];
output[yuvOffsetBase + imageSize * 1] = arrayYUV[1];
output[yuvOffsetBase + imageSize * 2] = arrayYUV[2];
}
}
}
試してみて
RGBAからYUVに10回変換するのに掛かった時間を測ってみました。SIMDなし145ms、SIMDあり170ms。まあ、あまり効率的にSIMD化出来てないですし・・・。
処理性能はおいておくとして、そもそもサポートされている機能が貧弱なので使いづらいです。SSEとかのIntel系のは使ったことが無いのでわかりませんが、Neonと同じくらいの機能は欲しいなと。