簡體   English   中英

這是 GCC 中遺漏的優化,從 .rodata 加載 16 位 integer 值而不是直接存儲嗎?

[英]Is this a missed optimization in GCC, loading an 16-bit integer value from .rodata instead of an immediate store?

尋找這段代碼:

#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 上。

幾乎重復,我以為我已經回答了這個問題,但沒有立即找到問答: 為什么短(16 位)變量將值移動到寄存器並存儲它,這與其他寬度不同?

在查看兩個 8 位分配而不是一個 16 位整數時,這個問題也有一個單獨的錯過優化。 此外,更新:此 GCC12 回歸已在 GCC 主干中得到修復; 抱歉,在建議您向上游報告之前我忘了檢查一下。


它避免了 16 位立即數的長度更改前綴 (LCP) 停頓,但 Sandybridge 上的mov和更高版本不存在這些停頓,因此它應該停止為tune=generic這樣做:P 你是對的, movw $16, ldap(%rip)將是最佳的。 這就是 GCC 在針對-mtune=znver3等非英特爾 uarches 進行調整時使用的內容。 或者至少舊版本所做的沒有從.rodata加載的其他錯過的優化。

它從.rodata部分加載16而不是將其用作立即數,這太瘋狂了。 具有 RIP 相對尋址模式的movzwl負載已經和mov $16, %eax一樣大,所以你是對的。 .rodata是 GCC 放置字符串文字、其地址被占用或無法優化的const變量等的部分。還有浮點常量;從 memory 加載常量對於 FP/SIMD 來說是正常的,因為 x86 缺少一個mov-immediate 到 XMM 寄存器,但即使對於 8 字節 integer 常量也很少見。)

GCC11 和更早版本執行了mov $16, %eax / movw %ax, ldap(%rip) ( https://godbolt.org/z/7qrafWhqd ),所以這是一個 GCC12 回歸,你應該在https://gcc.gnu 上報告。組織/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

或者使用默認的 tune=generic,GCC12 匹配 GCC11 code-gen。

store:
        movl    $16, %eax
        movw    %ax, x(%rip)
        ret

這對於 Core 2 到 Nehalem(支持 64 位模式的 Intel P6 系列 CPU,這是他們首先運行此代碼所必需的)是最佳的。這些已經過時了,現在的 GCC 可能是時候停止了花費額外的代碼大小和指令,僅 mov-immediate 到 memory,因為mov imm16操作碼特別在預解碼器中獲得特殊支持以避免 LCP 停頓,其中會有一個add $1234, x(%rip) 請參閱https://agner.org/optimize/ ,特別是他的微架構 PDF。( add sign_extended_imm8存在,不幸的是mov不存在,所以add $16, %ax 不會導致問題,但$1234會。)

但是由於那些舊的 CPU 沒有 uop 緩存,因此在最壞的情況下,內部循環中的 LCP 停頓可能會使事情變得更慢。 因此,為所有現代 CPU 編寫速度稍慢的代碼可能是值得的,以避免在最慢的 CPU 上出現大坑。

不幸的是 GCC 不知道 SnB 固定 LCP 在mov上停頓: -O3 -march=haswell仍然首先對寄存器執行 32 位mov -immediate。 所以現代英特爾 CPU 上的-march=native仍然會使代碼變慢:/

-O3 -march=alderlake確實使用 mov-imm16; 也許他們更新了它的調整,因為它也有 silvermont 系列的 E-cores。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM