SIMD

Overview

SIMD (pronounced /sim-dee/) is the abbreviation of "Single Instruction/Multiple Data", which means "single instruction, multiple data". It is the interface for JavaScript to operate the corresponding instructions of the CPU. You can see this as a different operation execution mode. The opposite of it is SISD ("Single Instruction/Single Data"), which means "single instruction, single data".

The meaning of SIMD is to use one instruction to complete the operation of multiple data; the meaning of SISD is to use one instruction to complete the operation of a single data, which is the default operation mode of JavaScript. Obviously, the execution efficiency of SIMD is higher than that of SISD, so it is widely used in 3D graphics operations, physical simulations and other projects with large computational complexity.

In order to understand SIMD, please look at the following example.

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]

In the above code, the corresponding members of the arrays a and b are added, and the result is put into the array c. Its operation mode is to process each array member in turn. There are four array members in total, so 4 operations are required.

If the SIMD mode is used, only one calculation is enough.

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]

In the above code, the addition of the four members of the arrays a and b is completed with only one instruction. Therefore, the speed is 4 times faster than the previous writing method.

One SIMD operation can process multiple data. These data are called "lane". In the above code, four data are calculated at one time, so there are four channels.

SIMD is usually used for vector operations.

v + w = ​​〈v1, …, vn+w1, …, wn=V1+w1, …, vn+wn

In the above code, v and w are two multivariate vectors. Their addition operation is completed by one instruction instead of n instructions under SIMD, which greatly improves efficiency. This is very important for operations such as 3D animation, image processing, signal processing, numerical processing, and encryption. For example, Canvas getImageData() will read the image file as a binary array, and SIMD is very suitable for processing this kind of array.

In general, SIMD is a means of data parallelism, which can speed up some computationally intensive operations. In the future, when combined with WebAssembly, JavaScript can reach the running speed of binary code.

type of data

SIMD provides 12 data types with a total length of 128 binary bits.

-Float32x4: Four 32-bit floating point numbers -Float64x2: Two 64-bit floating point numbers -Int32x4: Four 32-bit integers -Int16x8: Eight 16-bit integers -Int8x16: Sixteen 8-bit integers -Uint32x4: Four unsigned 32-bit integers -Uint16x8: Eight unsigned 16-bit integers -Uint8x16: Sixteen unsigned 8-bit integers -Bool32x4: Four 32-bit Boolean values -Bool16x8: Eight 16-bit Boolean values -Bool8x16: Sixteen 8-bit Boolean values -Bool64x2: Two 64-bit Boolean values

Each data type is separated into two parts by the x symbol, the latter part represents the number of channels, and the front part represents the width and type of each channel. For example, Float32x4 means that this value has 4 channels, and each channel is a 32-bit floating point number.

In each channel, four types of data can be placed.

-Floating point number (float, such as 1.0) -Signed integer (Int, such as -1) -Unsigned integer (Uint, such as 1) -Boolean value (Bool, contains two values ​​of true and false)

Each data type of SIMD is a function method, you can pass in parameters to generate the corresponding value.

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

In the above code, the variable a is a 128-bit value containing four 32-bit floating point numbers (that is, four channels).

Note that these data type methods are not constructors, and new cannot be added in front, otherwise an error will be reported.

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

Static method: mathematical operations

Each data type has a series of operators that support basic mathematical operations.

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

The abs method accepts a SIMD value as a parameter, converts each channel of it to an absolute value, and returns it as a new SIMD value.

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

The neg method accepts a SIMD value as a parameter, converts each of its channels to a negative value, and returns it as a new SIMD value.

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()

The add method accepts two SIMD values ​​as parameters, adds them for each channel, and returns as a new SIMD value.

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);

In the above code, after addition, the new SIMD value is (6.0, 12.0, 18.0. 24.0).

The addSaturate method has the same effect as the add method. Both channels are added, but the overflow handling is inconsistent. For the add method, if the addition of two values ​​overflows, the overflowed binary bits will be discarded; the addSaturate method returns the maximum value of the data type.

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]

In the above code, the maximum value of Uint16 is 65535, and the maximum value of Int16 is 32767. Once overflow occurs, these two values ​​are returned.

Note that the two data types Uint32x4 and Int32x4 do not have the addSaturate method.

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

The sub method accepts two SIMD values ​​as parameters, subtracts each of their channels, and returns them as a new SIMD value.

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]

The subSaturate method has the same function as the sub method. Both channels are subtracted, but the overflow handling is inconsistent. For the sub method, if the subtraction of two values ​​overflows, the overflowed binary bits will be discarded; the subSaturate method returns the minimum value of the data type.

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]

