Автоматическая оптимизация xvalue

Несколько неожиданно (для меня) следующие две программы компилируются с разными выводами, причем последняя имеет гораздо лучшую производительность (проверено с помощью gcc и 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);
    }
}

Может ли кто-нибудь объяснить мне, почему компилятор не учитывает (или не может) автоматически b значение x в последнем присваивании и не применяет семантику перемещения без явного приведения std::move?

Изменить: скомпилировано с помощью (g++|clang++) -std=c++11 -O3 -o test test.cpp


person Xoph    schedule 24.09.2014    source источник
comment
Какие параметры вы передаете компиляторам?   -  person Joe    schedule 24.09.2014
comment
Мое первое предположение состоит в том, что это неожиданно изменит семантику программы, превратив копию в ход.   -  person pmr    schedule 24.09.2014
comment
@pmr: Я тоже это подозреваю, но мне очень хотелось бы понять, почему. Наивно, мне кажется, что именно таким должно быть xvalue.   -  person Xoph    schedule 24.09.2014
comment
Это правда, что это может быть оптимизация, но она, безусловно, влияет на семантику самой программы, то есть на смену владельца. IIRC было много дискуссий по этой теме во время первых стандартных проектов.   -  person Marco A.    schedule 24.09.2014
comment
Компилятор просто безопасен, в этом случае он действительно будет работать, но похоже, что компилятор не использовал информацию b не будет использоваться снова при оптимизации, в то время как std::move сообщает ему явно   -  person meneldal    schedule 24.09.2014
comment
Я что-то упускаю? Второй делает ход, а первый делает копию, поэтому второй должен быть быстрее. std::move(b) является xvalue, b нет   -  person M.M    schedule 24.09.2014
comment
@MattMcNabb да, и мой вопрос заключался в том, почему компилятору не разрешено автоматически преобразовывать b в значение x, видя, что его область действия все равно заканчивается.   -  person Xoph    schedule 24.09.2014


Ответы (2)


Компиляторы не могут нарушать правило "как если"

Как указано в §1.9/1:

Семантические описания в настоящем стандарте определяют параметризованную недетерминированную абстрактную машину. Настоящий международный стандарт не предъявляет требований к структуре соответствующих реализаций. В частности, им не нужно копировать или эмулировать структуру абстрактной машины. Скорее, соответствующие реализации необходимы для имитации (только) наблюдаемого поведения абстрактной машины, как описано ниже.

то есть компилятор не может изменить наблюдаемое поведение программы. Автоматическое (даже без последствий) преобразование присваивания в присваивание перемещения нарушило бы это утверждение.

Удаление копирования может немного изменить это поведение, но это регулируется §12.8/31.

Если вы хотите использовать версию для перемещения, вам придется явно запросить ее, как в последнем примере.

person Marco A.    schedule 24.09.2014
comment
Итак, в частности, у программиста должен быть надежный вызов оператора копирования/перемещения/ctor. Я почему-то предположил, что было бы разумно потребовать от программиста сделать эти операции семантически совместимыми. Я предполагаю, что оба подхода будут иметь свои плюсы и минусы, но я понимаю, почему стандарт видит это по-разному. - person Xoph; 24.09.2014
comment
В этом конкретном коде это не нарушит правило «как если бы», потому что нет вывода - person M.M; 24.09.2014

Давайте посмотрим на следующий пример (пожалуйста, игнорируйте тип возврата void из 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();
}

operator= ведет себя по-разному в зависимости от своих аргументов. Компилятору разрешается оптимизировать код только в том случае, если он способен поддерживать наблюдаемое поведение таким же.

Рассматривая b из fun как xvalue, хотя это и не xvalue в момент вызова, это изменит наблюдаемое поведение программы, а это нежелательно и не разрешено стандартом.

person Mircea Ispas    schedule 24.09.2014
comment
Спасибо, я знал об изменении вызова конструктора. Я предположил, что от программиста каким-то образом требовалось, чтобы конструкторы/операторы перемещения и конструкторы/операторы копирования семантически совпадали, и я думаю, что это моя ошибка! - person Xoph; 24.09.2014