SIMD

概述

SIMD(發音/sim-dee/)是“Single Instruction/Multiple Data”的縮寫,意為“單指令,多資料”。它是 JavaScript 操作 CPU 對應指令的介面,你可以看做這是一種不同的運算執行模式。與它相對的是 SISD(“Single Instruction/Single Data”),即“單指令,單資料”。

SIMD 的含義是使用一個指令,完成多個數據的運算;SISD 的含義是使用一個指令,完成單個數據的運算,這是 JavaScript 的預設運算模式。顯而易見,SIMD 的執行效率要高於 SISD,所以被廣泛用於3D圖形運算、物理模擬等運算量超大的專案之中。

為了理解SIMD,請看下面的例子。

var a = [1, 2, 3, 4];
var b = [5, 6, 7, 8];
var c = [];

c[0] = a[0] + b[0];
c[1] = a[1] + b[1];
c[2] = a[2] + b[2];
c[3] = a[3] + b[3];
c // Array[6, 8, 10, 12]

上面程式碼中,陣列ab的對應成員相加,結果放入陣列c。它的運算模式是依次處理每個陣列成員,一共有四個陣列成員,所以需要運算4次。

如果採用 SIMD 模式,只要運算一次就夠了。

var a = SIMD.Float32x4(1, 2, 3, 4);
var b = SIMD.Float32x4(5, 6, 7, 8);
var c = SIMD.Float32x4.add(a, b); // Float32x4[6, 8, 10, 12]

上面程式碼之中,陣列ab的四個成員的各自相加,只用一條指令就完成了。因此,速度比上一種寫法提高了4倍。

一次 SIMD 運算,可以處理多個數據,這些資料被稱為“通道”(lane)。上面程式碼中,一次運算了四個資料,因此就是四個通道。

SIMD 通常用於向量運算。

v + w    = 〈v1, …, vn〉+ 〈w1, …, wn〉
      = 〈v1+w1, …, vn+wn〉

上面程式碼中,vw是兩個多元向量。它們的加運算,在 SIMD 下是一個指令、而不是 n 個指令完成的,這就大大提高了效率。這對於3D動畫、影象處理、訊號處理、數值處理、加密等運算是非常重要的。比如,Canvas的getImageData()會將影象檔案讀成一個二進位制陣列,SIMD 就很適合對於這種陣列的處理。

總的來說,SIMD 是資料並行處理(parallelism)的一種手段,可以加速一些運算密集型操作的速度。將來與 WebAssembly 結合以後,可以讓 JavaScript 達到二進位制程式碼的執行速度。

資料型別

SIMD 提供12種資料型別,總長度都是128個二進位制位。

  • Float32x4:四個32位浮點數
  • Float64x2:兩個64位浮點數
  • Int32x4:四個32位整數
  • Int16x8:八個16位整數
  • Int8x16:十六個8位整數
  • Uint32x4:四個無符號的32位整數
  • Uint16x8:八個無符號的16位整數
  • Uint8x16:十六個無符號的8位整數
  • Bool32x4:四個32位布林值
  • Bool16x8:八個16位布林值
  • Bool8x16:十六個8位布林值
  • Bool64x2:兩個64位布林值

每種資料型別被x符號分隔成兩部分,後面的部分表示通道數,前面的部分表示每個通道的寬度和型別。比如,Float32x4就表示這個值有4個通道,每個通道是一個32位浮點數。

每個通道之中,可以放置四種資料。

  • 浮點數(float,比如1.0)
  • 帶符號的整數(Int,比如-1)
  • 無符號的整數(Uint,比如1)
  • 布林值(Bool,包含truefalse兩種值)

每種 SIMD 的資料型別都是一個函式方法,可以傳入引數,生成對應的值。

var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);

上面程式碼中,變數a就是一個128位、包含四個32位浮點數(即四個通道)的值。

注意,這些資料型別方法都不是建構函式,前面不能加new,否則會報錯。

var v = new SIMD.Float32x4(0, 1, 2, 3);
// TypeError: SIMD.Float32x4 is not a constructor

靜態方法:數學運算

每種資料型別都有一系列運算子,支援基本的數學運算。

SIMD.%type%.abs(),SIMD.%type%.neg()

abs方法接受一個SIMD值作為引數,將它的每個通道都轉成絕對值,作為一個新的SIMD值返回。

var a = SIMD.Float32x4(-1, -2, 0, NaN);
SIMD.Float32x4.abs(a)
// Float32x4[1, 2, 0, NaN]

neg方法接受一個SIMD值作為引數,將它的每個通道都轉成負值,作為一個新的SIMD值返回。

var a = SIMD.Float32x4(-1, -2, 3, 0);
SIMD.Float32x4.neg(a)
// Float32x4[1, 2, -3, -0]

var b = SIMD.Float64x2(NaN, Infinity);
SIMD.Float64x2.neg(b)
// Float64x2[NaN, -Infinity]

SIMD.%type%.add(),SIMD.%type%.addSaturate()

add方法接受兩個SIMD值作為引數,將它們的每個通道相加,作為一個新的SIMD值返回。

var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
var b = SIMD.Float32x4(5.0, 10.0, 15.0, 20.0);
var c = SIMD.Float32x4.add(a, b);

上面程式碼中,經過加法運算,新的SIMD值為(6.0, 12.0, 18.0. 24.0)

addSaturate方法跟add方法的作用相同,都是兩個通道相加,但是溢位的處理不一致。對於add方法,如果兩個值相加發生溢位,溢位的二進位制位會被丟棄; addSaturate方法則是返回該資料型別的最大值。

var a = SIMD.Uint16x8(65533, 65534, 65535, 65535, 1, 1, 1, 1);
var b = SIMD.Uint16x8(1, 1, 1, 5000, 1, 1, 1, 1);
SIMD.Uint16x8.addSaturate(a, b);
// Uint16x8[65534, 65535, 65535, 65535, 2, 2, 2, 2]

var c = SIMD.Int16x8(32765, 32766, 32767, 32767, 1, 1, 1, 1);
var d = SIMD.Int16x8(1, 1, 1, 5000, 1, 1, 1, 1);
SIMD.Int16x8.addSaturate(c, d);
// Int16x8[32766, 32767, 32767, 32767, 2, 2, 2, 2]

上面程式碼中,Uint16的最大值是65535,Int16的最大值是32767。一旦發生溢位,就返回這兩個值。

注意,Uint32x4Int32x4這兩種資料型別沒有addSaturate方法。

SIMD.%type%.sub(),SIMD.%type%.subSaturate()

sub方法接受兩個SIMD值作為引數,將它們的每個通道相減,作為一個新的SIMD值返回。

var a = SIMD.Float32x4(-1, -2, 3, 4);
var b = SIMD.Float32x4(3, 3, 3, 3);
SIMD.Float32x4.sub(a, b)
// Float32x4[-4, -5, 0, 1]

subSaturate方法跟sub方法的作用相同,都是兩個通道相減,但是溢位的處理不一致。對於sub方法,如果兩個值相減發生溢位,溢位的二進位制位會被丟棄; subSaturate方法則是返回該資料型別的最小值。

var a = SIMD.Uint16x8(5, 1, 1, 1, 1, 1, 1, 1);
var b = SIMD.Uint16x8(10, 1, 1, 1, 1, 1, 1, 1);
SIMD.Uint16x8.subSaturate(a, b)
// Uint16x8[0, 0, 0, 0, 0, 0, 0, 0]

var c = SIMD.Int16x8(-100, 0, 0, 0, 0, 0, 0, 0);
var d = SIMD.Int16x8(32767, 0, 0, 0, 0, 0, 0, 0);
SIMD.Int16x8.subSaturate(c, d)
// Int16x8[-32768, 0, 0, 0, 0, 0, 0, 0, 0]

上面程式碼中,Uint16的最小值是0subSaturate的最小值是-32678。一旦運算髮生溢位,就返回最小值。

SIMD.%type%.mul(),SIMD.%type%.div(),SIMD.%type%.sqrt()

mul方法接受兩個SIMD值作為引數,將它們的每個通道相乘,作為一個新的SIMD值返回。

var a = SIMD.Float32x4(-1, -2, 3, 4);
var b = SIMD.Float32x4(3, 3, 3, 3);
SIMD.Float32x4.mul(a, b)
// Float32x4[-3, -6, 9, 12]

