"Function calls are expensive" vs. "Keep functions small"

JavascriptPerformance

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

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.

Solution 3 - Javascript

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.

Solution 4 - Javascript

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.

Solution 5 - Javascript

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:

  • Use both halves of the tuple coming from process.hrtime(). It returns an array [seconds, nanoseconds]. Your code uses the nanoseconds part of the tuple (element 1) and I can't find that it uses the seconds part (element 0).
  • Be explicit about units.

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

  • "For perspective, in a nanosecond (a billionth, 1e-9), light travels roughly 12 inches."
  • "We're only talking about small numbers of nanoseconds (47 to 5), so who cares about percentages?"
  • "Some algorithms make zillions of function calls each second, so it adds up for them."
  • "Most of us developers don't work with those algorithms, so worrying about the number of function calls is counterproductive for most of us."

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNickView Question on Stackoverflow
Solution 1 - JavascriptTomasz NurkiewiczView Answer on Stackoverflow
Solution 2 - Javascriptraina77owView Answer on Stackoverflow
Solution 3 - JavascriptnnnnnnView Answer on Stackoverflow
Solution 4 - JavascriptStefano FratiniView Answer on Stackoverflow
Solution 5 - JavascriptBaldEagleView Answer on Stackoverflow