Testing JavaScript Performance

Zell Liew 🤗 - Oct 29 '20 - - Dev Community

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:

  1. Check the current timestamp
  2. Run some operations
  3. 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)
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Experiments and Findings

I tested both performance.now and Date.now with a simple operation:

function test () {
  return 1 + 1
}
Enter fullscreen mode Exit fullscreen mode

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.


Time taken for one operation in Chrome

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.


Firefox rounds performance.now values to nearest millisecond

I had to increase the tests to 10 million operations before the numbers begin to make sense.

10 million operations.

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)
Enter fullscreen mode Exit fullscreen mode

Initial test

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)
Enter fullscreen mode Exit fullscreen mode

Stacked test.

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)
Enter fullscreen mode Exit fullscreen mode

Stacked results when using predefined function.

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)
}
Enter fullscreen mode Exit fullscreen mode

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!

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.


Thanks for reading. This article was originally posted on my blog. Sign up for my newsletter if you want more articles to help you become a better frontend developer.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .