Some way to optimize functions without side-effects?

Hello,

it's about functions without any side effect..

I know, that compiler are allowed to do some optimizations in the creation of return values, which change program behaviour.

1
2
3
4
5
6
7
8
struct Foo { Foo(){ cout << "foo"; } };

Foo foo() { return Foo(); }

int main()
{
    Foo f = foo();
}


May print "foo" one or two times on the screen. As far as I know, unfortunately it is not allowed for the compiler to detect that "f" is unused, ommiting it completely and not print "foo" at all. Of course, in some cases this is a good thing. RAII would not work anymore..

But in some places I would like to tell my hard-working compiler: "It's fine. no RAII here.. There are no side effects at all! Don't hold back. Go for it! Optimize everything.."

The same seems to become worse for parameter construction calls. Consider this:

1
2
3
4
5
6
7
8
struct Bar { Bar(){ cout << "bar"; } };

void bar(Bar b) {}

int main()
{
    bar( Bar() );
}


In this case, it is not allowed for a compiler to recognize that bar does nothing, inline it and finally throw away the function call and parameter construction completely and NOT printing out "bar". But why not?

Anyone know (probably non-portable) tricks to tell the compiler that he may safely skip parameter constructions and unused object creation like the one above? (Especially for the VC compilers)? Of course, a portable one involving some template or macro magic would be appreciated too :-D


By the way: It's not only about the constructor. Consider this example:

1
2
3
4
5
6
7
8
struct Baz { Baz operator!(){ cout << "baz"; return *this; } };

void baz(Baz b) {}

int main()
{
    baz( !Baz() );
}


Not only the constructor should be "skipable", but the whole parameter construction, including construction of "unused parameters". I know that there is probably a lot of code which doesn't work in presence of such optimization. So if there's a trick to enable this for only some parts of the code, this would be even more appreciated.. (Compile-unit granularity would be perfect)

If there is nothing alike, wouldn't this make a very good addition to the standard? Something like an "optimizable" keyword or "nosideeffects" to parameter/functions/classes?

1
2
3
4
5
6
7
8
9
10
11
// Compilers are allowed to *not* create Foo objects or allowed to not
// call member functions of Foo if he detects that the results are not used
optimizable struct Foo {...};

// Compilers are allowed to omit the call to this function, if the return
// value is unused
optimizable int bar();

// Compilers are allowed to not create the first parameter, if it turns out
// to be unused in the function
int baz(optimizable Foo f, int b);


Ciao, Imi.

PS: For cl.exe, I've looked through the compiler options and didn't find any obvious switch, except maybe "/LTCG" but this looks like something less harmful...
Last edited on
I'm pretty sure what you are asking for is for the compiler to violate the C++ standard, in which
case, no, it is not possible to do that.

The reason (non-copy) construction cannot be elided is because construction may have external
side effects that, if optimized out, would cause the program to not work correctly.

In all of your examples above, optimization can be achieved by creating an overload of the
functions that take no parameters.

(It is not always possible to do that, esp when dealing with virtual functions where certain
overrides may not need all of its parameters).


Wait a moment....

1
2
3
4
5
6
7
8
struct Foo { Foo(){ cout << "foo"; } };

Foo foo() { return Foo(); }

int main()
{
    Foo f = foo();
}


May print "foo" one or two times on the screen


Two times? I only see it happening once.



// Compilers are allowed to omit the call to this function, if the return
// value is unused


Here's where I'm confused....

