To continue my Functional Programming Sucks series of posts I will have a closer look at reduce().
I complained with Lodash (and Underscore) for different reasons. One complaint was performance, but I just read the code and presumed it was going to be slow without measuring. Then I complained with the performance of Functional Programming in general.
I thought it would be interesting to “improve” the Functional code with Lodash functions, and to my surprise (I admit I was both wrong and surprised) I found Lodash made it faster! After reading a little more about it I discovered this is a well known fact.
So, here are four different implementations of a function that checks if the elements (numbers) in an array are ordered (cnt is incremented if the array is sorted, such was the original problem).
// Standard reduce() this.test = function(p) { if ( false !== p.reduce(function(acc,val) { if ( false === acc || val < acc ) return false; return val; }, -1)) cnt++; }; // Lodash reduce(), and some other Lodash waste this.test = function(p) { if ( false !== LO.reduce(p,function(acc,val) { if ( false === acc || val < acc ) return false; // if ( !LO.isNumber(acc) || val < acc ) return false; return val; }, -1)) cnt++; }; // My own 4 minute to implement simpleReduce(), see below this.test = function(p) { if ( false !== simpleReduce(p,function(acc,val) { if ( false === acc || val < acc ) return false; return val; }, -1)) cnt++; }; // A simple imperative version this.test = function(p) { var i; for ( i=1 ; i < p.length ; i++ ) { if ( p[i] < p[i-1] ) return; } cnt++; }; // my own implementation reduce() function simpleReduce(array, func, initval) { var i; var v = initval; for ( i=0 ; i<array.length ; i++ ) { v = func(v, array[i]); } return v; }
The interesting thing here is that the standard library reduce() is the slowest.
However, my simpleReduce is faster than Lodash reduce().
(seconds) | reduce() | |||
Std Lib | Lodash | Simple | Imperative | |
Raspberry Pi v1 (ARMv6 @ 700) | 21 | 13 | 9.3 | 4.8 |
MacBook Air (Core i5 @ 1400) | 0.46 | 0.23 | 0.19 | 0.16 |
Conclusion
The conclusion is that from a performance perspective Functional Programming sucks. Lodash sucks too, but a little bit less so than the standard library (however, if you decorate all your code with isEmpty, isString, isNumber and that crap it will get worse).
That said, the generic nature of Lodash comes at a cost. The most simpleReduce() imaginable outperforms Lodash. As I see it, this leaves Lodash in a pretty bad (or small) place:
- Compared to the standard library it is an extra dependency with limited performance benefits
- The generic nature of Lodash comes at both a performance cost and it allows for sloppy coding
- A hand written reduce() outperforms Lodash and is a good excercise for anyone to write. I expect this is quite true also for other functions like take or takeRight.
- For best performance, avoid Functional Programming (and in this case the imperative version is arguably more readable than the FP reduce() versions)
Whats up with the Standard Library???
JavaScript is a scripted language (interpreted with a JIT compiler) that has a standard library written in C++. How can anything written in JavaScript execute faster than anything in the standard library that does the same thing?
First, kudos to the JIT designers! Amazing job! Perhaps the standard library people can learn from you?
I can imagine the standard library functions are doing some tests or validations that are somehow required by the standard, and that a faster and less strict version of reduce() would possibly break existing code (although this sounds far fetched).
I can (almost not) imagine that there is a cost of going from JS to Native and back to JS: that function calls to native code comes with overhead. Like going from user space to kernel space. It sounds strange.
I have read that there are optimizations techniques applied to Lodash (like lazy evaluation), but I certainly didn’t do anything like that in my simpleReduce().
For Node.js optimizing the standard library truly would make sense. In the standard library native code of a single-threaded server application every cycle counts.
UPDATE: I tried replacing parts of the above code: 1) the lambda function that is passed to reduce(), 2) the imperative version, with native code. That is, I wrote C++ code for V8 and used it instead of JavaScript code. In both cases this was slower! Obviously there is some overhead in going between native and JavaScript JIT, and for rather small functions this overhead makes C++ “slower” than JavaScript. My idea was to write a C++ reduce() function but I think the two functions I wrote are enough to show what is happening here. Conclusion: don’t write small native C++ functions for performance, and for maximum performance it can be worth to rewrite the standard library in JavaScript (although this is insane to do)!
All FP-sucks related articles
Functional Programming Sucks)
Underscore.js sucks! Lodash sucks!
Functional Programming Sucks! (it is slow)
Lodash Performance Sucks! (this one)
0 Comments.