In the above code, the minimum value of Uint16 is 0, and the minimum value of Int16 is -32678. Once the operation overflows, it returns to the minimum value.

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

The mul method accepts two SIMD values ​​as parameters, multiplies each of their channels, and returns them as a new SIMD value.

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]

The div method accepts two SIMD values ​​as parameters, divides each of their channels, and returns them as a new SIMD value.

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]

The sqrt method accepts a SIMD value as a parameter, finds the square root of each channel, and returns it as a new SIMD value.

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

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

The reciprocalApproximation method accepts a SIMD value as a parameter, finds the reciprocal of each channel (1 / x), and returns it as a new SIMD value.

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

The reciprocalSqrtApproximation method accepts a SIMD value as a parameter, finds the reciprocal of the square root of each channel (1 / (x^0.5)), and returns it as a new SIMD value.

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

Note that only floating-point data types have these two methods.

SIMD.%IntegerType%.shiftLeftByScalar()

The shiftLeftByScalar method accepts a SIMD value as a parameter, and then shifts the value of each channel to the left by the specified number of bits, and returns it as a new SIMD value.

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

If after shifting to the left, the new value exceeds the number of digits of the current data type, the overflow part will be discarded.

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

Note that only integer data types have this method.

SIMD.%IntegerType%.shiftRightByScalar()

The shiftRightByScalar method accepts a SIMD value as a parameter, and then shifts the value of each channel to the right by the specified number of bits, and returns a new SIMD value.

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

If the value of the original channel is a signed value, the sign bit remains unchanged and is not affected by the right shift. If it is a value without a sign bit, the header will be filled with 0 after shifting to the right.

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

In the above code, shifting -8 to the right by one bit becomes 2147483644, because for 32-bit unsigned integers, the binary form of -8 is 11111111111111111111111111111000, and shifting one bit to the right becomes 2147483644 01111111111111111111111111111100 is equivalent to 2147483644.

Note that only integer data types have this method.

Static method: channel processing

SIMD.%type%.check()

The check method is used to check whether a value is a SIMD value of the current type. If yes, return this value, otherwise report an error.

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

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

SIMD.Float32x4.check([1, 2, 3, 4]); // error
SIMD.Int32x4.check(a); // error
SIMD.Int32x4.check("hello world"); // error

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

The extractLane method is used to return the value of a given channel. It accepts two parameters, namely SIMD value and channel number.

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

The replaceLane method is used to replace the value of the specified channel and return a new SIMD value. It accepts three parameters, which are the original SIMD value, channel number and new channel value.

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

SIMD.%type%.load()

The load method is used to read in data from a binary array and generate a new SIMD value.

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]

The load method accepts two parameters: a binary array and the position to start reading (starting from 0). If the position is illegal (such as -1 or exceeds the size of the binary array), an error will be thrown.

There are three variants of this method, load1(), load2(), and load3(), which means that starting from the specified position, only load the values ​​of one channel, two channels, and three channels.

// format
SIMD.Int32x4.load(tarray, index);
SIMD.Int32x4.load1(tarray, index);
SIMD.Int32x4.load2(tarray, index);
SIMD.Int32x4.load3(tarray, index);

// instance
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()

The store method is used to write a SIMD value into a binary array. It accepts three parameters, which are the binary array, the position of the array to start writing, and the SIMD value. It returns the binary array after the value is written.

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]

In the above code, t1 is a binary array, v1 is a SIMD value, and there are only four channels. So after writing t1, only the first four positions have values, and the last four positions are all 0. And t2 is written from position 2, so the first two positions and the last two positions are both 0.

There are three variants of this method store1(), store2() and store3(), which means that only one channel, two channels and three channels are written.

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()

The splat method returns a new SIMD value, and all channels of this value will be set to the same predetermined value.

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

If the parameter is omitted, all SIMD values ​​of integer type will be set to 0, and SIMD values ​​of floating point type will be set to NaN.

SIMD.%type%.swizzle()

The swizzle method returns a new SIMD value and rearranges the channel order of the original SIMD value.

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

In the above code, the first parameter of the swizzle method is the original SIMD value, and the following parameters correspond to the four channels of the SIMD value to be returned. It means the four channels of the new SIMD, which are channel 1, channel 2, channel 0, and channel 3 of the original SIMD value in sequence. Since the SIMD value can have up to 16 channels, the swizzle method can accept up to 16 parameters in addition to the first parameter.

Here is another example.

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()

The shuffle method takes the specified channel from the two SIMD values ​​and returns a new SIMD value.

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]

In the above code, a and b have a total of 8 channels, which are numbered from 0 to 7. shuffle takes out the corresponding channel according to the number, and returns a new SIMD value.

