Mr. Mr.
Mr. Mr.

Reputation: 4285

Are void methods at their most basic faster/less of an overhead than methods that return a value?

I cannot find a similar question/answer either here on SO or on the internet, and as useless as this question maybe it is a question that came to my mind whilst doing some reading on MSIL. I am very curious to understand how IL operation execution works in my scenario, even if this is not a practical question I have no one else to ask.

Premise:

Bearing in mind that the execution of both MSIL commands and functions is done in three steps:

  1. Push command operands or function parameters onto the stack.
  2. Execute the MSIL command or call function. The command or function pops their operands (parameters) from the stack and pushes onto the stack result (return value).
  3. Read result from the stack.

Steps 1 and 3 are optional. For example, the void function doesn't push a return value to the stack.

I understand that it is what a method does that determines how much 'processing-power' is required, but for the sake of my curiosity lets consider these two very basic methods:

First method:

void Method1()
{
  var result = 1+1;
}

Second method:

int Method2()
{
  var result = 1+1;
  return result;
}

Question:

Because the void method doesn't push a return value (or is there an implicit return) does this mean that it requires less overhead when executing as opposed to the second method?

Upvotes: 4

Views: 2181

Answers (2)

Jon Hanna
Jon Hanna

Reputation: 113242

All things being even, doing something is going to be slower than not doing something.

Note though, that the implementation of the first method is going to be turned into:

void Method1()
{
}

After inlining, it isn't even called (it's just about possible that the call gets replaced by a read of a field, if this is an instance method on another object, since the optimisation can't stop the NullReferenceException() it should throw if called on null. If it's static, called from the same object, or the compiler determines the object can't be null, then not even that. Zero code.

Meanwhile, the second method will be turned into:

int Method2()
{
  return 2;
}

After inlining, a call that uses the value would be turned from e.g:

x = Method2();

To:

x = 2;

If the value isn't used, then not even that; it'll be exactly the same as the first.

This sort of thing going on means that even in cases where calls aren't inlined, such tiny differences as whether or not something is returned will mostly be lost as not even noise against the bigger factors, and also can happen counter-intuitively (by affecting whether or not some optimisation path is taken in some obscure way). Example: Value is produced, it's "returned" by putting it in a register, but maybe it was there anyway upon calculation, maybe it's next use works on it directly there; hence zero cost to "return" it.

Upvotes: 3

Jim Mischel
Jim Mischel

Reputation: 133995

Remember, though, that MSIL is not executed or even interpreted. MSIL is an expression of the compiled code as it would be executed on the virtual machine. But the JIT compiler converts MSIL to machine code (x86, etc.). The x86 is fundamentally different from the stack-based virtual machine.

At the most basic level, functions return values in registers. Assuming that the registers are large enough. Let's stick with 64-bit values (references, long integers, doubles, and smaller value types). Those can be returned in the RAX register. In your trivial example, there would be no difference in the generated machine code for the two functions. That is, Method1 would be:

mov rax, 1
inc rax
ret

(And, yes, I know that a smart compiler would collapse 1+1 to 2. Let's assume that there was a memory access there, okay?)

Method2 would be identical because the value to be returned is already in the RAX register.

So when you're working with 64 bit or smaller quantities, the difference between a method that returns a value and a method that doesn't return a value will most often differ, if at all, only in whatever instruction it takes to load the return value into the proper register. This is typically a single mov instruction. It's going to depend on the order in which things are done, and also on how well the compiler and JITer optimize things.

Upvotes: 6

Related Questions