Search code examples
javascriptv8

Does Javascript (V8) optimizes unused return values


Let's say we have a function like so:

function foo() {
  // do some work...
  return () => {}; // foo returns a function
}

The client code can use foo in two scenarios:

  1. Use the result of the function
const result = foo();
// some code that uses result...
  1. Ignore the result of the function
foo();

I wonder if the runtime (I don't want to refer to the language itself because it's likely that this is implementation-dependent) will optimize the 1rst case so I don't have to do it myself like this:

function foo(needTheResultValue = false) {
  // do some work...
  if (needTheResultValue) return () => {};
  // nothing is returned if the caller didn't ask for it
}

Solution

  • V8 developer here. The short answer is "it depends, don't worry about it".

    In general, V8 (and other engines, as far as I'm aware) optimizes on a per-function basis. So, in your example, if and when foo is optimized, it doesn't know whether its return value will be used or ignored, so it can't optimize it away.

    The exception to this is inlining: the optimizing compiler has the ability to inline called functions when the calling function is optimized, e.g. in this example:

    function foo() {
      // Do some FOO work...
      return {};
    }
    function bar() {
      foo();
      // Do some BAR work...
    }
    

    When foo is optimized, it will (continue to) return a freshly allocated empty object, regardless of where it's called from. When bar is optimized, the compiler might decide to inline foo, and after that step it sees (its own internal representation of a hypothetical function like):

    function bar() {
      // Do some FOO work...
      {};
      // Do some BAR work...
    }
    

    And then it can easily drop the unused object allocation in there.

    That said, as comments on the question have pointed out, this isn't something you need to worry about. (Unless if you happen to needlessly spend huge amounts of time constructing expensive but unused return values -- but that seems unlikely, because it's a fairly obvious inefficiency, so chances are you wouldn't write such code in the first place.)

    In particular, returning some value that you have computed anyway has zero cost compared to returning nothing, because every function always returns something -- if it doesn't have a return statement, then the engine will quietly insert a return undefined; for you. That means function f1() {} and function f2() { return undefined; } will compile to exactly the same code. And if you decided to write something like:

    function overly_clever(need_result) {
      let result = do_some_work();
      if (result < 0) handle_error();
    
      if (need_result) {
        return result;
      } else {
        // Return nothing.
      }
    }
    

    Then that function would be ever so slightly (not measurably) slower than if you replaced everything after the empty line with a simple return result, because the "empty" else-branch (with its automatically inserted return undefined;) is no faster than return result, so evaluating the need_result condition is a waste of time.

    So, in short: don't worry about it. Write code that makes sense, let the engine take care of optimizing it.

    (For completeness: if you do feel a need to do manual optimization work, let it be guided by measuring: profile your app to see where most time is being spent, and measure the effects of any attempted changes to see if they are effective. Don't use microbenchmarks, because they tend to be misleading.)