Skip to content

Instantly share code, notes, and snippets.

@cipharius
Last active January 22, 2018 17:41
Show Gist options
  • Save cipharius/0e5608aa72033fdf22b551ea85d4cb00 to your computer and use it in GitHub Desktop.
Save cipharius/0e5608aa72033fdf22b551ea85d4cb00 to your computer and use it in GitHub Desktop.
Blazing fast yes in Nim

Recently I stumbled upon a post which takes a closer look at the yes command line tool. The main purpose of it is to write endless stream of a single letter y at a ridiculous speed.

On the first glance this seems like a really simple problem, just two lines of Nim and you're done, right?

while true:
  echo "y"

And indeed, this gives us plenty of y's. But when we take a look at the write speed..

$ ./yes | pv > /dev/null
... [2.84MiB/s] ...
$ yes | pv > /dev/null
... [7.13GiB/s] ...

..that is a mind-blowing difference!

This curious detail is thoroughly researched in the original post, so I'll get straight to the key difference. The original yes writes a page aligned buffer, which is filled with the desired message, where page size typically is 4096 bytes.

Now to apply the newfound knowledge in Nim:

const
  pageSize = 4096
  yes = "y\n"
var buffer = ""

for i in 1..pageSize:
  buffer &= yes

while true:
  discard stdout.writeChars(buffer, 0, buffer.len)

And check the write speed..

$ ./yes | pv > /dev/null
... [5.11GiB/s] ...

..Well, this looks way better, but I'm not quite pleased with the missing 2 GB/s. After checking out Nim source code it seem that the fwrite function is the bottleneck.

Luckily, we can easily import any other C function, so why not try using the same one used in the original post..

# Use POSIX write
proc write(fd: cint, buffer: pointer, count: cint) {.header: "<unistd.h>", importc: "write".}

const
  pageSize = 4096
  yes = "y\n"
var buffer = ""

for i in 1..pageSize:
  buffer &= yes

while true:
  write(1, addr buffer[0], cint(buffer.len))

And the result..

$ ./yes | pv > /dev/null
... [7.16GiB/s] ...

That's it! Nim has succesfully achieved the same efficiency as the yes written in native C.

Although we managed to match the write speed, we also made our code less expressive. Luckily this is Nim, so a simple template will help cleaning this up while keeping the performance unimpacted!

# Use POSIX write
proc write(fd: cint, buffer: pointer, count: cint) {.header: "<unistd.h>", importc: "write".}

template fastWrite(str: string) =
  write(1, addr str[0], cint(str.len))

const
  pageSize = 4096
  yes = "y\n"

var buffer = ""

for i in 1..pageSize:
  buffer &= yes

while true:
  fastWrite(buffer)

And there we go! A seemingly simple problem that manages to highlight the beauty of Nim.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment