Notes from Porting Node


These are my notes from a talk given by Tim Caswell on July 3, 2012 at NodeConf Portland. You can follow Tim at @creationix, and these slides are available here:

Before I was married, I was kind of a nomad and I would spend eight weeks each year teaching canoeing at a summer camp and I was disconnected from the internet.

Last fall, the WebOS thing was kind of in limbo; V8 is a great VM but kind of heavy for a phone. As of node 0.6, there's this awesome library called libuv. Why not port libuv to LuaJit? I' called it Luvit. It also includes http_parser, openssl, zlib and some others. I hacked on this a lot.

Why did I decide to do this? Lua is a slightly simpler language and it's a lot lighter weight. You can easily have a process under 1MB, whereas node starts at 10MB. I'd also recently done nodeknockout where I did a lot of C++ porting WebGL to node, but I prefer C. Lua has coroutines (an alternative to callbacks) and fast FFI built in.

Learning libuv
I cloned the libuv and read the header file (include/uv.h), joined #libuv on IRC and I joined cloud 9 at around the same time as two of the main contributors of libuv.

Learning Lua
It's not hard if you know JavaScript. I bought the Programming in Lua 2nd Edition and learned the lua-1 mailing list, joined #lua on IRC.

Now, I needed to see if I could port node's APIs onto another language. Can I keep these same APIs, or modify them slightly? Lua, for example, has tables instead of objects. What they do have is metatables, which is close to a harmony proxy. Using metatables you can implement prototypal inheritance. Anything can be used as keys, including tables and functions, and tables may not contain nil for keys or values. Oh and there's no 'this' rather there is a colon syntax

Remember the coroutine thing I was talking about? I played with this and wrote a sugar library which I unfortunately called fiber. Even though they're native to Lua, I don't like suspending the main thread. I don't like that. If you want to use coroutines, you have to start a new fiber, and inside that fiber you can convert asynchronous functions into "fake" synchronous functions. You can use this to write synchronous-style code like sleep(1000) which blocks your coroutine but not the main event loop.

Now, we need to bind libuv to the language. There's two ways in the LuaJit engine: you can use the C api or you can use FFI, which is very very fast. FFI is the foreign function interface, where you can call a C library without binding to it. I can feed it the header file for OpenGL, and call it from inside your script. It's extremely fast, great!

Unfortunately, you can't use callbacks in FFI if you pass structures by value. I have no idea what that means but whatever. I started writing this all in C, which is great because Rackspace wanted to use Luvit in production (and they do today) and they didn't use FFI.

Memory management is hard. You have to do it manually in C. You also have to do it in Lua. Node had to manually reference callbacks and then remember to free it after it called the function. These are things we take for granted, but when you're writing it you have to handle all of it. Libuv has done a bunch to help with this, although I haven't been able to pull them into Luvit yet.

Now, I have over 12,000 lines of C code. That's just integration with Libuv, not even zlib, http, or anything else! We also need something to parse JSON. So much work! How do I get it released?

Saved by the community. Rackspace and the community reached out and helped complete the openssl binding and everything else necessary to release Luvit. The community has helped this become a successful project. I could not have done it on my own. Luvit was 4x faster than node, and then we added more features. It still uses 20x less RAM. This is what Rackspace uses it for. They can run it in their customers' VMs without consuming enough RAM that their customers get angry. We make releases! This is a real project now! Except I still work at node all day, because it's awesome and it's my job.

What did I learn? One thing, Lua is very similar to JS. I learned that not all VMs have to be dog slow when calling C code, which is good. Performance is weird! Every use case is different. I'm no longer looking for coroutines in JavaScript. Streaming JSON is really cool. Small codebases are good. The biggest dependency was LuaJit, which built very fast. I can build Luvit in 4 seconds. Now that we've added features it's a little slower. Also, Lua's module system is kind of wanky so I copied node's system.

Open source collaboration makes it possible to build anything.

Did you enjoy this post? Please spread the word.