накладные расходы на перемещение std::shared_ptr?

Вот фрагмент C++.Func1генерирует общий объект, который непосредственно перемещается вFunc2. Мы считаем, что в . Поместив этот фрагмент в Compiler Explorer, мы видим в 2-3 раза более короткий код с MSVC по сравнению с clang или GCC. Почему это так и можно ли получить более короткий код с помощью clang/GCC?

Это выглядит какFunc3генерирует код обработки исключений для очистки временного общего объекта.

      #include <memory>

std::shared_ptr<double> Func1();
void Func2 (std::shared_ptr<double> s);

void Func3()
{
  Func2(Func1());
}

1 ответ

Проблема сводится к ABI платформы и лучше иллюстрируется совершенно непрозрачным типом:

      struct A {
    A(const A&);
    A(A&&);
    ~A();
};

A make() noexcept;
void take(A) noexcept;

void foo() {
    take(make());
}

См. сравнение в Compiler Explorer.

Выход MSVC

      void foo(void) PROC
        push    ecx
        push    ecx
        push    esp
        call    A make(void)
        add     esp, 4
        call    void take(A)
        add     esp, 8
        ret     0
void foo(void) ENDP

Вывод GCC (clang очень похож)

      foo():
        sub     rsp, 24
        lea     rdi, [rsp+15]
        call    make()
        lea     rdi, [rsp+15]
        call    take(A)
        lea     rdi, [rsp+15]
        call    A::~A() [complete object destructor]
        add     rsp, 24
        ret

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

- Itanium C++ ABI §3.1.2.3 Нетривиальные параметры

Объяснение

Здесь происходит следующее:

  • make()возвращает значение типа
  • это подается в параметрtake(A)
    • происходит обязательное копирование, поэтому конструкторы копирования/перемещения не вызываются.
  • только GCC и clang уничтожают на месте вызова

Вместо этого MSVC уничтожает временныйA(или в вашем случае ) внутри вызываемого абонента, а не на месте вызова. Дополнительный код, который вы видите, представляет собой встроенную версиюstd::shared_ptrдеструктор.

В конце концов, вы не должны увидеть какого-либо серьезного влияния на производительность. Однако, еслиFunc2сбрасывает/освобождает общий указатель, то, к сожалению, большая часть кода деструктора на месте вызова не работает. Эта проблема ABI аналогична проблеме сstd::unique_ptr:

Существует также языковая проблема, связанная с порядком уничтожения параметров функции и выполнением деструктора. Для простоты это игнорируется в данной статье, но полное решение "unique_ptrтак же дешево пройтиT*"придется решить и эту проблему.


Смотрите также

Агнер Туман. - Соглашения о вызовах для разных компиляторов C++ и операционных систем.

Другие вопросы по тегам