📜 ⬆️ ⬇️

Asm.js practice

image
On this cool day, I was looking for algorithms and implementations for calculating the number pi. There were a myriad of algorithms, but here there was a post describing the algorithm and its implementation on C.
The algorithm impresses with its speed, although it gives a hex representation, but it just so happened that I needed the option on js. The instant, practically, processing to normal js showed very poor statistics, the work on calculating the 1,000,000th sign took ... 48 seconds (4 GHz FF).
How met with asmjs and what stones met it is possible to learn under a cat.


For the impatient, the result is on a githaba .

After a cursory review, it became clear that we did not need to make work with the entire generation into the asm module, but only two functions were used: expm and series. Since expm is called inside a series, then we should export only series from the module.
Original functions
double series (int m, int id) /* This routine evaluates the series sum_k 16^(id-k)/(8*k+m) using the modular exponentiation technique. */ { int k; double ak, eps, p, s, t; double expm (double x, double y); #define eps 1e-17 s = 0.; /* Sum the series up to id. */ for (k = 0; k < id; k++){ ak = 8 * k + m; p = id - k; t = expm (p, ak); s = s + t / ak; s = s - (int) s; } /* Compute a few terms where k >= id. */ for (k = id; k <= id + 100; k++){ ak = 8 * k + m; t = pow (16., (double) (id - k)) / ak; if (t < eps) break; s = s + t; s = s - (int) s; } return s; } double expm (double p, double ak) /* expm = 16^p mod ak. This routine uses the left-to-right binary exponentiation scheme. */ { int i, j; double p1, pt, r; #define ntp 25 static double tp[ntp]; static int tp1 = 0; /* If this is the first call to expm, fill the power of two table tp. */ if (tp1 == 0) { tp1 = 1; tp[0] = 1.; for (i = 1; i < ntp; i++) tp[i] = 2. * tp[i-1]; } if (ak == 1.) return 0.; /* Find the greatest power of two less than or equal to p. */ for (i = 0; i < ntp; i++) if (tp[i] > p) break; pt = tp[i-1]; p1 = p; r = 1.; /* Perform binary exponentiation algorithm modulo ak. */ for (j = 1; j <= i; j++){ if (p1 >= pt){ r = 16. * r; r = r - (int) (r / ak) * ak; p1 = p1 - pt; } pt = 0.5 * pt; if (pt >= 1.){ r = r * r; r = r - (int) (r / ak) * ak; } } return r; } 

Basic pattern.


In essence, we create a black box with some interface, so all we can do is pass something to the module and get a set of methods and / or values ​​from it.
In the codes I reviewed, the construction of the form was established:
 (function (window) { "use strict"; //  var buffer = new ArrayBuffer(BUFFER_SIZE); //       var functionNameOrStuctureName = (function (stdlib, env, heap) { "use asm"; //  //   return { methodNameExport: methodNameInModule, methodName2Export: methodName2InModule, }; //   return methodNameInModule })( { //    (stdlib) Uint8Array: Uint8Array, Int8Array: Int8Array, Uint16Array: Uint16Array, Int16Array: Int16Array, Uint32Array: Uint32Array, Int32Array: Int32Array, Float32Array:Float32Array, Float64Array:Float64Array, Math: Math }, { //  (env) NTP:NTP }, buffer //  ,  ,    > 4096    0 ); })(window); // wrapper end 

An error of the type “TypeError: asm.js type error: asm.js module fails return a statement statement” crashes , make sure that the module returns something. If it returns as expected, then you should make sure that the variable declarations are correct. I had a mistake when after the declaration I tried to do something else with one intovy variable.
I hope the basic things have already learned, but still:
  function f1(i, d) { i = i | 0; // integer ,  i   d = +d; // double ,  d     var char = new Uint8Array(heap); // ,       var i2 = 0; //    (     fixnum) var d2 = 0.0; //      i2 = i2 | 0; //  fixnum  integer return +d2; //    double     double } 

')

Pitfall # 1: Variables and Module Functions


I used to first declare all variables, and only then assign values ​​to them. In asm.js, everything is somewhat more cunning: first, we declare all the variables that we use through closures, and the functions of the mathematical library also need to be described here, and not called below.
This is what happened:
  "use asm"; //  stdlib  var floor = stdlib.Math.floor; //       ,    var pow = stdlib.Math.pow; //    ,    var tp1 = 0; //     var tp = new stdlib.Float64Array(heap); //    var ntp = env.NTP | 0; //       env,    

Functions cannot be created before initializing the variable and assigning it a function. Therefore, functions follow.
  function expm(p, ak) { //  } 


Pitfall # 2: variables in module functions.


But in the body of the module's functions, you must first specify the type of variables accepted at the input, then initialize the internal variables, if anything, if it is int, then var i = 0, if double, then var d = 0.0, if array, then through new and type msssiv with the transfer of heap into it, and after the initialization of the ints, I advise them to “pre-initialize” by assigning the form i = i | 0. By the way: initialization of variables in advance in the style of C is not required. Numbers of the form 1e-17 give an error of overshooting, use 0.00000000000000001
At the exit:
  function expm(p, ak) { p = +p; ak = +ak; var i = 0; var pt = 0.0; // .... i = i | 0; ntp = ntp | 0; //  } 


Pitfall number 3: comparisons and iterators


Frankly, I laughed for a long time, but int and int cannot be compared . If you do something like i == k or i <l, then compiling with an error like this will fall out
"TypeError: asm.js type error: arguments and a comparison must be signed, unsigned or doubles, int and int are given . "
A little more laughter was added by comparing int and integer (i == 0)
"TypeError: asm.js type error: arguments for a comparison must both be signed, unsigned or doubles, fixnum and int are given . "
Only floating point numbers are fine (for example, pt == 1.0).
As a result, if you want to compare the int with another int. it is necessary to declare a construction of the form (i | 0) <(ntp | 0).
As for the iterators , then simply “charm”: instead of all of us, the usual i ++, we have i = (i | 0) + 1 | 0
Result:
  if ((tp1 | 0) == 0) { // vars for (i = 1; (i | 0) < (ntp | 0); i = (i | 0) + 1 | 0) { // surprise, read more) } } 


Pitfall # 4: Mathematical and other external functions.


Everything is simple, if you want to use floor, sin, etc., then you need to declare them at the beginning of the module (immediately after “use asm”). If I write stdlib.Math.floor in a function, I threw out a return type error. Apparently because of the appeal to the properties of the object.

Reef 5: Buffers.


And here everything is very, very interesting. What do we do when we want to get / set the value from / to the arr array with index i? arr [i]. Suppose we do so, but then we get an error of the form.
  arr[i] = +1; 

“TypeError: asm.js type error: index expression isn't shifted; must be an Int8 / Uint8 access »
We are subtly hinted that there must be a shift. One guru I found a shift of 2 to the right.
  arr[i >> 2] = +1; 

"TypeError: asm.js type error: shift amount must be 3"
We are subtly hinted that he should be three.
  arr[i << 3 >> 3] = +1; 

It turns out in the end. Shift to the left, we compensated for the shift to the right. It seems everything is the same, but it works.

The result of the works
 /** * Created with JetBrains WebStorm. * User: louter * Date: 12.09.13 * Time: 17:49 */ (function (window) { "use strict"; var ihex; var NTP = 25; var buffer = new ArrayBuffer(1024 * 1024 * 8); var series = (function (stdlib, env, heap) { "use asm"; var floor = stdlib.Math.floor; var pow = stdlib.Math.pow; var tp1 = 0; var tp = new stdlib.Float64Array(heap); var ntp = env.NTP | 0; function expm(p, ak) { p = +p; ak = +ak; var i = 0; var j = 0; var p1 = 0.0; var pt = 0.0; var r = 0.0; // float as double i = i | 0; j = j | 0; ntp = ntp | 0; if ((tp1 | 0) == 0) { tp1 = 1 | 0; tp[0] = +1; for (i = 1; (i | 0) < (ntp | 0); i = (i | 0) + 1 | 0) { tp[(i << 3) >> 2] = +(+2 * tp[((i - 1) << 3) >> 3]); } } if (ak == 1.0) { return +0; } for (i = 0; (i | 0) < (ntp | 0); i = (i | 0) + 1 | 0) { if (+tp[i << 3 >> 3] > p) { break; } } pt = +tp[(i - 1) << 3 >> 3]; p1 = +p; r = +1; for (j = 0; (j | 0) <= (i | 0); j = (j | 0) + 1 | 0) { if (p1 >= pt) { r = +16 * r; r = r - (+(floor(r / ak))) * ak; p1 = p1 - pt; } pt = 0.5 * pt; if (pt >= 1.) { r = r * r; r = r - (+(floor(r / ak))) * ak; } } return +r; } function series(m, id) { m = m | 0; id = id | 0; var k = 0; var ak = 0.0; var eps = 0.0; var p = 0.0; var s = 0.0; var t = 0.0; eps = 0.00000000000000001; k = 0 | 0; for (k; (k | 0) < (id | 0); k = (k | 0) + 1 | 0) { ak = +8 * (+(k | 0)) + (+(m | 0)); p = (+(id | 0) - +(k | 0)); t = +expm(p, ak); s = s + t / ak; s = s - floor(s); } for (k = (id | 0); (k | 0) <= ((id + 100) | 0); k = (k | 0) + 1 | 0) { ak = +8 * (+(k | 0)) + (+(m | 0)); t = pow(+16, +(id | 0) - (+(k | 0))) / +ak; if (t < eps) break; s = s + t; s = s - floor(s); } return +s; } return series; })( { Uint8Array: Uint8Array, Int8Array: Int8Array, Uint16Array: Uint16Array, Int16Array: Int16Array, Uint32Array: Uint32Array, Int32Array: Int32Array, Float32Array:Float32Array, Float64Array:Float64Array, Math: Math }, { NTP:NTP }, buffer ); ihex = function (x, nhx, chx) { var i, y, hx = "0123456789ABCDEF"; y = Math.abs(x); for (i = 0; i < nhx; i++) { y = 16. * (y - (y | 0)); chx[i] = hx[y | 0]; } }; window.pi = function (id) { var pid, s1, s2, s3, s4 , hex = []; s1 = series(1, id); s2 = series(4, id); s3 = series(5, id); s4 = series(6, id); pid = 4 * s1 - 2 * s2 - s3 - s4; pid = pid - (pid | 0) + 1; ihex(pid, 16, hex); return { hex: hex.join('').substr(0, 10), fraction:pid }; }; })(window); 


PS I don’t know who or what is wrong, but (!) For some reason the compiled program produced worse results than asm.js.
Namely
time ./pi 1,000,000
>> real 0m2.161s
>> user 0m2.149s
>> sys 0m0.001s

console.time ('s');
pi (1,000,000);
console.timeEnd ('s');
>> s: 1868.5ms

And further:
time ./pi 10,000,000
>> real 0m25.345s
>> user 0m25.176s
>> sys 0m0.019s

console.time ('s');
pi (10,000,000);
console.timeEnd ('s');
>> s: 22152.83ms

Do not believe? Check it out. The source of the program in the above post . My sources are also listed above.

UPD:
I put testASM.js on github to check if asmjs is working or not. After connecting, the window.asmjs (bool) variable appears. testASM.js and testASM.min.js

Source: https://habr.com/ru/post/193642/


All Articles