Это пропущенная оптимизация в GCC, загрузка 16-битного целочисленного значения из .rodata вместо немедленного сохранения?

Ищем этот код:

#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.

Что такое пропущенная оптимизация? Что следует оптимизировать?

JohnFilleau 18.01.2023 05:52

Я думаю, что одна инструкция movw $16, ldap(%rip) в порядке. Почему нужно добавить еще одно чтение памяти? @JohnFilleau

untitled 18.01.2023 05:55

Он избегает зависаний 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

Peter Cordes 18.01.2023 08:03
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
97
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Почти дубликат, я думал, что уже ответил на этот вопрос, но не сразу нашел вопросы и ответы: Почему короткая (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.

untitled 18.01.2023 10:10

Создана ошибка GCC: gcc.gnu.org/bugzilla/show_bug.cgi?id=108441

untitled 18.01.2023 10:32

Может быть, вы можете сообщить о новой ошибке, посмотрите последние комментарии: ссылка. Если вы сообщите о новой ошибке, вы можете добавить меня в список CC? Я тоже хочу посмотреть, спасибо большое.

untitled 19.01.2023 04:32

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