Почему построение 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
Ответы
Ответ 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
.