Testing JavaScript Performance
I was curious about testing JavaScript performance and did some research on it.
When I talk about JavaScript performance here, I’m not talking about things like time-to-first-byte, time-to-interaction, etc.. I’m talking about raw computing speed – how long does function X run compared to function Y.
I discovered we can use two methods to test performance – performance.now
and Date.now
. I was curious about the difference between them, so I made some experiments to document my findings.
The procedure
The procedure for testing performance is simple. There are three steps:
- Check the current timestamp
- Run some operations
- Check the timestamp again
The difference between the two timestamps will be the amount of time needed to run the operations.
Here’s what this process looks like in code:
const start = performance.now()
// Do stuff
const end = performance.now()
const elapsed = end - start
console.log(elapsed)
Performance.now vs Date.now
performance.now
is said to generate a Dom high-res timestamp, which means it’s going to be more accurate than Date.now
.
Unfortunately, browsers have to round off this timestamp because of security issues, so it doesn’t make much of a difference in the end (according to my findings).
To help with the tests, I created a perf
function.
function perf(message, callback, loops = 1) {
const startTime = performance.now()
while (loops) {
callback()
loops = loops - 1
}
const endTime = performance.now()
const elapsed = endTime - startTime
console.log(message, elapsed)
}
I also created a Date.now
equivalent and I named it perfDate
function perfDate(message, callback, loops = 1) {
const startTime = Date.now()
while (loops) {
callback()
loops = loops - 1
}
const elapsed = Date.now() - startTime
console.log(message, elapsed)
}
Experiments and Findings
I tested both performance.now
and Date.now
with a simple operation:
function test() {
return 1 + 1
}
While testing, I realised there’s no point in testing one operation because of two reasons.
First, performance.now
can measure operations in microseconds but Date.now
can’t. So we won’t be able to see the differences between them.
Second, performance.now
gets rounded off to the nearest millisecond in Safari and Firefox. So there’s no point comparing anything that takes less than 1ms.
I had to increase the tests to 10 million operations before the numbers begin to make sense.
Finding #1: Performance.now vs Date.now
I ran this code:
const count = 10000000
perf(
'Performance',
_ => {
return 1 + 1
},
count,
)
perfDate(
'Performance',
_ => {
return 1 + 1
},
count,
)
Here, I found no major difference between performance.now
and Date.now
.
However, performance.now
seems slower on Safari and Firefox. performance.now
also gets rounded to the nearest millisecond on Safari and Firefox.
Finding #2: Chrome takes time to define functions
I tried stacking perf
and perfDate
functions to see if there were any differences. The results startled me.
const count = 10000000
perf(
'Performance',
_ => {
return 1 + 1
},
count,
)
perf(
'Performance',
_ => {
return 1 + 1
},
count,
)
perf(
'Performance',
_ => {
return 1 + 1
},
count,
)
perfDate(
'Date',
_ => {
return 1 + 1
},
count,
)
perfDate(
'Date',
_ => {
return 1 + 1
},
count,
)
perfDate(
'Date',
_ => {
return 1 + 1
},
count,
)
Second and Third tests on Chrome for both perf
and perfDate
jumped from 8ms to 80ms. That’s a 10x increase. I thought I was doing something wrong!
I discovered this increase was caused by defining functions on the fly. If I used a predefined function, the numbers reduced back to 8ms.
function test() {
return 1 + 1
}
const count = 10000000
perf('Performance', test, count)
perf('Performance', test, count)
perf('Performance', test, count)
perfDate('Date', test, count)
perfDate('Date', test, count)
perfDate('Date', test, count)
Note: I also found out that Node’s performance.now
has the same behaviour as Chrome’s performance.now
.
Finding #3: It’s impossible to get an average result
I realised each performance.now
and Date.now
resulted in different values. I wanted to get an average of the results, so I added another loop to perf
.
(I did the same to perfDate
too).
function perf(message, callback, loops = 1, rounds = 10) {
const results = []
while (rounds) {
const startTime = performance.now()
while (loops) {
callback()
loops = loops - 1
}
const endTime = performance.now()
const elapsed = endTime - startTime
results.push(elapsed)
rounds = rounds - 1
}
const average = results.reduce((sum, curr) => curr + sum, 0) / results.length
console.log(message)
console.log('Average', average)
console.log('Results', results)
}
But the results were strange: the elapsed timing for the second loop onwards dropped to zero. This happened for both perf
and perfDate
.
It also happened for all three browsers!
I’m not sure what’s wrong here. If you know why, please tell me!
Update: Dylan pointed out the possibility that the loop
variable may have been reduced to 0 from the first runthrough in first round, We could fix it by resetting loop
back to the original value.
Conclusion
Both performance.now
and Date.now
can be used to test for JavaScript performance. There isn’t a major difference between these two methods though.
When testing on Chrome, make sure you use predefined functions. Don’t define functions on the fly or you’ll get inaccurate tests.