Web App Development

Writing a custom parseInt function in Javascript and benchmarking it

The function I wrote didn't include features like different radixes or negative numbers. In spite of the reduced requirements I expected the function to be slower than the native code, which turned out to be false.

Takeaways

  • MyParseInt was 67% faster than parseInt with radix 10 in Chrome, in Firefox it was about 3x as fast as parseInt
  • This does not apply to slower Javascript engines like the IE9 and Opera 11 ones where the native functions are faster
  • Arrays with precached results are too slow to be useful if finding the result only requires a few calculations
  • Once I had a faster simplified version of myParseInt I couldn't find a way to speed it up by more than 10 to 20%. So what follows might not be very effective and applies to Firefox, it the effects were small and inconsistent in Chrome
  • Calculating the first initial result value of the parsed number instead of assigning 0 and adding the  initial value in the loop seemed to be beneficial
You can have a look at the tests on jsperf.com. Performance will likely vary with string length.

First naive attempt


function myParseInt1(str)
{
var res = 0;
var nums = {
"1": 1, "2": 2, "3": 3, "4": 4, "5": 5,
"6": 6,"7": 7,"8": 8,"9": 9};
for (var i=0; i<str.length; i++)
{
var char = str.charAt(i);
var value = nums[char];
res += value;
res *= 10;
}
res /= 10;
return res;
}

This was especially slow in Firefox, taking 10 times as much time as the native parseInt. In Chrome the code took 3 times as much time as parseInt.

Using ascii codes instead of a dictionary


function myParseInt2(str) {
var res = 0;
for (i=0; i<str.length; i++)
{
var charCode = str.charCodeAt(i);
var value = charCode - 48;
res += value;
res *= 10;
}
res /= 10;
return res;
};

My second attempt got me very close to the final result: 50% faster than parseInt.
The clear next step would be to cache str.length

Attempted improvement with inconsistent/little effect

I added the caching in myParseInt3. myParseInt4 and myParseInt6 were failed attempts. In myParseInt5 I avoided the multiplication for the first digit, which allowed me to remove the division at the end. I doubt that this was an improvement... definitely not in Firefox.
In myParseInt5b I started caching the last variable:


function myParseInt5b(str) {
var res = 0;
var strLength = str.length;
var last = strLength - 1;
for (i=0; i<strLength; i++)
{
var charCode = str.charCodeAt(i);
var value = charCode - 48;
res += value;
if (i !== last)
{
res *= 10;
}
}
return res;
};

This increased the speed a little, although it was still slower than myParseInt3 in Firefox.
I made three more changes to myParseInt5b:
a) avoid variables, instead write res += str.charCodeAt(i) - 48;
b) calculate the initial res value (for the first digit) at the top of the function
c) Move the res*10 to the top of the for loop, that way we can get the if out of the loop
(a) seemed to have the code slightly slower in Firefox and slightly faster in Chrome. But the difference was quite small and inconsistent. (b) made the code a bit faster in both browsers. (c) helped in Firefox and did not make a significant difference in Chrome.

Final code

This code was fastest in Chrome. It contains improvements (b) and (c) from above:


function myParseInt5bDoFirstNoIf(str) {
var res = str.charCodeAt(0) - 48;
var strLength = str.length;
for (i=1; i<strLength; i++)
{
res *= 10;
var charCode = str.charCodeAt(i)
var value = charCode - 48;
res += value;
}
return res;
};

Failed attempt myParseInt4: Using a dictionary instead of local variables

I don't know what I thought when I tried this. It made the code a lot slower, especially in Firefox - although it was still faster than native parseInt.


(function(){
var vars = {};
window.myParseInt4 = function(str) {
vars.res = 0;
vars.strLength = str.length;
for (i=0; i<vars.strLength; i++)
{
vars.charCode = str.charCodeAt(i);
vars.value = vars.charCode - 48;
vars.res += vars.value;
vars.res *= 10;
}
vars.res /= 10;
return vars.res;
};
})()

Failed attempt myParseInt6: avoiding ascii to digit conversion

Instead of turning the ascii character code into a digit by subtracting 48 this code adds the ascii code to the result - weighed by the digit position. At the end of the function a precalculated value is subtracted from the result - the value depends on the length of the number string. This was 36% slower than the fastest result in Chrome, probably because the array lookups take up quite some time and the code change avoids little calculation.


(function(){
var pow10Cache = [];
for (var i=0; i&lt;200; i++)
{
pow10Cache[i] = Math.pow(10, i);
}
var addCache = [];
for (var strLength=0; strLength&lt;200; strLength++)
{
var subs = 0;
for (i=0; i&lt;strLength; i++)
{
var reverseI = strLength - i - 1;
subs += pow10Cache[reverseI] * 48;
}
addCache[strLength] = -subs;
}

window.myParseInt6 = function(str){
var strLength = str.length;
var res = addCache[strLength];
var last = strLength - 1;
for (i=0; i&lt;strLength; i++)
{
var charCode = str.charCodeAt(i);
var reverseI = last - i;
res += charCode * pow10Cache[reverseI];
}
return res;
}
})()

Comments


Follow me on Twitter