The programs below calculate the rsync weak sum (of block size 4096) of the last block of the data available on stdin.
The data is assumed to be at least block size big; the weak sum is calculated in a direct manner for the first block, and from that on, the sum is adjusted on each incoming byte using the "rollability" of this kind of checksum.
The primary goal was to see the performance of naive implementations; so my metrics was both performance and "how close is the code to be possible to call it executable pseudocode". (For Python and Ruby, naivity is somewhat compromised by my compulsion of writing code which runs with both of their major versions (respectively), but hey, my compulsion reflects a real world situation, which in fact does compromise the easy use of these languages!) So I did not go for using numerical libraries, or mutable monads in case of Haskell.
However, I'd be happy to see contributions which either add other language implementations or improve on performance by any kind of black
magic. If you code up something, to test it for correctness, take a file larger than the block size, then feed various tails of it to the
program like tail -c $n test-file | rols
; for $n
≥ block size the sum (the second number in the result) should be invariant (and of course,
match the values seen with the other implementations :) ).
- Go is the clear winner by the combined metrics. In pseudocodiness, it wins over Python/Ruby due to the above mentioned compat glue in them and also because of those ugly modulus operations. Performance is in par wih C.
- C rocks, no surprise for such a task.
- Python/Ruby sucks, no surprise. Well, tried with Rubinius as well, and that sucks more than expected.
- Haskell is ubercool...
and ubersucks. Despite my naivity-compatible efforts for optimization (use ByteString, consume input via the strict fold variant, use O(1)-appendable deques for the rolling window) I ended up with horrible performance, stack overflow, or consumption of all system memory (in such a mystic way which is worth to elaborate on: got the stack overflow on a 400k input, but not on a 4M one, that just ate memory w/o stack overflow). Thanks to Balázs Kőműves' scrutine eyes, the Haskell code can be fixed by enforcing some strictness by means of bang patterns, by which it ends up with a run time somewhere below that of LuaJIT.
I'm not writing exact numbers, see for yourself, this is not the "do not try it at home" type of thing.
Also added Javascipt (node.js), Lua and Perl version.
- Ruby is twice as fast as Python, Lua and Perl are twice as fast as Ruby, Node is twice as fast as Lua/Perl; however, the speed king of
interpreteddynamic languages is the LuaJIT implementation of Lua, being twice as fast as Node. - OK, this was a bit of oversimplification:
- Ruby is twice as fast Python3, actually. Python 2.x is faster than Py3, somewhere between Ruby and Python3.
- Node could be the speed king, being twice as fast as LuaJIT, weren't it a dumbass piece of crap who does not know math. If we
used the built-in
%
modulo operator, it would be like that speed-wise, just we'd get an incorrect result because of JS arithmetics' weird signedness. So we need to define our modulo function using >>> 0 to sort of "cast to unsigned type". This function quite holds Node back. To make this weird shit even more weirder and shittier, if you were to add the amended modulo funcion as a Number method, Node will slow down beyond imagination (OK, method dispatch takes some time, but that much??). - So, that incorrect super-fast Node speed would be about fourth times slow than Go/C.
Checked JRuby. Twice as fast as Matz Ruby, somewhere in between Lua and Perl. That's a bit of disappointment, in the light of their agenda about the JVM and JIT.