- Back to basics - Binary search
- Back to basics - Bubble sort
- Remote machine development
- WebAssembly In The Browser
- WebAssembly Brief Intro
- The Programming Metagame
- Raspberry Pi Zero Cluster
- Goodbye Ghost
- Download Mac Os X El Capitan from Mojave
From procedural to functional with nim
Being a multiparadigm programming language, nim lets you code in many different styles, in this post I show you how I went from a procedural code to a functional(ish) one. Making the code gain readability and expressiveness, without any performance penalty.
Thee following it's a subset of the nim implementation of my raybench project.
proc main() = var data = newSeq[seq[V3]]() let world = world_new() let vdu = (world.camera.rt - world.camera.lt) / float32(WIDTH) let vdv = (world.camera.lb - world.camera.lt) / float32(HEIGHT) randomize() for y in 0..(HEIGHT-1): var row = newSeq[V3]() for x in 0..(WIDTH-1): var color = zero var ray:Ray ray.origin = world.camera.eye for i in 1..SAMPLES: ray.direction = ((world.camera.lt + (vdu * (float32(x) + float32(random(1'f32))) + vdv * (float32(y) + float32(random(1'f32))))) - world.camera.eye).unit color = color + trace(world, ray, 0) color = color / float32(SAMPLES) row.add(color) data.add(row) writeppm(data)
proc main() = let world = world_new() let vdu = (world.camera.rt - world.camera.lt) / float32(WIDTH) let vdv = (world.camera.lb - world.camera.lt) / float32(HEIGHT) let ss = toSeq(1..SAMPLES) let hs = toSeq(0..<HEIGHT) let ws = toSeq(0..<WIDTH) randomize() hs.map(proc (y:int): auto = ws.map(proc (x:int): auto = foldl(ss.mapIt( trace(world, ( world.camera.eye, ((world.camera.lt + (vdu * (float32(x) + random(1.0)) + vdv * (float32(y) + random(1.0)))) - world.camera.eye).unit ), 0)), a + b) / float32(SAMPLES) ) ).writeppm
The comparison shows a decrease in the number of characters used to describe the same computation.
Performance wise, the results are as shown (best out of three, running on a Core-i7 6700):
$ time ./nimrb real 0m25.924s user 0m25.781s sys 0m0.125s
$ time ./nimrb_map real 0m25.016s user 0m24.828s sys 0m0.156s
There seems to be a very small improvement in speed, but I wouldn't make much of it.
While changes in the code improved readability, it also allowed me to make another small improvement. Using the Parallel Map function described here: http://blog.ubergarm.com/10-nim-one-liners-to-impress-your-friends/ and changing a single line from this:
hs.map(proc (y:int): auto =
hs.pMap(proc (y:int): auto =
We are taking advantage of nim's threadpool library and, with it, of all the resources available from the current CPU, improving the running speed.
$ time ./nimrb_pmap real 0m8.884s user 1m4.156s sys 0m0.250s
Making the code run almost 3 times as fast as the single threaded version, with no significant changes to the code.
I find this very interesting, and will be looking forward to keep playing around with nim some more.