Search code examples
javascriptmicro-optimization

Micro optimization: Returning from an inner block at the end of a function


In languages such as javascript or (maybe?) lua, all functions by default are treated as if they had a return statement at the end:

function() {
    // do
    return;
}

Is equal to

function() {
    // do
}

I'm wondering if returning from an inner block at the end of the function changes anything in the core, the compiling process, the VM.

function() {
    if (condition) {
        return;
    }

    // end of function
}

Same question applies to breaking a loop:

function() {
    for ( loop ) {
        return;
    }

    // end of function
}

Does the machine "look" for anything when a loop is broken, or a condition check has ended?

This is not a stylistic question, please don't tell me to make code readable.


Solution

  • TL:DR / optimization advice: you don't need to do anything special to gain performance. if(condition) return inside an inner loop is typically at least as efficient as an if(condition)break; to reach the end of the function.

    Putting nested loops inside a function so you can use a return as a multi-level break is a good way of being able to express the logic efficiently without a goto, easy for humans and easy for compilers/interpreters.

    Making loop conditions more complicated to avoid multiple return statements in one function is not helpful for performance.


    Generally no, a return in the middle of a function is not fundamentally different or more or less efficient than reaching the implicit return at the end. And an explicit return at the bottom of a function isn't special either.

    (We're talking about void functions that never return a value, I assume. Obviously returning a value is different from not returning a value.)

    Restructuring your code to break out of a loop and reach the implicit return at the bottom of a function is not more efficient (but could easily be less efficient in some interpreted languages, especially if they aren't JITed.) e.g. if the interpreter does a jump within the function and then has to do another jump. (A good ahead-of-time compiler or JIT optimizer could see what was happening and make good machine code anyway.)

    Some compilers / interpreters may handle a return by just jumping to a common block of cleanup (epilogue) that all return statements share. But tail-duplication is possible: when compiled to machine code, a function can have multiple copies of epilogue + ret instructions, reachable from different paths.

    (JavaScript implementations do typically JIT functions to machine code; IDK about LUA. And of course they can inline functions. return statements in functions that get inlined can be just plain jumps, or could get optimized away entirely.)