Reputation: 6025
On the one hand, I read or hear that "function calls are expensive" and that they impact efficiency (for example, on Nicholas Zakas' Google tech talk).
On the other hand, however, it seems accepted that functions/methods are best kept short and should only really perform one task, as generally accepted in here.
Am I missing something here, or don't these two pieces of advice run contrary to one another? Is there some rule-of-thumb that allows one to maintain a Zen-like balance?
Upvotes: 58
Views: 15411
Reputation: 1018
To all: This has more the feel of a "comment". Acknowledged. I chose to use the space of an "answer". Please tolerate.
@StefanoFratini: Please take my note as building on your work. I want to avoid being critical.
Here are two ways to further improve the code in your post:
Can I match my bluster? Dunno. Here's a development of Stephano's code. It has flaws; I won't be surprised if someone tells me about it. And that'd be okay.
"use strict";
var a = function(val) { return val+1; }
var b = function(val) { return val-1; }
var c = function(val) { return val*2 }
var time = process.hrtime();
var reps = 100000000
for(var i = 0; i < reps; i++) { a(b(c(100))); }
time = process.hrtime(time)
let timeWith = time[0] + time[1]/1000000000
console.log(`Elapsed time with function calls: ${ timeWith } seconds`);
time = process.hrtime();
var tmp;
for(var i = 0; i < reps; i++) { tmp = 100*2 - 1 + 1; }
time = process.hrtime(time)
let timeWithout = time[0] + time[1]/1000000000
console.log(`Elapsed time without function calls: ${ timeWithout } seconds`);
let percentWith = 100 * timeWith / timeWithout
console.log(`\nThe time with function calls is ${ percentWith } percent\n` +
`of time without function calls.`)
console.log(`\nEach repetition with a function call used roughly ` +
`${ timeWith / reps } seconds.` +
`\nEach repetition without a function call used roughly ` +
`${ timeWithout / reps } seconds.`)
It is clearly a descendent of Stephano's code. The results are quite different.
Elapsed time with function calls: 4.671479346 seconds
Elapsed time without function calls: 0.503176535 seconds
The time with function calls is 928.397693664312 percent
of time without function calls.
Each repetition with a function call used roughly 4.671479346e-8 seconds.
Each repetition without a function call used roughly 5.0317653500000005e-9 seconds.
Like Stephano, I used Win10 and Node (v6.2.0 for me).
I acknowledge the arguments that
I'll hang my hat on the economic argument: My computer and the one before it each cost less than $400 (US). If a software engineer earns something like $90 to $130 per hour, the value of their time to their bosses is at a ratio of one computer like mine to three or four hours of their work. In that environment:
How does that compare to the dollars per hour a company loses when software it needs stops working?
How does that compare to lost good will and prestige when a paying customer temporarily can't use shrink-wrapped software produced by a business partner?
There many other such questions. I'll omit them.
As I interpret the answers: Readability and maintainability reign over computer performance. My advice? Write the first version of your code accordingly. Many people I respect say short functions help.
Once you finish your code and don't like the performance, find the choke points. Many people I respect say those points are never where you would have expected them. Work 'em when you know 'em.
So both sides are right. Some.
Me? I'll guess I'm off somewhere. Two cents.
Upvotes: 4
Reputation: 3819
Function calls are always expensive (especially in for cycles) and inlining doesn't happen as often as you may think
The V8 engine that ships with Node.js (any version) is supposed to do inlining extensively but in practical terms this capability is greatly constrained.
The following (trivial) snippet of code proves my point (Node 4.2.1 on Win10x64)
"use strict";
var a = function(val) {
return val+1;
}
var b = function(val) {
return val-1;
}
var c = function(val) {
return val*2
}
var time = process.hrtime();
for(var i = 0; i < 100000000; i++) {
a(b(c(100)));
}
console.log("Elapsed time function calls: %j",process.hrtime(time)[1]/1e6);
time = process.hrtime();
var tmp;
for(var i = 0; i < 100000000; i++) {
tmp = 100*2 + 1 - 1;
}
console.log("Elapsed time NO function calls: %j",process.hrtime(time)[1]/1e6);
Results
Elapsed time function calls: 127.332373
Elapsed time NO function calls: 104.917725
+/- 20% performance drop
One would have expected the V8 JIT compiler to inline those functions but in reality a
, b
or c
could be called somewhere else in the code and are not good candidate for the low hanging fruit inlining approach you get with V8
I've seen plenty of code (Java, Php, Node.js) having poor performance in production because of method or function call abuse: if you write your code Matryoshka style, run time performance will degrade linearly with the invocation stack size, despite looking conceptually clean.
Upvotes: 1
Reputation: 150010
My rule of thumb is that it's time to break a function into smaller pieces if it is more than a screen-full of lines long, though many of my functions just naturally end up somewhat smaller than that without being "artificially" split. And I generally leave enough white-space that even a screen-full isn't really a whole lot of code.
I try to have each function do only one task, but then one task might be "repaint the screen" which would involve a series of sub-tasks implemented in separate functions that in turn might have their own sub-tasks in separate functions.
Having started with what feels natural (to me) for readability (and therefore ease of maintenance) I don't worry about function calls being expensive unless a particular piece of code performs badly when tested - then I'd look at bringing things back in-line (particularly in loops, starting with nested loops). Though having said that sometimes you just know that a particular piece of code isn't going to perform well and rewrite it before getting as far as testing...
I'd avoid "premature optimisation", particularly with languages that use smart compilers that might do those same optimisations behind the scenes. When I first started C# I was told that breaking code up into smaller functions can be less expensive at run-time because of the way the JIT compiler works.
Going back to my one screen-full rule, in JavaScript it is common to have nested functions (due to the way JS closures work), and this can make the containing function longer than I'd like if I were using another language, so sometimes the end result is a compromise.
Upvotes: 4
Reputation: 106375
In a perfect world, where there's no bugs (because code just fixes itself magically), and requirements are frozen from the day one, it may be possible to live with huge omnipotent functions.
But in this world it turns to be just too expensive - and not only in terms of 'man-month'. Nicholas Zakas wrote a brilliant article describing most of the challenges software developers face these days.
The transition may seem somewhat artificial, but my point is that 'one function - one task' approach is much more maintainable and flexible - in other words, it's what makes BOTH developers and customers happy, in the end.
It doesn't mean, though, that you'd not strive to use as few function calls as possible: just remember that it's not a top priority.
Upvotes: 13
Reputation: 340693
The general rule applying to all languages is: keep functions (methods, procedures) as small as possible. When you add proper naming, you get very maintainable and readable code where you can easily focus on general picture and drill down to interesting details. With one huge method you are always looking at the details and the big picture is hidden.
This rule applies specifically to clever languages and compiler that can do fancy optimizations like inlining or discovering which methods aren't really virtual so double dispatch isn't needed.
Back to JavaScript - this is heavily dependant on JavaScript engine. In some cases I would expect decent engine to inline function, avoiding the cost of execution, especially in tight loops. However, unless you have a performance problem, prefer smaller functions. Readability is much more important.
Upvotes: 51