Nested helper functions and performance

2020-07-09 09:46发布

问题:

Nested helper functions can be useful for making your code more understandable. Google even recommends using nested functions in their style guide. I'm wondering about the instantiation of these nested functions and performance. For example,

work(1);
work(2);

function work(a) {
    // do some stuff
    log();
    // do some more stuff
    function log() {
        console.log(a);
    }
}

work is instantiated once, but is log instantiated twice?

If log is instantiated every time work is executed, would it generally be recommended not to nest functions? Instead, write code like the following

work(1);
work(2);

function work(a) {
    // do some stuff
    log(a);
    // do some more stuff
}

function log(a) {
    console.log(a);
}

These examples are overly trivial and the question is more about the general case.

回答1:

work is instantiated once, but is log instantiated twice?

Yes, on each call to work.

would it generally be recommended not to nest functions?

Why not? I presume you're hinting at performance issues.

Whether a practice is good or bad depends on your reasons for using it. In the case of simple helpers, it's good to keep them local because it means you can make them suitable just for your special case and not worry about the extra cruft of a general function. E.g. to pad a number with a leading zero:

function pad(n) {
  return (n<10? '0' : '') + n; 
}

works very well as a helper where n is expected to always be in the range 0 to 99, but as a general function is missing a lot of features (dealing with non–number n, -ve numbers, etc.).

If you are concerned about performance, you can always use a closure so the helper is only instantiated once:

var work = (function() {

  function log() {
    console.log(a);
  }

  return function (a) {
    // do some stuff
    log();
    // do some more stuff
  };
}());

Which can also make sense where log is used by more than one function within the closure.

Note that for a single case, this is very much a micro optimisation and not likely to deliver any noticeable difference in performance.



回答2:

Nested function-objects are instantiated and added to the LexicalEnvironment created when an enclosing function is run. Each of these nested functions will also have a [[Scope]] property created on them. In addition when a nested function is run, a new LexicalEnvironment object will be created and the [[Scope]] copied to its outer property.

When the enclosing function completes, then the nested function-object and its associated memory will be eligible for garbage collection.

This process will repeat for every call to the outer function.

Contrast this with your second implementation, where the function-object need only be created once; likewise its garbage collection.

If this is a "hot" function (i.e. called many times) then the second implementation is infinitely preferable.



回答3:

RobG is right. Performance us affected by instantiating functions for each work thread. Whether it is a noticeable problem or not really comes down to how many simultaneous active working threads you have, as this affects memory consumption as well as execution speed.

If performance is a big issue on your application (e.g. a complex, heavy function) and you only want to use the function in one place, closures are the way to go.

If the function you're calling from "work" is to be used from several parts of your code, it's better to keep them separate instead of nesting them. This makes to keep the code updated simpler (as you only update it in one place).

Most JS engines parse code only once (even for nested functions) so the work involved in instantiating functions is not a big issue.

Memory usage, on the other side, can be an issue if you have many nesting levels as well as several simultaneous threads or event listeners, so nesting should be managed carefully in these cases (on large-scale applications).



回答4:

Yes. Instantiation occurs each time you invoke the nested functions. Instantiating functions does use CPU time but it's not as important as parsing.

So, for the general case (not the case now that you mention that your functions will be invoked many times per second), parsing time is more relevant than instantiation.

In this case, nesting functions will use a lot of memory and CPU time,CSO it's best to use RobG's solution (closures), where functions are instantiated once and they are simply called, causing less memory usage.

If you want more optimized code in this critical piece of code, you should try to use as few functions as possible as this will work faster, albeit at the expense of losing code readability and maintainability.