I generally use a different approach for problems like this — Just use pointers for the beginning and end, and compare their overlap. If you know about how you can do compare on assignment, the difference between double minus before or after the variable, and the use of commas in a for loop…

function isPalindrome4(str) {
str = str.toLowerCase();
for (
var i = 0, j = str.length;
i < --j;
i++
) if (str[i] !== str[j]) return false;
return true;
}

They trade blows every refresh when testing, but this is a bit simpler/cleaner IMHO. Though to be fair, I think about solving problems like this like an assembly programmer. Put ES:EDI as one, DS:ESI as the other, inc, dec, cmp loop.

Side note, you might want to rethink your testing methodology. Some of your results are below browser timer granularity. When testing such small code simply looking for ms between stop and start is flawed.

Even timer jitter since you don’t know when the start rollover was can screw over your results. That’s one of the BIG contributors to differences between refreshes on testing. (though not the only one)

The approach I prefer is to make a function that waits for timer rollover on performance.now, then stores it adding a fixed amount of time (half a second usually suffices). We then loop our test — switching up the data set if possible — counting the number of loops until performance.now() > our rollover + 500.

You then use the number of iterations as your benchmark base, not the time it took.

You can even simply run an empty function as a calibration. 500 / calibration loops == time to subtract from other tests. It removes all the overhead apart from the differences between the routines.

function test(callback, data) {var 
count = 0,
start,
end,
jitter = performance.now();

do {
start = performance.now();
} while (start === jitter);

start += 500;

do {
count++;
for (var args of data) callback.apply(null, args);
} while ((end = performance.now()) < start);

var raw_ms = 500 / count;

return {
count,
raw_ms,
calibrated_ms : raw_ms - (this.calibration_ms ?? 0)
};

}
var
testData = [
'madam',
'racecar',
'pop',
'something',
'abc'
];

test.calibration_ms = test(function(){}, [ testData ]).raw_ms;
var
test3 = test(isPalindrome3, [ testData ]),
test4 = test(isPalindrome4, [ testData ]);
console.log('calibration per call : ', test.calibration_ms);
console.log('isPalindrome3 actual : ', test3.raw_ms);
console.log('isPalindrome3 calibrated : ', test3.calibrated_ms);
console.log('isPalindrome4 actual : ', test4.raw_ms);
console.log('isPalindrome4 calibrated : ', test4.calibrated_ms);
console.log(
'% difference',
(100 * test3.calibrated_ms / test4.calibrated_ms - 100).toFixed(4)
);

Fun times. I should probably turn that into a full blown testing suite, given that jsPerf seems to have gone off to never never land… or is that just me getting that “Zeit” error?

Anyhow, it’s an excellent topic and something that — as an assembly programmer — I often think about, because sometimes the smaller code performs like junk. Sometimes certain techniques — like foreach — thanks to the overhead of callbacks performs like junk. Just because it’s small, doesn’t always mean it’s good.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store