div方法接受兩個SIMD值作為引數,將它們的每個通道相除,作為一個新的SIMD值返回。

var a = SIMD.Float32x4(2, 2, 2, 2);
var b = SIMD.Float32x4(4, 4, 4, 4);
SIMD.Float32x4.div(a, b)
// Float32x4[0.5, 0.5, 0.5, 0.5]

sqrt方法接受一個SIMD值作為引數,求出每個通道的平方根,作為一個新的SIMD值返回。

var b = SIMD.Float64x2(4, 8);
SIMD.Float64x2.sqrt(b)
// Float64x2[2, 2.8284271247461903]

SIMD.%FloatType%.reciprocalApproximation(),SIMD.%type%.reciprocalSqrtApproximation()

reciprocalApproximation方法接受一個SIMD值作為引數,求出每個通道的倒數(1 / x),作為一個新的SIMD值返回。

var a = SIMD.Float32x4(1, 2, 3, 4);
SIMD.Float32x4.reciprocalApproximation(a);
// Float32x4[1, 0.5, 0.3333333432674408, 0.25]

reciprocalSqrtApproximation方法接受一個SIMD值作為引數,求出每個通道的平方根的倒數(1 / (x^0.5)),作為一個新的SIMD值返回。

var a = SIMD.Float32x4(1, 2, 3, 4);
SIMD.Float32x4.reciprocalSqrtApproximation(a)
// Float32x4[1, 0.7071067690849304, 0.5773502588272095, 0.5]

注意,只有浮點數的資料型別才有這兩個方法。

SIMD.%IntegerType%.shiftLeftByScalar()

shiftLeftByScalar方法接受一個SIMD值作為引數,然後將每個通道的值左移指定的位數,作為一個新的SIMD值返回。

var a = SIMD.Int32x4(1, 2, 4, 8);
SIMD.Int32x4.shiftLeftByScalar(a, 1);
// Int32x4[2, 4, 8, 16]

如果左移後,新的值超出了當前資料型別的位數,溢位的部分會被丟棄。

var ix4 = SIMD.Int32x4(1, 2, 3, 4);
var jx4 = SIMD.Int32x4.shiftLeftByScalar(ix4, 32);
// Int32x4[0, 0, 0, 0]

注意,只有整數的資料型別才有這個方法。

SIMD.%IntegerType%.shiftRightByScalar()

shiftRightByScalar方法接受一個SIMD值作為引數,然後將每個通道的值右移指定的位數,返回一個新的SIMD值。

var a = SIMD.Int32x4(1, 2, 4, -8);
SIMD.Int32x4.shiftRightByScalar(a, 1);
// Int32x4[0, 1, 2, -4]

如果原來通道的值是帶符號的值,則符號位保持不變,不受右移影響。如果是不帶符號位的值,則右移後頭部會補0

var a = SIMD.Uint32x4(1, 2, 4, -8);
SIMD.Uint32x4.shiftRightByScalar(a, 1);
// Uint32x4[0, 1, 2, 2147483644]

上面程式碼中,-8右移一位變成了2147483644,是因為對於32位無符號整數來說,-8的二進位制形式是11111111111111111111111111111000,右移一位就變成了01111111111111111111111111111100,相當於2147483644

注意,只有整數的資料型別才有這個方法。

靜態方法:通道處理

SIMD.%type%.check()

check方法用於檢查一個值是否為當前型別的SIMD值。如果是的,就返回這個值,否則就報錯。

var a = SIMD.Float32x4(1, 2, 3, 9);

SIMD.Float32x4.check(a);
// Float32x4[1, 2, 3, 9]

SIMD.Float32x4.check([1,2,3,4]) // 報錯
SIMD.Int32x4.check(a) // 報錯
SIMD.Int32x4.check('hello world') // 報錯

SIMD.%type%.extractLane(),SIMD.%type%.replaceLane()

extractLane方法用於返回給定通道的值。它接受兩個引數,分別是SIMD值和通道編號。

var t = SIMD.Float32x4(1, 2, 3, 4);
SIMD.Float32x4.extractLane(t, 2) // 3

replaceLane方法用於替換指定通道的值,並返回一個新的SIMD值。它接受三個引數,分別是原來的SIMD值、通道編號和新的通道值。

