Подтвердить что ты не робот

Почему построение std:: optional <int> дороже, чем std:: pair <int, bool>?

Рассмотрим эти два подхода, которые могут представлять "необязательный int":

using std_optional_int = std::optional<int>;
using my_optional_int = std::pair<int, bool>;

Учитывая эти две функции...

auto get_std_optional_int() -> std_optional_int 
{
    return {42};
}

auto get_my_optional() -> my_optional_int 
{
    return {42, true};
}

... и g++ trunk и clang++ trunk-std=c++17 -Ofast -fno-exceptions -fno-rtti) создают следующую сборку:

get_std_optional_int():
        mov     rax, rdi
        mov     DWORD PTR [rdi], 42
        mov     BYTE PTR [rdi+4], 1
        ret

get_my_optional():
        movabs  rax, 4294967338 // == 0x 0000 0001 0000 002a
        ret

живой пример на godbolt.org


Почему get_std_optional_int() требуется три команды mov, тогда как get_my_optional() нужен только один movabs? Является ли это проблемой QoI или есть что-то в std::optional эта оптимизация?

Также обратите внимание, что пользователи функций могут быть полностью оптимизированы независимо:

volatile int a = 0;
volatile int b = 0;

int main()
{
    a = get_std_optional_int().value();
    b = get_my_optional().first;
}

... приводит к:

main:
        mov     DWORD PTR a[rip], 42
        xor     eax, eax
        mov     DWORD PTR b[rip], 42
        ret
4b9b3361

Ответ 1

libstdС++, по-видимому, не реализует P0602 "вариант и необязательно должен распространять тривиальность копирования/перемещения" . Вы можете проверить это с помощью:

static_assert(std::is_trivially_copyable_v<std::optional<int>>);

который не работает для libstdС++, и передает libС++ и стандартную библиотеку MSVC (которому действительно нужно собственное имя, поэтому нам не нужно назовите его либо "Реализация MSVC стандартной библиотеки С++", либо "MSVC STL" ).

Конечно, MSVC все еще не пройдет optional<int> в регистре, потому что MS ABI.

Ответ 2

Почему get_std_optional_int() требуется три команды mov, тогда как get_my_optional() нужен только один movabs?

Непосредственная причина в том, что optional возвращается через скрытый указатель, а pair возвращается в регистр. Почему? Спецификация SysV ABI, раздел 3.2.3 "Передача параметров" говорит:

Если объект С++ имеет либо нетривиальный конструктор копии, либо нетривиальный деструктор, он передается невидимой ссылкой.

Сортировка беспорядка С++, который является optional, непросто, но, кажется, существует нетривиальный конструктор копии, по крайней мере, в optional_base класс реализации, который я проверил.

Ответ 3

В Вызывающие соглашения для разных компиляторов С++ и операционных систем Agner Fog говорят, что конструктор или деструктор копирования предотвращает возврат структуры в регистры. Это объясняет, почему optional не возвращается в регистры.

Должно быть что-то еще, препятствующее компилятору выполнять слияние хранилищ (объединяет непрерывные хранилища с непосредственными значениями, более узкими, чем слово, на меньшее количество магазинов, чтобы уменьшить количество инструкций)... Обновление: gcc bug 82434 - -fstore-merging не работает надежно.

Ответ 4

Оптимизация технически разрешена, даже если std::is_trivially_copyable_v<std::optional<int>> является ложным. Однако для компилятора может потребоваться необоснованная степень "умности". Кроме того, для конкретного случая использования std::optional в качестве возвращаемого типа функции оптимизация может потребоваться во время привязки, а не времени компиляции.

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

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

std::is_trivially_copyable_v<std::optional<int>> должно быть правдой. Если бы это было так, разработчикам было бы намного легче обнаружить и выполнить эту оптимизацию. Итак, чтобы ответить на ваш вопрос:

Является ли это проблемой QoI или есть что-то в std::optional спецификации, предотвращающей эту оптимизацию?

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


* Предполагая, что вы не сделали что-то действительно странное, например #define int something_else.