If the call can be omitted, why do you even call it?
Last edited on
You can stop the compiler doing optimizations like that by using the volatile qualifier.
jsmith: Yes, I am also pretty sure that what I am asking for would not work in a lot of cases. But I am also saying that there are cases where this optimization makes perfect sense (i.e. functions/object that don't have any side-effects) and I was hoping for some nifty "oh, just use __hidden_optimization for VC and __my_unknown_super_inline for gcc" or something like that. Well, at least I tried... :-D

The "overloading with no parameter" has the problem that now the caller of the function has to decide whether the function has no side effects (by calling the parameter-less version or not calling the function at all). It's exactly what I was trying to achive: To move the optimization responsibility to the function that is being called.


Disch: I am sorry, you are completely right. Only one call to the standard constructor takes place.

What I meant is, that the compiler are allowed to omit calls to the *copy constructor*, not the standard constructor. My mistake.

1
2
3
4
5
6
7
8
struct Foo { Foo(const Foo& f) { cout << "foo"; } };

Foo foo() { return Foo(); }

int main()
{
    Foo f = foo();
}


Here, although the copy constructor has a side effect (printing "foo" to the console), the standard allows for compilers to not call the copy constructor as many times as one should expect. It *should* be called at least twice (one time copying the anonymous Foo() object within the function foo() into the return value and the second time when copying the return value to the variable f in the function main(). However, compilers are free to not call the copy constructor twice (or even not at all)
I'm still a little confused as to what you're actually asking about.

The compiler can optimize away copy ctor calls.

And there's no point (that I can see) to optimize away non-copy ctor calls. Instead of asking the compiler to optimize away a useless object... why don't you just not create useless objects?

I must be missing something here. Can you show me a practical example of a situation where this would be useful? I'm just having a hard time connecting the dots on this one.

The only situation in which I can see this being useful is the "old" object returned from the postfix ++ and -- operators... except those can make use of the copy ctor and can thus be optimized out (provided the function is inlined, which it typically should be)
Disch: I was implementing a template that used template parameter classes' functions to customize behaviour (policy template pattern). However, some of the parameter to the functions where not necessary in some cases. Yet even if the policy function was empty, the compiler still generated code for constructing the parameter to the policy functions.

much simplified:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct SomePolicy
{
    void beforeWrite(const Environment&) { /* something that doesn't need environment */ }
};
struct SomeOtherPolicy
{
    void beforeWrite(const Environment& e) { /* something that uses environment */ }
};


template<class T>
struct Engine
{
    T policy;
    ...
    policy.beforeWrite(createEnvironment()); // createEnvironment is somewhat costly
    ...
};



Well, I somewhat "solved" the problem by testing first whether the policy has a function beforeWrite() taking no argument and calling this function instead. But the code looks clumsy and complicated. And the error messages in case something goes wrong are awefull..

(Specializing the template for the simple beforeWrite-method doesn't scale with more than one policy. You would have to create an exponential number of specializations - in theory one for each combination - if you don't want to make assumptions about coupling of different policy functions. We have 3 functions in policy, so this would already make 8 specializations).


I am aware that the example looks strikingly similar to polymorphism and virtual functions. In this case, I think it is not possible for the compiler to optimize any call (as he don't know which function will be called at compile time). However, with the templates here, the compiler *could* omit the call to createEnvironment() if he detects, that a) the parameter is unused in beforeWrite() anyway and b) I told him that createEnvironment has no side-effects.

Ciao, Imi.
Interesting situation. I see now why that would be useful.
Well, while I couldn't find a compiler switch as I original hoped, the following solution works reasonably well for my last scenario with the template engine. The base idea is to pass functors which obtain the parameter values instead of variables to the policy classes. If the parameter is not used by the implementation of the policy, it just doesn't call the functor and the costly parameter creation is never executed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct Environment {};
struct SomePolicy
{
    template<class T>
    void beforeWrite(const T&) { /* if you don't need the parameter, just ignore it. */ }
};
struct SomeOtherPolicy
{
    template<class T>
    void beforeWrite(const T& t)
    {
        // if you need the parameter, obtain it by calling t like a function
        const Environment& e = t();
    }
};


template<class Policy>
struct Engine
{
    Policy policy;
    ...
    // If you have a handy global "createEnvironment", just pass this.
    policy.beforeWrite(createEnvironment);

    // If not, create one or use the new handy lambda-function syntax.
    Environment e;
    policy.beforeWrite([&e]()->const Environment&{return e;});
    ...
};


Yeah, it's another "adding a level of indirection" solution ;-). The "price to pay" is one function call (if you pass a pointer to a function like createEnvironment) or nothing at all (if you pass an inlineable functor-class).

Ciao, Imi.
Last edited on
Topic archived. No new replies allowed.