var t = SIMD.Float32x4(1, 2, 3, 4);
SIMD.Float32x4.replaceLane(t, 2, 42)
// Float32x4[1, 2, 42, 4]

SIMD.%type%.load()

load方法用於從二進位制陣列讀入資料,生成一個新的SIMD值。

var a = new Int32Array([1,2,3,4,5,6,7,8]);
SIMD.Int32x4.load(a, 0);
// Int32x4[1, 2, 3, 4]

var b = new Int32Array([1,2,3,4,5,6,7,8]);
SIMD.Int32x4.load(a, 2);
// Int32x4[3, 4, 5, 6]

load方法接受兩個引數:一個二進位制陣列和開始讀取的位置(從0開始)。如果位置不合法(比如-1或者超出二進位制陣列的大小),就會丟擲一個錯誤。

這個方法還有三個變種load1()load2()load3(),表示從指定位置開始,只加載一個通道、二個通道、三個通道的值。

// 格式
SIMD.Int32x4.load(tarray, index)
SIMD.Int32x4.load1(tarray, index)
SIMD.Int32x4.load2(tarray, index)
SIMD.Int32x4.load3(tarray, index)

// 實例
var a = new Int32Array([1,2,3,4,5,6,7,8]);
SIMD.Int32x4.load1(a, 0);
// Int32x4[1, 0, 0, 0]
SIMD.Int32x4.load2(a, 0);
// Int32x4[1, 2, 0, 0]
SIMD.Int32x4.load3(a, 0);
// Int32x4[1, 2, 3,0]

SIMD.%type%.store()

store方法用於將一個SIMD值,寫入一個二進位制陣列。它接受三個引數,分別是二進位制陣列、開始寫入的陣列位置、SIMD值。它返回寫入值以後的二進位制陣列。

var t1 = new Int32Array(8);
var v1 = SIMD.Int32x4(1, 2, 3, 4);
SIMD.Int32x4.store(t1, 0, v1)
// Int32Array[1, 2, 3, 4, 0, 0, 0, 0]

var t2 = new Int32Array(8);
var v2 = SIMD.Int32x4(1, 2, 3, 4);
SIMD.Int32x4.store(t2, 2, v2)
// Int32Array[0, 0, 1, 2, 3, 4, 0, 0]

上面程式碼中,t1是一個二進位制陣列,v1是一個SIMD值,只有四個通道。所以寫入t1以後,只有前四個位置有值,後四個位置都是0。而t2是從2號位置開始寫入,所以前兩個位置和後兩個位置都是0。

這個方法還有三個變種store1()store2()store3(),表示只寫入一個通道、二個通道和三個通道的值。

var tarray = new Int32Array(8);
var value = SIMD.Int32x4(1, 2, 3, 4);
SIMD.Int32x4.store1(tarray, 0, value);
// Int32Array[1, 0, 0, 0, 0, 0, 0, 0]

SIMD.%type%.splat()

splat方法返回一個新的SIMD值,該值的所有通道都會設成同一個預先給定的值。

SIMD.Float32x4.splat(3);
// Float32x4[3, 3, 3, 3]
SIMD.Float64x2.splat(3);
// Float64x2[3, 3]

如果省略引數,所有整數型的SIMD值都會設定0,浮點型的SIMD值都會設成NaN

SIMD.%type%.swizzle()

swizzle方法返回一個新的SIMD值,重新排列原有的SIMD值的通道順序。

var t = SIMD.Float32x4(1, 2, 3, 4);
SIMD.Float32x4.swizzle(t, 1, 2, 0, 3);
// Float32x4[2,3,1,4]

上面程式碼中,swizzle方法的第一個引數是原有的SIMD值,後面的引數對應將要返回的SIMD值的四個通道。它的意思是新的SIMD的四個通道,依次是原來SIMD值的1號通道、2號通道、0號通道、3號通道。由於SIMD值最多可以有16個通道,所以swizzle方法除了第一個引數以外,最多還可以接受16個引數。

下面是另一個例子。

var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
// Float32x4[1.0, 2.0, 3.0, 4.0]

var b = SIMD.Float32x4.swizzle(a, 0, 0, 1, 1);
// Float32x4[1.0, 1.0, 2.0, 2.0]

