Automatic xvalue optimization
Asked Answered
C

2

12

Somewhat surprisingly (to me), the following two programs compile to different outputs, with the latter one having much better performance (tested with gcc and clang):

#include <vector>
int main()
{
    std::vector<int> a(2<<20);
    for(std::size_t i = 0; i != 1000; ++i) {
        std::vector<int> b(2<<20);
        a = b;
    }
}

vs.

#include <vector>
int main()
{
    std::vector<int> a(2<<20);
    for(std::size_t i = 0; i != 1000; ++i) {
        std::vector<int> b(2<<20);
        a = std::move(b);
    }
}

Could someone explain to me why the compiler does (or can) not automatically consider b an xvalue in the last assignment and apply move semantics without the explicit std::move cast?

Edit: Compiled with (g++|clang++) -std=c++11 -O3 -o test test.cpp

Circle answered 24/9, 2014 at 9:22 Comment(8)
What are the parameters you're passing to the compilers?Adhibit
My first guess is that this would change the semantics of the program in an unexpected way turning a copy into a move.Webworm
@pmr: That is what I suspect too, but I really would like to understand why. Naively, it does seem exactly like what an xvalue should be to me.Circle
It is true that it might be an optimization, but it surely affects the semantic of the program itself, i.e. changing ownership. IIRC there has been a lot of discussion on this topic during the first standard draftsTineid
The compiler is just being safe, in this case it would indeed work but it seems that the compiler didn't use the information "b won't be used again" when making the optimization while std::move tells him explicitelyDaleth
Related to Is default constructor elision / assignment elision possible in principle?Antinomian
Am I missing something? The second one does a move and the first one does a copy, so the second one should be faster. std::move(b) is an xvalue, b is notDietrich
@MattMcNabb yes, and my question was why the compiler isn't allowed to automatically convert b into an xvalue, seeing that its scope is ending anyway.Circle
T
7

Compilers can't break the as-if rule

As §1.9/1 states:

The semantic descriptions in this International Standard define a parameterized nondeterministic abstract machine. This International Standard places no requirement on the structure of conforming implementations. In particular, they need not copy or emulate the structure of the abstract machine. Rather, conforming implementations are required to emulate (only) the observable behavior of the abstract machine as explained below

i.e. a compiler can't change the observable behavior of the program. Automatically (even if with no repercussions) converting an assignment to a move assignment would break this statement.

Copy elisions can slightly alter this behavior, but that is regulated by §12.8/31.

If you want to use the move version, you'll have to explicitly ask for it as in the latter example.

Tineid answered 24/9, 2014 at 9:42 Comment(2)
OK, so in particular the programmer should have reliable copy/move operator/ctor calling. I somehow assumed it would be sensible to require the programmer to make these operations compatible semantically. I guess both approaches would have their pros and cons, but I can see why the standard sees it differently.Circle
In this specific code it wouldn't break the as-if rule because there is no outputDietrich
G
5

Let's look at the next sample (please ignore void return type from operator=):

#include <iostream>

struct helper
{
    void operator=(helper&&){std::cout<<"move"<<std::endl;}
    void operator=(const helper&){std::cout<<"copy"<<std::endl;}
};

void fun()
{
    helper a;
    {
        helper b;
        a = b;
    }
}

void gun()
{
    helper a;
    {
        helper b;
        a = std::move(b);
    }
}
int main()
{
    fun();
    gun();
}

The operator= has different behaviour depending on its arguments. The compiler is allowed to optimise the code only if it is able to maintain the observable behaviour the same.

Considering b from fun an xvalue, while it isn't an xvalue at the moment of the call it will change the observable behaviour of the program and this is not desired nor allowed by the standard.

Gianna answered 24/9, 2014 at 9:38 Comment(1)
Thanks, I was aware of the change in the constructor calling. I assumed that the programmer was somehow required to make move constructors/operators and copy constructors/operators "semantically matching", and I guess that's my flaw here!Circle

© 2022 - 2024 — McMap. All rights reserved.