Why our visual regression is so slow? 
Tags: testing, news, javascript, ocaml
Published: 2020-11-13 21:11:52


Hola! Lazy dev here, and today we are going to discuss computer eyes 👁👁 that help us to test how our products look like. 

The visual regression tools that we are using every day are extremely slow. That is the fact. At least because the task to compare 2 images is hard. And I tried to fix this 👀. 

But first of all, let's take a look at how visual regression tools work under the hood and what they are actually doing.

## Image comparison 

Image comparison itself is pretty hard. We can not just compare 2 image buffers using "===" and even we can not take all the pixels from the image and compare them one by one. Why? Because human eyes can not see the difference between all the colors. For example, how do you think – do these colors are the same or not? 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/3nqsovqqbh12bsetkv3j.png)

If yes – 👍 you have a really good vision! But 95% of people will not spot the difference between `rgba(200, 100, 100, 250)` and `rgba(200, 100, 95, 250)` colors.

That's why in order to **visually** compare images – we need to take all the pixels in the image and it is a lot (for a full-HD screenshot (1920x1080) we have `1920 * 1080 = 2 090 880` pixels) – iterate them one by one and calculate the [color difference](https://en.wikipedia.org/wiki/Color_difference). 

This is a hard task for computers. So the algorithm is: 

1) Read and decode the image
2) Make a loop with **millions** iterations, make some calculations and save the different pixels
3) Make an image of different pixels 
4) Encode and save the image diff 

But the result is extremely helpful when you are testing the user interface:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kyf7s0ygy38980nfbpfd.png)

## The slowness

But unfortunately, tools that we are commonly using to compare screenshots are extremely slow. And they are slow not because they had a bad code inside. The main problem is that they are often *written in the wrong language** or doing some **useless job** under the hood.

For example, the most popular image comparison tool in the javascript community – [pixelmatch](https://github.com/mapbox/pixelmatch) is really slow in Node.js environment (but blazing fast in the browser otherwise). 

Using pixelmatch to comparing 2 screenshots of https://cypress.io home page will take around 7-8 seconds. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bnnt2j1j7fjgn0e82t10.png)

## Impact on your CI

This really affects our CI time. Let's calculate the CI time for visual regression if we are running 25000 screenshot tests per month. And this number is not something overwhelming. It is a very basic plan of https://percy.io, which is usually **not enough** for huge projects. 

So, if we are running 25000 visual tests and each screenshot test running for 7 seconds we are spending **48,6 hours** on CI! 

```
25000 * 7 / 3600 = 48,611111111
```

That is a lot! This can take even more time than all the other UI tests and that's why performance for this sort of task **really matters**. 

Cause if we can save at least 3 seconds per each snapshot we will save **20 hours** per month.

```
25000 * 3 / 3600 = 20,83333 
```

## Solution 

That was a pain point for me, so I decided to fix this – and wrote that **fastest in the world image comparison tool**. 

And I am happy to introduce you [odiff](https://github.com/dmtrKovalenko/odiff)! 👀🥳🎉 It was designed to handle the "huge" images, be fast, memory-efficient, and save **your CI time**.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mc5lilp2ig60b5njwtxq.png)

This tool is running the same comparison 2 times faster than analogs! Yes, it can save you those 3 seconds per snapshot :) 

Here are some benchmarks: 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0lbdghivm2tz0ccjw702.png)

Also, here are the results of comparing the same cypress.io home page screenshot: 

| Command                                                                                    |      Mean [s] | Min [s] | Max [s] |    Relative |
| :----------------------------------------------------------------------------------------- | ------------: | ------: | ------: | ----------: |
| `pixelmatch www.cypress.io-1.png www.cypress.io.png www.cypress-diff.png`                  | 7.712 ± 0.069 |   7.664 |   7.896 | 1.82 ± 0.03 |
| ImageMagick `compare www.cypress.io-1.png www.cypress.io.png -compose src diff-magick.png` | 8.881 ± 0.121 |   8.692 |   9.066 | 2.09 ± 0.04 |
| `odiff www.cypress.io-1.png www.cypress.io.png www.cypress-diff.png`                       | 4.247 ± 0.053 |   4.178 |   4.344 |        1.00 |

## How? 

Why it is so fast? The answer is simple:

- It is written in OCaml and compiled to the native binary executable. OCaml compiler is extremely fast and predictable so it is easy to profile and optimize performance-sensitive code. And also we have direct node.js bindings!
- It is not doing a useless job under the hood. It is working directly with the low-level bytes buffer and avoids unnecessary memory allocations. 
- It is optimized by profiling produced assembly output 👯‍♀️

## Check it out 

Try it out right now! Give us your feedback, and do not forget about ⭐️ the project if you are interested! 

{% github dmtrKovalenko/odiff %}


Thank you for your time! Optimize your CI pipeline and in order to follow the tradition – no pixels were harmed in the making of this article 🙈