var c = SIMD.Float32x4.swizzle(a, 3, 3, 3, 3);
// Float32x4[4.0, 4.0, 4.0, 4.0]

var d = SIMD.Float32x4.swizzle(a, 3, 2, 1, 0);
// Float32x4[4.0, 3.0, 2.0, 1.0]

SIMD.%type%.shuffle()

shuffle方法從兩個SIMD值之中取出指定通道,返回一個新的SIMD值。

var a = SIMD.Float32x4(1, 2, 3, 4);
var b = SIMD.Float32x4(5, 6, 7, 8);

SIMD.Float32x4.shuffle(a, b, 1, 5, 7, 2);
// Float32x4[2, 6, 8, 3]

上面程式碼中,ab一共有8個通道,依次編號為0到7。shuffle根據編號,取出相應的通道,返回一個新的SIMD值。

靜態方法:比較運算

SIMD.%type%.equal(),SIMD.%type%.notEqual()

equal方法用來比較兩個SIMD值ab的每一個通道,根據兩者是否精確相等(a === b),得到一個布林值。最後,所有通道的比較結果,組成一個新的SIMD值,作為掩碼返回。notEqual方法則是比較兩個通道是否不相等(a !== b)。

var a = SIMD.Float32x4(1, 2, 3, 9);
var b = SIMD.Float32x4(1, 4, 7, 9);

SIMD.Float32x4.equal(a,b)
// Bool32x4[true, false, false, true]

SIMD.Float32x4.notEqual(a,b);
// Bool32x4[false, true, true, false]

SIMD.%type%.greaterThan(),SIMD.%type%.greaterThanOrEqual()

greatThan方法用來比較兩個SIMD值ab的每一個通道,如果在該通道中,a較大就得到true,否則得到false。最後,所有通道的比較結果,組成一個新的SIMD值,作為掩碼返回。greaterThanOrEqual則是比較a是否大於等於b

var a = SIMD.Float32x4(1, 6, 3, 11);
var b = SIMD.Float32x4(1, 4, 7, 9);

SIMD.Float32x4.greaterThan(a, b)
// Bool32x4[false, true, false, true]

SIMD.Float32x4.greaterThanOrEqual(a, b)
// Bool32x4[true, true, false, true]

SIMD.%type%.lessThan(),SIMD.%type%.lessThanOrEqual()

lessThan方法用來比較兩個SIMD值ab的每一個通道,如果在該通道中,a較小就得到true,否則得到false。最後,所有通道的比較結果,會組成一個新的SIMD值,作為掩碼返回。lessThanOrEqual方法則是比較a是否等於b

var a = SIMD.Float32x4(1, 2, 3, 11);
var b = SIMD.Float32x4(1, 4, 7, 9);

SIMD.Float32x4.lessThan(a, b)
// Bool32x4[false, true, true, false]

SIMD.Float32x4.lessThanOrEqual(a, b)
// Bool32x4[true, true, true, false]

SIMD.%type%.select()

select方法通過掩碼生成一個新的SIMD值。它接受三個引數,分別是掩碼和兩個SIMD值。

var a = SIMD.Float32x4(1, 2, 3, 4);
var b = SIMD.Float32x4(5, 6, 7, 8);

var mask = SIMD.Bool32x4(true, false, false, true);

SIMD.Float32x4.select(mask, a, b);
// Float32x4[1, 6, 7, 4]

上面程式碼中,select方法接受掩碼和兩個SIMD值作為引數。當某個通道對應的掩碼為true時,會選擇第一個SIMD值的對應通道,否則選擇第二個SIMD值的對應通道。

這個方法通常與比較運算子結合使用。

var a = SIMD.Float32x4(0, 12, 3, 4);
var b = SIMD.Float32x4(0, 6, 7, 50);

var mask = SIMD.Float32x4.lessThan(a,b);
// Bool32x4[false, false, true, true]

var result = SIMD.Float32x4.select(mask, a, b);
// Float32x4[0, 6, 3, 4]

上面程式碼中,先通過lessThan方法生成一個掩碼,然後通過select方法生成一個由每個通道的較小值組成的新的SIMD值。

SIMD.%BooleanType%.allTrue(),SIMD.%BooleanType%.anyTrue()

allTrue方法接受一個SIMD值作為引數,然後返回一個布林值,表示該SIMD值的所有通道是否都為true

