Testing JavaScript Performance
Tags: javascript
Published: 2020-10-29 01:10:50

---
title: Testing JavaScript Performance 
description: "The difference between `Date.now` and `performance.now`. Plus an interesting finding on Chrome!" 
canonical_url: https://zellwk.com/blog/performance-now
tags: javascript
newsletter: jsr
cover_image: https://zellwk.com/images/2020/performance/results-all.png
published: true
---

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: 

```js
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](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp), 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. 

```js
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`

```js
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: 

```js
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. 

<figure role="figure" aria-label="Time taken for one operation">
  <img src="https://zellwk.com/images/2020/performance/oneop-chrome.png" alt="">
  <figcaption>Time taken for one operation in Chrome</figcaption>
</figure>

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. 

<figure role="figure" aria-label="Firefox rounds performance.now values to nearest millisecond">
  <img src="https://zellwk.com/images/2020/performance/oneop-firefox.png" alt="">
  <figcaption>Firefox rounds performance.now values to nearest millisecond</figcaption>
</figure>

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

<figure role="figure">
  <img src="https://zellwk.com/images/2020/performance/manyops-firefox.png" alt="10 million operations.">
</figure>

### Finding #1: Performance.now vs Date.now

I ran this code: 

```js
const count = 10000000
perf('Performance', _ => { return 1 + 1 }, count)
perfDate('Performance', _ => { return 1 + 1 }, count)
```

<figure role="figure">
  <img src="https://zellwk.com/images/2020/performance/initial.png" alt="Initial test">
</figure>

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. 

```js
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)
```

<figure role="figure">
  <img src="https://zellwk.com/images/2020/performance/stacked.png" alt="Stacked test.">
</figure>

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. 

```js
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)
```

<figure role="figure">
  <img src="https://zellwk.com/images/2020/performance/stacked-2.png" alt="Stacked results when using predefined function.">
</figure>

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). 

```js
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`. 

<figure role="figure">
  <img src="https://zellwk.com/images/2020/performance/results-chrome.png" alt="">
</figure>

It also happened for all three browsers! 

<figure role="figure">
  <img src="https://zellwk.com/images/2020/performance/results-all.png" alt="">
</figure>

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. 


<hr>

Thanks for reading. This article was originally posted on [my blog](https://zellwk.com/blog/performance-now).  Sign up for [my newsletter](https://zellwk.com) if you want more articles to help you become a better frontend developer.