Thread overview
Performance of default and enum parameters
Feb 07, 2018
jmh530
Feb 07, 2018
H. S. Teoh
Feb 08, 2018
jmh530
February 07, 2018
I'm a little curious on the impact of default and enum parameters on performance.

In the simple example below, I have foo and bar that return different results depending on a bool. Foo is a template with check as a template parameter and bar is a normal function. foo creates two different versions so it never needs to perform a run-time evaluation of check.

However, I was a little curious about bar. Obviously if check is passed as either true or false, then the if statement has to be run, but I wasn't sure what happens if check is not passed or if check is known at compile-time.

If check is not passed, I think it depends on how default function arguments work. I could imagine that it works in two ways: 1) if you call bar(x), then the compiler effectively re-writes it to bar(x, true), which would mean that the if statement must run, 2) the compiler creates functions int bar(int x) and int bar(int x, bool check), where the first has the check optimized away and the second is as normal, calling bar(x) means that the optimized one is called.

If check is known at compile-time, I would think that the compiler could optimize away the if statement because it is known at compile-time which part will be there or not.

int foo(bool check = true)(int x)
{
    static if (check)
        return x;
    else
        return x + 1;
}

int bar(int x, bool check = true)
{
    if (check)
        return x;
    else
        return x + 1;
}

enum bool val = false;

void main()
{
    import std.stdio : writeln;

    int x = 1;

    writeln(foo(x));        //two versions of foo
    writeln(foo!false(x));  //two versions of foo

    writeln(bar(x));        //does run-time if happen?
    writeln(bar(x, true));  //if statement at run-time
    writeln(bar(x, false)); //if statement at run-time

    writeln(foo!val(x));    //same as foo!false
    writeln(bar(x, val));   //does run-time if happen?
}
February 07, 2018
On Wed, Feb 07, 2018 at 11:04:29PM +0000, jmh530 via Digitalmars-d-learn wrote: [...]
> If check is not passed, I think it depends on how default function
> arguments work. I could imagine that it works in two ways: 1) if you
> call bar(x), then the compiler effectively re-writes it to bar(x,
> true), which would mean that the if statement must run, 2) the
> compiler creates functions int bar(int x) and int bar(int x, bool
> check), where the first has the check optimized away and the second is
> as normal, calling bar(x) means that the optimized one is called.

Default arguments mean precisely that: if you don't specify that parameter, the compiler inserts the default argument for you. IOW:

	int bar(int x, bool check = true) { ... }
	bar(1);		// rewritten as bar(1, true);


> If check is known at compile-time, I would think that the compiler could optimize away the if statement because it is known at compile-time which part will be there or not.
[...]

Default arguments are a syntactic construct. They have little to do with what optimizations are done by the compiler. A function without default arguments can be optimized the same way as a function with default arguments.  Whether or not something is optimized away is more dependent on the optimizer than on the syntax used to write the code.

If you want to know for sure, check the assembly output, e.g., of ldc. The last time I checked, ldc is able to inline and optimize away even nested loops (up to a certain complexity), needless to say a simple boolean if-statement like your example code.  This was in the context of certain proposed Phobos optimizations, and it was funny because we were getting strange benchmark results like 0ms for the entire function when compiled with ldc. Disassembling showed that what actually happened was that ldc determined that since the return value of the function was never used, the entire function call could be deleted completely.

In another case, because the function arguments were known at compile-time, ldc executed the function at compile-time and substituted the function call with just loading the return value directly into a register, so the function was never actually called.

Basically, if you use a compiler with an aggressive optimizer like ldc's, don't waste your time with micro-optimizations. The compiler will do it for you.  Trying to hand-optimize your code can sometimes backfire -- the code can become too strange for the optimizer to understand, so it gives up and ends up *not* optimizing it at all.

In this day and age, the only time you actually need to hand-optimize is when a profiler has identified real hotspots, and usually the fix is relatively simple. (And IME, the real hotspots are often NOT where I predict them to be. So optimizing stuff before I have real data from a profiler is usually a waste of my time.)


T

-- 
Public parking: euphemism for paid parking. -- Flora
February 08, 2018
On Wednesday, 7 February 2018 at 23:18:53 UTC, H. S. Teoh wrote:
> [snip]

Appreciate the detailed reply. I had tried to look at the assembly with that tool recently discussed in the announce thread, but there was so much extra stuff reported that I threw up my hands.