var a = SIMD.Bool32x4(true, true, true, true);
var b = SIMD.Bool32x4(true, false, true, true);

SIMD.Bool32x4.allTrue(a); // true
SIMD.Bool32x4.allTrue(b); // false

anyTrue方法則是隻要有一個通道為true,就返回true,否則返回false

var a = SIMD.Bool32x4(false, false, false, false);
var b = SIMD.Bool32x4(false, false, true, false);

SIMD.Bool32x4.anyTrue(a); // false
SIMD.Bool32x4.anyTrue(b); // true

注意,只有四種布林值資料型別(Bool32x4Bool16x8Bool8x16Bool64x2)才有這兩個方法。

這兩個方法通常與比較運算子結合使用。

var ax4    = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
var bx4    = SIMD.Float32x4(0.0, 6.0, 7.0, 8.0);
var ix4    = SIMD.Float32x4.lessThan(ax4, bx4);
var b1     = SIMD.Int32x4.allTrue(ix4); // false
var b2     = SIMD.Int32x4.anyTrue(ix4); // true

SIMD.%type%.min(),SIMD.%type%.minNum()

min方法接受兩個SIMD值作為引數,將兩者的對應通道的較小值,組成一個新的SIMD值返回。

var a = SIMD.Float32x4(-1, -2, 3, 5.2);
var b = SIMD.Float32x4(0, -4, 6, 5.5);
SIMD.Float32x4.min(a, b);
// Float32x4[-1, -4, 3, 5.2]

如果有一個通道的值是NaN,則會優先返回NaN

var c = SIMD.Float64x2(NaN, Infinity)
var d = SIMD.Float64x2(1337, 42);
SIMD.Float64x2.min(c, d);
// Float64x2[NaN, 42]

minNum方法與min的作用一模一樣,唯一的區別是如果有一個通道的值是NaN,則會優先返回另一個通道的值。

var ax4 = SIMD.Float32x4(1.0, 2.0, NaN, NaN);
var bx4 = SIMD.Float32x4(2.0, 1.0, 3.0, NaN);
var cx4 = SIMD.Float32x4.min(ax4, bx4);
// Float32x4[1.0, 1.0, NaN, NaN]
var dx4 = SIMD.Float32x4.minNum(ax4, bx4);
// Float32x4[1.0, 1.0, 3.0, NaN]

SIMD.%type%.max(),SIMD.%type%.maxNum()

max方法接受兩個SIMD值作為引數,將兩者的對應通道的較大值,組成一個新的SIMD值返回。

var a = SIMD.Float32x4(-1, -2, 3, 5.2);
var b = SIMD.Float32x4(0, -4, 6, 5.5);
SIMD.Float32x4.max(a, b);
// Float32x4[0, -2, 6, 5.5]

如果有一個通道的值是NaN,則會優先返回NaN

var c = SIMD.Float64x2(NaN, Infinity)
var d = SIMD.Float64x2(1337, 42);
SIMD.Float64x2.max(c, d)
// Float64x2[NaN, Infinity]

maxNum方法與max的作用一模一樣,唯一的區別是如果有一個通道的值是NaN,則會優先返回另一個通道的值。

var c = SIMD.Float64x2(NaN, Infinity)
var d = SIMD.Float64x2(1337, 42);
SIMD.Float64x2.maxNum(c, d)
// Float64x2[1337, Infinity]

靜態方法:位運算

SIMD.%type%.and(),SIMD.%type%.or(),SIMD.%type%.xor(),SIMD.%type%.not()

and方法接受兩個SIMD值作為引數,返回兩者對應的通道進行二進位制AND運算(&)後得到的新的SIMD值。

var a = SIMD.Int32x4(1, 2, 4, 8);
var b = SIMD.Int32x4(5, 5, 5, 5);
SIMD.Int32x4.and(a, b)
// Int32x4[1, 0, 4, 0]

上面程式碼中,以通道0為例,1的二進位制形式是00015的二進位制形式是01001,所以進行AND運算以後,得到0001

or方法接受兩個SIMD值作為引數,返回兩者對應的通道進行二進位制OR運算(|)後得到的新的SIMD值。

