Ищем этот код:
#include <stdint.h>
extern struct __attribute__((packed))
{
uint8_t size;
uint8_t pad;
uint16_t sec_num;
uint16_t offset;
uint16_t segment;
uint64_t sec_id;
} ldap;
//uint16_t x __attribute__((aligned(4096)));
void kkk()
{
ldap.size = 16;
ldap.pad = 0;
//x = 16;
}
После компиляции с -O2, -O3 или -Ofast будет:
.globl kkk
.type kkk, @function
kkk:
movzwl .LC0(%rip), %eax
movw %ax, ldap(%rip)
ret
.size kkk, .-kkk
.section .rodata.cst2,"aM",@progbits,2
.align 2
.LC0:
.byte 16
.byte 0
Я думаю, что лучший это:
kkk:
movw $16, ldap(%rip)
ret
и это тоже нормально:
kkk:
movl $16, %eax
movw %ax, ldap(%rip)
ret
Но я действительно не знаю, что делает rodata .LC0?
Я использую GCC 12.2 в качестве компилятора, установленного apt на Ubuntu 22.10.
Я думаю, что одна инструкция movw $16, ldap(%rip) в порядке. Почему нужно добавить еще одно чтение памяти? @JohnFilleau
Он избегает зависаний LCP для 16-битных немедленных, но их нет для mov на Sandybridge и более поздних версиях, поэтому он должен перестать делать это для tune=generic :P Кроме того, это безумие, что он загружает 16 из раздела .rodata вместо того, чтобы использовать его как немедленное. Нагрузка movzwl с режимом адресации относительно RIP уже достигает mov $16, %eax. Раньше GCC делал это, как GCC11 и ранее ( godbolt.org/z/7qrafWhqd ), так что это регрессия GCC12, о которой вы должны сообщать на gcc.gnu.org/bugzilla





Почти дубликат, я думал, что уже ответил на этот вопрос, но не сразу нашел вопросы и ответы: Почему короткая (16-битная) переменная перемещает значение в регистр и сохраняет его, в отличие от других ширин?
В этом вопросе также есть отдельная пропущенная оптимизация при рассмотрении двух 8-битных назначений, а не одного 16-битного целого числа. Кроме того, обновление: эта регрессия GCC12 уже исправлена в магистрали GCC; извините, я забыл проверить это, прежде чем предлагать вам сообщить об этом вверх по течению.
Это позволяет избежать остановок префикса изменения длины (LCP) для 16-битных немедленных, но их не существует для mov на Sandybridge и более поздних версиях, поэтому это должно прекратиться для tune=generic :P Вы правы, movw $16, ldap(%rip) было бы оптимальным . Это то, что GCC использует при настройке для не-Intel uarch, таких как -mtune=znver3. Или, по крайней мере, то, что делали старые версии, в которых не было другой пропущенной оптимизации загрузки из .rodata.
Это безумие, что он загружает 16 из раздела .rodata вместо того, чтобы использовать его как непосредственное. Нагрузка movzwl с режимом адресации, относящимся к RIP, уже равна mov $16, %eax, так что вы правы. (.rodata — это раздел, в котором GCC помещает строковые литералы, const переменные, адреса которых заняты или иным образом не могут быть оптимизированы, и т. д. Также константы с плавающей запятой; загрузка константы из памяти нормальна для FP/SIMD, поскольку x86 не имеет -непосредственно к регистрам XMM, но редко даже для 8-байтовых целочисленных констант.)
GCC11 и более ранние версии сделали mov $16, %eax / movw %ax, ldap(%rip) (https://godbolt.org/z/7qrafWhqd ), так что это регресс GCC12, о котором вы должны сообщить https://gcc.gnu.org/bugzilla
Загрузка из .rodata не происходит только с x = 16 (https://godbolt.org/z/ffnjnxjWG). Предположительно какое-то взаимодействие с объединением двух отдельных 8-битных хранилищ в 16-битное хранилище приводит к отключению GCC.
uint16_t x __attribute__((aligned(4096)));
void store()
{
//ldap.size = 16;
//ldap.pad = 0;
x = 16;
}
# gcc12 -O3 -mtune=znver3
store:
movw $16, x(%rip)
ret
Или с настройками по умолчанию = generic, GCC12 соответствует code-gen GCC11.
store:
movl $16, %eax
movw %ax, x(%rip)
ret
Это оптимально для Core 2 через Nehalem (процессоры семейства Intel P6, поддерживающие 64-битный режим, который необходим для запуска этого кода в первую очередь). тратить дополнительный размер кода и инструкции и просто перемещать-немедленно в память, поскольку mov imm16 коды операций специально получают специальную поддержку в предварительных декодерах, чтобы избежать остановки LCP, где был бы один с add $1234, x(%rip). См. https://agner.org/optimize/ , в частности, его микроархив PDF. (add sign_extended_imm8 существует, mov, к сожалению, нет, поэтому add $16, %ax не вызовет проблем, а $1234 вызовет.)
Но поскольку эти старые процессоры не имеют кеша uop, остановка LCP во внутреннем цикле может в худшем случае значительно замедлить работу. Так что, возможно, стоит сделать несколько более медленный код для всех современных процессоров, чтобы избежать этой большой выбоины на самых медленных процессорах.
К сожалению, GCC не знает, что фиксированная LCP SnB останавливается на mov: -O3 -march=haswell по-прежнему сначала выполняет 32-битную mov-немедленную обработку регистра. Так что -march=native на современных процессорах Intel все равно код будет медленнее :/
-O3 -march=alderlake использует mov-imm16; возможно, они обновили настройку для него, потому что у него также есть E-сердечники семейства Silvermont.
Большое вам спасибо. Я сообщу об ошибке в GCC.
Создана ошибка GCC: gcc.gnu.org/bugzilla/show_bug.cgi?id=108441
Может быть, вы можете сообщить о новой ошибке, посмотрите последние комментарии: ссылка. Если вы сообщите о новой ошибке, вы можете добавить меня в список CC? Я тоже хочу посмотреть, спасибо большое.
Что такое пропущенная оптимизация? Что следует оптимизировать?