[英]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.