Without further details, the question can only be answered in a general sense, I'm afraid.
Do Not Re-Invent the Wheel
So should You, generally speaking, try to re-implement Math.sqrt using lookup tables and whatnot? I would strongly advise against that. You would be hard pressed to even come close to the default JSVM implementation. It's not even a fair fight: The JavaScript Virtual Machine (JSVM) has access to native code and even hardware instructions that You likely do not.
So what can You do? In the following, You will find some suggestions, in the order in which I would personally try them:
Swapping Loop Variables
In Your given example, the order of loop variables is sub-optimal. The better order would be:
for(let i = 0; i < width ; ++i)
for(let j = 0; j < height; ++j)
array[i][j] = Math.sqrt(/* some expression involving i and j */);
This should be significantly faster because array is actually an
Array of Array references, i.e. every time You call array[i] for
a different i, a new Array object has to be looked up in memory.
It is much more cache friendly to process the inner Arrays one after
another.
Motivate the JSVM to use float32
I have not actually tested this, but You might be able to tell the JSVM to use float32 precision instead of float64. One way to do that might be to use a lot of Math.fround calls, something along the lines of:
for(let i = 0; i < width ; ++i)
for(let j = 0; j < height; ++j)
array[i][j] = Math.fround( Math.sqrt( Math.fround(/* some expression involving i and j */) ) );
You should always benchmark to see if this gives You any performance gains at all.
Another way to to enforce float32 is to use Float32Array as inner arrays. Not only do Float32Arrays give type information to the JSVM. They also have half the memory size compared to Arrays of Numbers, i.e. it should increase Your throughput.
const array = Array.from({length: width}, () => new Float32Array(height));
for(let i = 0; i < width ; ++i)
for(let j = 0; j < height; ++j)
array[i][j] = Math.sqrt(/* some expression involving i and j */);
For more information on this topic, see this blog post.
Fast inverse square root
If You are using the square root to normalize vectors for computer graphics, there is a bonkers approximation of the inverse square root that was originally used in the Quake 3 engine. It might be faster than 1/Math.sqrt(y). As always, benchmarking is Your best friend.
Math.hypot()
If You do something along the lines of Math.sqrt(x*x + y*y + ...), You can
try to use Math.hypot(x,y,...) instead. It might be a tiny bit faster and, on top of that, it is underflow- and overflow-safe.
Use the WebGL/WebGPU/tensorflow.js
If all else fails You can try to use GPU acceleration to speed up Your computations. The easiest way to to that might be using some machine learning framework like tensorflow.js.
import * as tf from '@tensorflow/tfjs';
tf.setBackend('webgl');
const sqrt = await tf.sqrt(array).array();
console.log({sqrt});
Moving data to and from the GPU comes with some overhead, so this might only give performance benefits if You off-load more than just the sqrt operation to the GPU.
You can of course use WebGL or WebGPU direcly, but it will be a lot more work.
Answer from Dirk on Stack Overflowoptimization - JavaScript Math.sqrt performance - Stack Overflow
How to implement sqrt() in javascript.
javascript - Faster Alternative to Math.sqrt() - Stack Overflow
JavaScript - Improving algorithm for finding square roots of perfect squares without Math.sqrt - Stack Overflow
Videos
Without further details, the question can only be answered in a general sense, I'm afraid.
Do Not Re-Invent the Wheel
So should You, generally speaking, try to re-implement Math.sqrt using lookup tables and whatnot? I would strongly advise against that. You would be hard pressed to even come close to the default JSVM implementation. It's not even a fair fight: The JavaScript Virtual Machine (JSVM) has access to native code and even hardware instructions that You likely do not.
So what can You do? In the following, You will find some suggestions, in the order in which I would personally try them:
Swapping Loop Variables
In Your given example, the order of loop variables is sub-optimal. The better order would be:
for(let i = 0; i < width ; ++i)
for(let j = 0; j < height; ++j)
array[i][j] = Math.sqrt(/* some expression involving i and j */);
This should be significantly faster because array is actually an
Array of Array references, i.e. every time You call array[i] for
a different i, a new Array object has to be looked up in memory.
It is much more cache friendly to process the inner Arrays one after
another.
Motivate the JSVM to use float32
I have not actually tested this, but You might be able to tell the JSVM to use float32 precision instead of float64. One way to do that might be to use a lot of Math.fround calls, something along the lines of:
for(let i = 0; i < width ; ++i)
for(let j = 0; j < height; ++j)
array[i][j] = Math.fround( Math.sqrt( Math.fround(/* some expression involving i and j */) ) );
You should always benchmark to see if this gives You any performance gains at all.
Another way to to enforce float32 is to use Float32Array as inner arrays. Not only do Float32Arrays give type information to the JSVM. They also have half the memory size compared to Arrays of Numbers, i.e. it should increase Your throughput.
const array = Array.from({length: width}, () => new Float32Array(height));
for(let i = 0; i < width ; ++i)
for(let j = 0; j < height; ++j)
array[i][j] = Math.sqrt(/* some expression involving i and j */);
For more information on this topic, see this blog post.
Fast inverse square root
If You are using the square root to normalize vectors for computer graphics, there is a bonkers approximation of the inverse square root that was originally used in the Quake 3 engine. It might be faster than 1/Math.sqrt(y). As always, benchmarking is Your best friend.
Math.hypot()
If You do something along the lines of Math.sqrt(x*x + y*y + ...), You can
try to use Math.hypot(x,y,...) instead. It might be a tiny bit faster and, on top of that, it is underflow- and overflow-safe.
Use the WebGL/WebGPU/tensorflow.js
If all else fails You can try to use GPU acceleration to speed up Your computations. The easiest way to to that might be using some machine learning framework like tensorflow.js.
import * as tf from '@tensorflow/tfjs';
tf.setBackend('webgl');
const sqrt = await tf.sqrt(array).array();
console.log({sqrt});
Moving data to and from the GPU comes with some overhead, so this might only give performance benefits if You off-load more than just the sqrt operation to the GPU.
You can of course use WebGL or WebGPU direcly, but it will be a lot more work.
As your iterating in a 2-dimensional array you can reduce the iterations by ~2.
For instance :
if your expression is i x j, and your array is 3-3 size starting from 0 your going to compute in your loop :
- 0*1 AND 1*0
- 0*2 AND 2*0
- 1*2 AND 2*1
which is the same
0x0 1x0 2x0
0x1 1x1 2x1
0x2 1x2 2x2
You can be sure that the fastest algorithm you will write your self is already implemented within Math.sqrt if not better .
There is an algorithm to go through the numbers till the middle (with some simply calculation) : Writing your own square root function
but as I said, it's probably implemented if not better.
You can try to look for some specific business/domain logic in order to reduce numbers range .
Do not know how your sqrt is implemented (not a javascript coder) so what is faster I can only speculate but there are few fast methods out there using "magic numbers" for IEEE 754 float/double formats and also for integers for example like in Quake3. That works more or less precise with just few ops on defined intervals and are most likely faster then your sqrt but usable only on specific intervals.
Usual sqrt implementations are done by:
approximation polynomial
usually Taylor series, Chebyshev, etc expansions are used and the number of therms is dependent on target accuracy. Not all math functions can be computed like this.
iterative approximation
there are few methods like Newton, Babylonian, etc which usually converge fast enough so no need to use too much therms. My bet is your
sqrtuse Newtonian approximation.There are also binary search based computations
- Power by squaring for negative exponents
Binary search requires the same count of iterations then used bits of number result which is usually more then therms used in approximation methods mentioned above. But binary search for sqrt has one huge advantage and that is it can be done without multiplication (which is significant for bignums...)
- How to get a square root for 32 bit input in one clock cycle only?
There are also other search approximations like:
- How approximation search works
algebraically using
log2,exp2you can compute
powfromlog2,exp2directly andsqrt(x)=pow(x,0.5)so see- How Math.Pow (and so on) actually works
LUT
You can use piecewise interpolation with precomputed look up tables.
hybrid methods
You can combine more methods together like estimate result with low accuracy approximation polynomial and then search around it (just few bits) with binary search ... But this is meaningful only for "big" numbers (in manner of bits)...
some math operations and constants can be computed with PCA
but I see no point to use it in your case...
Also for more info take a look at related QA:
- How is the square root function implemented?
Do not know what are you computing but fastest sqrt is when you do not compute it at all. Many computations and algorithms can be rewritten so they do not need to use sqrt at all or at least not that often (like comparing distances^2 etc...).
For examle if you want to do:
x = Random();
y = sqrt(x);
You can rewrite it to:
y= Random();
x = y*y;
but beware the randomness properties are not the same !!!
Here is a small improvement I can suggest. First - start iterating from 0. Second - exit loop when the square of root candidate exceeds the number.
function squareroot(number) {
for (var i = 0; i * i <= number; i++) {
if (i * i === number)
return i;
}
return number; // don't know if you should have this line in case nothing found
}
This algo will work in O(√number) time comparing to initial O(n) which is indeed performance improvement that you asked.
Edit #1
Just even more efficient solution would be to binary search the answer as @Spektre suggested. It is known that x2 is increasing function.
function squareroot(number) {
var lo = 0, hi = number;
while(lo <= hi) {
var mid = Math.floor((lo + hi) / 2);
if(mid * mid > number) hi = mid - 1;
else lo = mid + 1;
}
return hi;
}
This algo has O(log(number)) running time complexity.
function squareRoot(n){
var avg=(a,b)=>(a+b)/2,c=5,b;
for(let i=0;i<20;i++){
b=n/c;
c=avg(b,c);
}
return c;
}
This will return the square root by repeatedly finding the average.
var result1 = squareRoot(25) //5
var result2 = squareRoot(100) //10
var result3 = squareRoot(15) //3.872983346207417
JSFiddle: https://jsfiddle.net/L5bytmoz/12/