All posts

We rebuilt our file converter. Here is every benchmark we ran

Nemi converts, resizes and packs your files on machines we run ourselves. In September we rewrote the service that does it. For this post we built every version again, ran twelve ordinary jobs through each, five times, and charted all of it: the wins, the jobs where nothing changed, and the one that had got slower, which measuring it made us fix.

Engineering · · 10 min read

Every time you convert a photo, pull a frame out of a video or pack files into an archive, a service we run does the work. In September we rewrote it from top to bottom. This post measures what that changed, properly, including the parts that are not flattering and the two things measuring it made us fix.

We built the version from before the rewrite, the rewrite itself and today's version, side by side on the same machine. Then we ran the same twelve jobs through each, the kind people actually do: re-encoding a phone photo, shrinking a camera file, turning a recording into an MP3, packing a folder. Five timed runs each, the middle one counts.

All twelve jobs, before and today

Here is every result on one axis, comparing the version from before the rewrite with the one running today. It is logarithmic, because the gains range from a few percent to fifty times, and anything slower would sit to the left of the line.

How many times faster each job is today

same10x100x

Convert 50 PNGs to JPEG in one go

51x

Re-encode a 12 MP JPEG

41x

ZIP four photos and videos

18x

Resize a 24 MP photo

11x

Take a still from a video

8.4x

Convert a 12 MP photo to PNG

2.3x

Pack 40 MB of logs as tar.xz

1.5x

Convert a Word document to PDF

1.2x

Convert 3 minutes of WAV to MP3

1.1x

Convert a 12 MP photo to WebP

1x

Convert a 12 MP photo to AVIF

1x

Convert a 1080p MOV to MP4

1x
How we measured

Every version was built from source and measured on 3 October 2026: before is commit f1c2d0a6, after is d0e9ebcb, and today is the current version, which includes the two fixes this post describes. The old version needed a two line patch so it would fetch test files from this machine, and it was built against today's libraries because it predates our lock file.

Apple M2, 8 cores, 8 GB, a working desktop rather than an idle bench machine. Each job ran once to warm up and then 5 times per version, with the versions interleaved so background noise lands on all of them alike. The figure is the median, measured from sending the request to the last byte of the result. Production runs on a different machine, so read the ratios, not the seconds.

51x

faster converting fifty images in one go

41x

faster re-encoding a 12 megapixel photo

24.3x

smaller WebP files, at the same speed as before

Three groups stand out. Images got dramatically faster. Archives got faster where media was involved. And the jobs that hand straight over to a well known encoder, video, audio and documents, barely moved, because the encoder was never the problem. We will take them in that order.

Watch any of them run

A ratio is easy to read past. So here are the measured times played side by side. Pick a job and press start; "Real time" plays the medians exactly as they were measured.

Before and after, started at the same moment

4000 x 3000 JPEG, 2.3 MB, to JPEG at quality 85. 34x faster after the rewrite.

0 ms

Before the rewrite

After the rewrite

Why did one JPEG take seven seconds?

Re-encoding a 12 megapixel JPEG at quality 85 took 7.08 s before the rewrite and takes 173 ms today. The encoder is the same library at the same setting, and the two files differ by fewer than a hundred bytes. The difference is entirely in how the result was written to disk.

The old service handed the encoder a plain file. A JPEG encoder writes its output in very small pieces, and without a buffer in between, each of those pieces becomes its own request to the operating system. Millions of tiny writes, each one cheap, add up to seconds. The rewrite puts a buffer between the encoder and the file, so the same bytes reach the disk in a few hundred large writes.

Before

The encoder writes straight to the file. Every small piece of output is a separate trip into the operating system.

After

The encoder writes into memory, and the memory is flushed to the file in large blocks. Same bytes, a tiny fraction of the trips.

Fifty images is the same story fifty times. Converting fifty 1080p PNGs to JPEG took 36.8 s before and 716 ms after, because every one of them went through that same JPEG writer. That makes it the biggest win in the table, at 51 times.

The slowest part of the old converter was not converting. It was writing the answer down.

A still from a video, and a resize

Taking one frame from 45 seconds into a minute long video went from 1.79 s to 213 ms. The old service asked the video tool to decode from the start and throw frames away until it reached 45 seconds. The rewrite asks it to jump to 45 seconds first and decode from there. In the tool's own terms, the -ss option moved in front of the input. One argument, the same frame, 8.4 times faster, and the further into a long video you go, the bigger the gap gets.

Shrinking a 24 megapixel photo to 1920 pixels wide went from 1.71 s to 163 ms. The rewrite uses a resizing library that works on many pixels per instruction, plus the same buffered writer.

Why did zipping photos and videos get so much faster?

Packing two photos and two videos, 86.9 MB in all, into a zip took 1.87 s before and takes 107 ms today. The old service compressed everything it put in a zip. Photos and videos are already compressed, so that work bought almost nothing: the zip came out the same size. The rewrite recognises files that do not compress and stores them as they are. The archive is the same size and opens the same way. It is the same reasoning behind the light setting Nemi uses when you download a folder as one zip.

Packing 40 MB of log files as .tar.xz is 1.5 times faster, because the compressor now uses more than one core. On a file that small it can only split the work into two blocks, so two cores is all it gets; bigger archives can be split further. Every .tar.xz it writes carries a CRC64 checksum, multithreaded or not, so a damaged copy is caught when it is unpacked.

The one that got slower, and how we fixed it

Converting a 12 megapixel photo to WebP took 361 ms before the rewrite and 802 ms right after it: twice as slow. Part of that was deliberate. The old service ignored the quality slider and always wrote lossless WebP, which is quick to make and enormous. The rewrite did what the slider said, and lossy encoding is real work.

But slower was not good enough, so while writing this post we looked at where the time went. Almost all of it was in one setting. The WebP encoder has a dial for how hard it searches for the smallest file, from 0 to 6, and we were on its default of 4. We tried every step on the test photo and on four real camera photos of 8 to 21 megapixels. Step 2 encodes about twice as fast as step 4, and the files land between 4 percent smaller and 7 percent larger, with picture quality the same to within a rounding error. The steps above it spend their time on a search that buys a few percent of file size on a photo. Turning on more threads made no difference.

The same photo as WebP: before, after the rewrite, and today

Before: lossless, quality ignoredAfter the rewrite: encoder on step 4Today: encoder on step 2

Time

4000 x 3000 JPEG, 2.3 MB, to WebP at quality 80

361 ms
802 ms
352 ms
BeforeAfter the rewriteToday

File size

19 MB
802 KB
810 KB
Time to convert the same 12 megapixel photo, and what the file weighs. Before the rewrite the file was lossless whatever quality you asked for.
How we measured

Every version was built from source and measured on 3 October 2026: before is commit f1c2d0a6, after is d0e9ebcb, and today is the current version, which includes the two fixes this post describes. The old version needed a two line patch so it would fetch test files from this machine, and it was built against today's libraries because it predates our lock file.

Apple M2, 8 cores, 8 GB, a working desktop rather than an idle bench machine. Each job ran once to warm up and then 5 times per version, with the versions interleaved so background noise lands on all of them alike. The figure is the median, measured from sending the request to the last byte of the result. Production runs on a different machine, so read the ratios, not the seconds.

So today a WebP takes 352 ms, against 361 ms before the rewrite, and the file is 810 KB instead of 19 MB. Same speed, a file you would actually put on a web page.

What did not change, and why that is fine

Converting a 1080p MOV to MP4 took 13.1 s before and takes 13.1 s today. Three minutes of WAV to MP3, and a 41 page Word document to PDF, moved by about ten percent. These jobs are almost entirely the encoder's own work, the encoder runs with the same settings in every version, and the files come out the same to within a few bytes. There was nothing for a rewrite to win here.

The video work in the rewrite went elsewhere: real progress while an encode runs instead of a bar that guesses, and a limit on how many heavy encodes run at once, so a queue of videos no longer starts one encoder per core with every encoder trying to use every core. If what you need is a smaller video rather than a faster one, our guide to making a video small enough to send has the settings.

AVIF is in the same group for a different reason. It is 1 times as fast, but the comparison is not like for like: the rewrite also changed the encoder's speed setting, so it is a slightly different file made faster, not the same file.

The full table

Every median for all three versions. Switch to the log scale to compare the small ones.

Every job, before, after the rewrite and today

Before the rewriteAfter the rewriteToday

Re-encode a 12 MP JPEG

4000 x 3000 JPEG, 2.3 MB, to JPEG at quality 85

7.08 s
207 ms
173 ms

41x faster

Resize a 24 MP photo

6000 x 4000 JPEG, 4.5 MB, to 1920 px wide JPEG at quality 85

1.71 s
175 ms
163 ms

11x faster

Convert a 12 MP photo to WebP

4000 x 3000 JPEG, 2.3 MB, to WebP at quality 80

361 ms
802 ms
352 ms

1x faster

Convert a 12 MP photo to AVIF

4000 x 3000 JPEG, 2.3 MB, to AVIF at quality 70

2.34 s
1.98 s
2.31 s

1x faster

Convert a 12 MP photo to PNG

4000 x 3000 JPEG, 2.3 MB, to PNG

343 ms
146 ms
148 ms

2.3x faster

Take a still from a video

60 s 1080p H.264 MP4, 61 MB, frame at 45 s to JPEG

1.79 s
208 ms
213 ms

8.4x faster

Convert a 1080p MOV to MP4

20 s 1080p30 H.264 MOV, 18.9 MB, to H.264 MP4 (crf 23, preset medium)

13.1 s
12.6 s
13.1 s

1x faster

Convert 3 minutes of WAV to MP3

3 min 44.1 kHz stereo WAV, 31.8 MB, to MP3 at 192 kbps

1.42 s
1.3 s
1.28 s

1.1x faster

Convert a Word document to PDF

28 KB DOCX (41 pages once rendered) to PDF via pandoc and WeasyPrint

1.76 s
1.56 s
1.52 s

1.2x faster

Convert 50 PNGs to JPEG in one go

50 PNGs at 1920 x 1080, 172 MB, to JPEG at quality 85, returned as a ZIP

36.8 s
721 ms
716 ms

51x faster

Pack 40 MB of logs as tar.xz

40 text files, 40 MB, to tar.xz at level 6

15.9 s
10.2 s
10.8 s

1.5x faster

ZIP four photos and videos

2 JPEGs and 2 videos, 86.9 MB, to ZIP

1.87 s
123 ms
107 ms

18x faster

How we measured

Every version was built from source and measured on 3 October 2026: before is commit f1c2d0a6, after is d0e9ebcb, and today is the current version, which includes the two fixes this post describes. The old version needed a two line patch so it would fetch test files from this machine, and it was built against today's libraries because it predates our lock file.

Apple M2, 8 cores, 8 GB, a working desktop rather than an idle bench machine. Each job ran once to warm up and then 5 times per version, with the versions interleaved so background noise lands on all of them alike. The figure is the median, measured from sending the request to the last byte of the result. Production runs on a different machine, so read the ratios, not the seconds.

How should you read these numbers?

Every job ran five times per version, with the versions interleaved, and every output was checked before a timing counted. Three things are worth keeping in mind.

  • Read the ratios, not the seconds. This is an ordinary 8 GB M2 with other work running, and production is a different machine.
  • Every run is kept. The harness, the test files and each individual timing are saved, so we can repeat the measurement whenever the service changes.
  • Conversion runs on hardware we operate. Your files are not sent to an outside conversion company, as the security page sets out.

If you want to see what the converter can do now, the format explorer lists every pair it supports, and Convert is where you use it. The help page on converting files walks through the panel step by step.

Questions people ask

Is WebP better than JPEG?

For photos on a web page, usually yes: a lossy WebP is typically smaller than a JPEG that looks the same, and every current browser shows it. JPEG still opens in more older software, so it is the safer choice for email attachments and print. In our benchmark a 12 megapixel photo became a 0.8 MB WebP at quality 80; the two formats' quality scales do not mean the same thing, so compare them by eye, not by the number.

Why is my WebP file bigger than the JPEG?

Often it is lossless WebP, which keeps every pixel exactly and barely shrinks a camera photo. Before our rewrite, Nemi's converter wrote lossless WebP whatever quality you picked, and a 12 megapixel photo came out at about 20 MB. Today it follows the quality setting, and the same photo is about 0.8 MB.

How fast is Nemi's file converter?

It depends on the job. In our benchmark of twelve ordinary jobs, converting fifty images in one go is 51 times faster today than before the rewrite, and re-encoding a 12 megapixel JPEG about 41 times faster. Video, audio and document conversions, which are almost entirely the encoder's own work, stayed about the same.

Why does zipping photos and videos not make them smaller?

Photos, videos and most audio are already compressed, so compressing them again finds almost nothing to remove. Nemi's converter recognises those files and stores them in the archive as they are, which made zipping 87 MB of photos and videos about 17 times faster, with an archive of the same size.

How do I take a still from a video quickly?

Ask the video tool to jump to the moment first and decode from there, instead of decoding from the start and throwing frames away. In ffmpeg that means putting the -ss option in front of the input. That one change made taking a frame 45 seconds into a video about 8 times faster in Nemi's converter.

Are my files sent to another company when I convert them in Nemi?

No. Conversion runs on hardware we operate, not at an outside conversion company, as our security page sets out. You choose whether the result is downloaded to your device or saved in your workspace next to the original.

Where the numbers come from

  • The measurements (12 jobs, 3 versions, 5 runs each): lib/blog/data/conversion-bench.json
  • The harness, fixtures and the patch the old version needed: scripts/blog-bench/README.md
  • Every individual run, with the tool command lines: scripts/blog-bench/results-2026-10-03.json
  • Before the rewrite: commit f1c2d0a6
  • The rewrite: commit d0e9ebcb
  • Today: commit fc6cfd7d

Read next

Use the thing we write about.

Files, docs, sheets, photos, calendar and meetings in one account.