Как проверить эллизионную копию С++

Я наткнулся на эту статью на copy ellision в C++, и я видел комментарии об этом в библиотеке boost. Это привлекательно, так как я предпочитаю, чтобы мои функции выглядели как

verylargereturntype DoSomething(...)

скорее, чем

void DoSomething(..., verylargereturntype& retval)

Итак, у меня есть два вопроса по этому поводу

  1. У Google практически нет документации по этому поводу, насколько это реально?
  2. Как я могу проверить, что эта оптимизация действительно происходит? Я предполагаю, что это связано с просмотром сборки, но скажем так, это не моя сильная сторона. Если бы кто-нибудь мог привести очень простой пример того, как выглядит успешный ellision, это было бы очень полезно.

Я не буду использовать copy ellision только для того, чтобы приукрасить вещи, но если я могу гарантировать, что это работает, это звучит довольно полезно.


person Steve    schedule 05.04.2010    source источник
comment
Кстати, в статье неправильно определены значения lvalue и rvalue. См. вопросы 6.7 и 20.39b на c-faq.com.   -  person conio    schedule 06.04.2010


Ответы (7)


Я думаю, что это очень часто применяемая оптимизация, потому что:

  1. компилятору не сложно это сделать
  2. это может быть большой выигрыш
  3. это область C++, которая часто подвергалась критике до того, как оптимизация стала обычным явлением.

Если вам просто интересно, поместите отладку printf() в свой конструктор копирования:

class foo {
public:
    foo(): x(0) {};

    foo(int x_) : x( x_) {};

    foo( foo const& other) : x( other.x) {
        printf( "copied a foo\n");
    };

    static foo foobar() {
        foo tmp( 2);

        return tmp;
    }


private:
    int x;
};



int main()
{
    foo myFoo;

    myFoo = foo::foobar();

    return 0;
}

Распечатывает «скопировано foo», когда я запускаю неоптимизированную сборку, но ничего, когда я оптимизирую сборку.

person Michael Burr    schedule 05.04.2010
comment
На некоторых компиляторах RVO можно вызвать даже с минимальными настройками оптимизации или без них. - person fbrereto; 06.04.2010
comment
Я немного напуган тем, что компилятор оптимизировал предполагаемый побочный эффект... Похоже, он должен оптимизировать ваш конструктор копирования только в том случае, если это конструктор копирования по умолчанию или что-то в этом роде. - person sblom; 06.04.2010
comment
Стандарт явно разрешает эту оптимизацию, поэтому в целом вы не должны зависеть от побочных эффектов вашего конструктора копирования, помимо очевидного. Это довольно важный метод оптимизации, и ограничение его для объектов с тривиальными конструкторами копирования было бы чрезвычайно ограничивающим. - person Dennis Zickefoose; 06.04.2010
comment
@sblom: Как говорит Деннис, стандарт прямо говорит (12.8/12 Копирование объектов класса): при соблюдении определенных критериев реализации разрешается опускать конструкцию копирования объекта класса, даже если конструктор копирования и/или деструктор для объект имеет побочные эффекты - person Michael Burr; 06.04.2010
comment
@sblom: есть только один конструктор копирования, и он оптимизируется только в том случае, если объект, из которого он копируется, не имеет имени. Если вам нужны побочные эффекты, просто дайте имя промежуточному объекту. - person Potatoswatter; 06.04.2010
comment
@Potatocom: даже если возвращаемое значение названо, копию все равно можно оптимизировать. Это сложнее, но компиляторы умнее. - person Dennis Zickefoose; 06.04.2010
comment
Фактически, в примере, который я опубликовал и протестировал, используется именованное возвращаемое значение (да, простое, но тем не менее именованное). Оптимизация именованного возвращаемого значения известна как NRVO и может быть немного менее распространена, чем простая RVO, но я думаю, что современные компиляторы в значительной степени обрабатывают их одинаково. - person Michael Burr; 06.04.2010
comment
@Michael: это зависит от настроек компилятора и оптимизации. Известно, что Visual не реализует RVO в сборках отладки, в то время как gcc делает URVO, я думаю. Теперь я не уверен, что действительно стоит копаться в спецификациях компилятора, чтобы точно знать, какая оптимизация применяется на каком уровне... - person Matthieu M.; 06.04.2010

Из цитируемой вами статьи:

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

Он более известен как оптимизация возвращаемого значения.

person msw    schedule 05.04.2010

Единственный способ узнать наверняка - посмотреть на сборку, но вы задаете неправильный вопрос. Вам не нужно знать, пропускает ли компилятор копию, если это не имеет значения для времени выполнения программы. Профилировщик должен легко сказать вам, если вы проводите слишком много времени в конструкторе копирования.

Способ бедняка понять это - поместить статический счетчик в конструктор копирования и попробовать обе формы вашей функции. Если счетчики совпадают, вы успешно избежали копирования.

person Mark Ransom    schedule 05.04.2010
comment
эта вещь действительно большая и вызывается довольно часто - person Steve; 06.04.2010
comment
Действительно большие и довольно часто могут быть на порядки больше, чем вы думаете. Меня поражает, что современный компьютер может сделать за долю секунды. - person Mark Ransom; 06.04.2010

Вместо этого погуглите «Оптимизация именованного возвращаемого значения» и «Оптимизация возвращаемого значения». Фактически современные компиляторы во многих случаях не будут выполнять копирование.

Вы можете проверить, происходит ли это, возвращая тип с побочными эффектами, такими как печать сообщения. В Википедии есть несколько хороших примеров того, где выходные данные программы меняются, когда действует RVO и/или NRVO.

person Billy ONeal    schedule 05.04.2010

Пример того, как это выглядит:

#include <iostream>

struct Foo {
    int a;
    Foo(int a) : a(a) {}
    Foo(const Foo &rhs) : a(rhs.a) { std::cout << "copying\n"; }
};

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

Если вы не видите вывода, значит, произошло удаление копии. Это исключение копии из инициализатора. Другим допустимым случаем исключения копии является возвращаемое значение, и оно проверяется:

Foo getFoo() {
    return Foo(1);
}

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

или более интересно для именованного возвращаемого значения:

Foo getFoo() {
    Foo f(1);
    return f;
}

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

g++ выполняет для меня все эти исключения без флагов оптимизации, но вы не можете точно знать, сможет ли более сложный код перехитрить компилятор.

Обратите внимание, что исключение копирования не помогает при присваивании, поэтому следующее всегда будет приводить к вызову operator=, если этот оператор что-либо печатает:

Foo f(1);
f = getFoo();

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

person Steve Jessop    schedule 05.04.2010

Чтобы ответить на вопрос 2, вы можете написать демонстрационную программу, в которой вы пишете class DemoReturnType; с инструментальными конструкторами и деструкторами, которые просто пишут в cout при вызове. Это должно дать вам достаточно информации о том, на что способен ваш компилятор.

person quamrana    schedule 05.04.2010

Ссылки Rvalue решают эту проблему в C++0x. Другой вопрос, можете ли вы получить компилятор с поддержкой rvalue - в прошлый раз я проверял, что его поддерживает только Visual Studio 2010.

person Puppy    schedule 05.04.2010
comment
GCC также поддерживает это. См. aristeia.com/C++0x/C++0xFeatureAvailability.htm. и щелкните вкладку «Язык» внизу. - person Nate; 06.04.2010
comment
Ссылки на Rvalue дополняют [N]RVO. Когда это возможно, компилятор по-прежнему будет создавать объект на месте и избегать прямого перемещения/копирования. - person Dennis Zickefoose; 06.04.2010