var a = SIMD.Int32x4(1, 2, 4, 8);
var b = SIMD.Int32x4(5, 5, 5, 5);
SIMD.Int32x4.or(a, b)
// Int32x4[5, 7, 5, 13]

xor方法接受兩個SIMD值作為引數,返回兩者對應的通道進行二進位制”異或“運算(^)後得到的新的SIMD值。

var a = SIMD.Int32x4(1, 2, 4, 8);
var b = SIMD.Int32x4(5, 5, 5, 5);
SIMD.Int32x4.xor(a, b)
// Int32x4[4, 7, 1, 13]

not方法接受一個SIMD值作為引數,返回每個通道進行二進位制”否“運算(~)後得到的新的SIMD值。

var a = SIMD.Int32x4(1, 2, 4, 8);
SIMD.Int32x4.not(a)
// Int32x4[-2, -3, -5, -9]

上面程式碼中,1的否運算之所以得到-2,是因為在計算機內部,負數採用”2的補碼“這種形式進行表示。也就是說,整數n的負數形式-n,是對每一個二進位制位取反以後,再加上1。因此,直接取反就相當於負數形式再減去1,比如1的負數形式是-1,再減去1,就得到了-2

靜態方法:資料型別轉換

SIMD提供以下方法,用來將一種資料型別轉為另一種資料型別。

  • SIMD.%type%.fromFloat32x4()
  • SIMD.%type%.fromFloat32x4Bits()
  • SIMD.%type%.fromFloat64x2Bits()
  • SIMD.%type%.fromInt32x4()
  • SIMD.%type%.fromInt32x4Bits()
  • SIMD.%type%.fromInt16x8Bits()
  • SIMD.%type%.fromInt8x16Bits()
  • SIMD.%type%.fromUint32x4()
  • SIMD.%type%.fromUint32x4Bits()
  • SIMD.%type%.fromUint16x8Bits()
  • SIMD.%type%.fromUint8x16Bits()

帶有Bits字尾的方法,會原封不動地將二進位制位拷貝到新的資料型別;不帶字尾的方法,則會進行資料型別轉換。

var t = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
SIMD.Int32x4.fromFloat32x4(t);
// Int32x4[1, 2, 3, 4]

SIMD.Int32x4.fromFloat32x4Bits(t);
// Int32x4[1065353216, 1073741824, 1077936128, 1082130432]

上面程式碼中,fromFloat32x4是將浮點數轉為整數,然後存入新的資料型別;fromFloat32x4Bits則是將二進位制位原封不動地拷貝進入新的資料型別,然後進行解讀。

Bits字尾的方法,還可以用於通道數目不對等的拷貝。

var t = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
SIMD.Int16x8.fromFloat32x4Bits(t);
// Int16x8[0, 16256, 0, 16384, 0, 16448, 0, 16512]

上面程式碼中,原始SIMD值t是4通道的,而目標值是8通道的。

如果資料轉換時,原通道的資料大小,超過了目標通道的最大寬度,就會報錯。

實例方法

SIMD.%type%.prototype.toString()

toString方法返回一個SIMD值的字串形式。

var a = SIMD.Float32x4(11, 22, 33, 44);
a.toString() // "SIMD.Float32x4(11, 22, 33, 44)"

實例:求平均值

正常模式下,計算n個值的平均值,需要運算n次。

function average(list) {
  var n = list.length;
  var sum = 0.0;
  for (var i = 0; i < n; i++) {
    sum += list[i];
  }
  return sum / n;
}

使用SIMD,可以將計算次數減少到n次的四分之一。

function average(list) {
  var n = list.length;
  var sum = SIMD.Float32x4.splat(0.0);
  for (var i = 0; i < n; i += 4) {
    sum = SIMD.Float32x4.add(
      sum,
      SIMD.Float32x4.load(list, i)
    );
  }
  var total = SIMD.Float32x4.extractLane(sum, 0) +
              SIMD.Float32x4.extractLane(sum, 1) +
              SIMD.Float32x4.extractLane(sum, 2) +
              SIMD.Float32x4.extractLane(sum, 3);
  return total / n;
}

上面程式碼先是每隔四位,將所有的值讀入一個 SIMD,然後立刻累加。然後,得到累加值四個通道的總和,再除以n就可以了。

results matching ""

    No results matching ""