Почему конструкция std:: optional дороже, чем std:: pair?



рассмотрим эти два подхода, которые могут представлять собой " необязательный 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++ и багажник и 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
696   4  

4 ответов:

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

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

который не работает для libstdc++ и проходит для libc++ и стандартной библиотеки MSVC (который действительно нуждается в собственном имени, поэтому нам не нужно называть его либо "реализация MSVC стандартной библиотеки C++", либо "MSVC STL").

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

EDIT: эта проблема была исправлена в серии выпусков GCC 8.

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

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

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

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

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

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

оптимизация технически допустимая, даже 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.

Comments

    Ничего не найдено.