Static method: comparison operation

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

The equal method is used to compare each channel of two SIMD values ​​a and b, and get a boolean value according to whether the two are exactly equal (a === b). Finally, the comparison results of all channels form a new SIMD value, which is returned as a mask. The notEqual method compares whether the two channels are not equal (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()

The greatThan method is used to compare each channel of the two SIMD values ​​a and b. If in this channel, a is larger, you get true, otherwise, you get false. Finally, the comparison results of all channels form a new SIMD value, which is returned as a mask. greaterThanOrEqual compares whether a is greater than or equal to 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()

The lessThan method is used to compare each channel of the two SIMD values ​​a and b. If in this channel, a is smaller, you get true, otherwise, you get false. Finally, the comparison results of all channels will form a new SIMD value and return it as a mask. The lessThanOrEqual method compares whether a is equal to 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()

The select method generates a new SIMD value through the mask. It accepts three parameters, namely the mask and two SIMD values.

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]

In the above code, the select method accepts a mask and two SIMD values ​​as parameters. When the mask corresponding to a channel is true, the channel corresponding to the first SIMD value will be selected, otherwise the channel corresponding to the second SIMD value will be selected.

This method is usually used in combination with comparison operators.

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]

In the above code, a mask is first generated by the lessThan method, and then a new SIMD value composed of the smaller value of each channel is generated by the select method.

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

The allTrue method accepts a SIMD value as a parameter, and then returns a boolean value indicating whether all channels of the SIMD value are 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

The anyTrue method returns true as long as one channel is true, otherwise it returns 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

Note that only four Boolean data types (Bool32x4, Bool16x8, Bool8x16, Bool64x2) have these two methods.

These two methods are usually combined with comparison operators.

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()

The min method accepts two SIMD values ​​as parameters, and returns the smaller value of the two corresponding channels to form a new SIMD value.

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]

If there is a channel whose value is NaN, it will return NaN first.

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

The function of minNum is exactly the same as that of min, the only difference is that if the value of one channel is NaN, the value of the other channel will be returned first.

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()

The max method accepts two SIMD values ​​as parameters, and returns the larger value of the two corresponding channels to form a new SIMD value.

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]

If there is a channel whose value is NaN, it will return NaN first.

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

The function of maxNum is exactly the same as that of max, the only difference is that if the value of one channel is NaN, the value of the other channel will be returned first.

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

Static method: bit operation

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

The and method accepts two SIMD values ​​as parameters, and returns the new SIMD value obtained after the binary AND operation (&) is performed on the corresponding channels of the two.

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]

In the above code, taking channel 0 as an example, the binary form of 1 is 0001, and the binary form of 5 is 01001, so after performing the AND operation, 0001 is obtained.

The or method accepts two SIMD values ​​as parameters, and returns the new SIMD value obtained after the binary OR operation (|) is performed on the corresponding channels of the two.

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]

The xor method accepts two SIMD values ​​as parameters, and returns the new SIMD value obtained by performing the binary "exclusive OR" operation (^) on the corresponding channels of the two.

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]

The not method accepts a SIMD value as a parameter, and returns the new SIMD value obtained after each channel performs a binary "no" operation (~).

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

In the above code, the reason why the negation of 1 gets -2 is because in the computer, negative numbers are expressed in the form of "2's complement". That is to say, the negative number form of the integer n, -n, is to add 1 after the inversion of each binary digit. Therefore, directly negating is equivalent to subtracting 1 from the negative number form. For example, the negative number form of 1 is -1, and subtracting 1 again, you get -2.

Static method: data type conversion

SIMD provides the following methods to convert one data type to another data type.

-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()

The method with the Bits suffix will copy the binary bits to the new data type intact; the method without the suffix will perform the data type conversion.

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]

In the above code, fromFloat32x4 converts a floating-point number to an integer, and then stores it in a new data type; fromFloat32x4Bits copies the binary bits intact into the new data type, and then decodes it.

The method of Bits suffix can also be used for copying with unequal channels.

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]

In the above code, the original SIMD value t is for 4 channels, and the target value is for 8 channels.

If the data size of the original channel exceeds the maximum width of the target channel during data conversion, an error will be reported.

Example method

SIMD.%type%.prototype.toString()

The toString method returns the string form of a SIMD value.

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

Example: Find the average

In normal mode, calculating the average of n values ​​requires n operations.

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

Using SIMD, the number of calculations can be reduced to a quarter of n times.

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;
}

The above code first reads all the values ​​into a SIMD every four digits, and then accumulates them immediately. Then, get the sum of the accumulated value of the four channels, and divide it by n.