![]() |
1
3
gcc有x86调优选项来控制字符串操作策略以及何时内联与库调用。(参见
https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html
)。
我不得不用
注意,如果SRC和DST与
GCC只会
这可能不应该“算数”,因为我可能
不
对于除“使编译器使用”之外的任何用例,建议使用这些优化选项
但是上面的c编译
with gcc8.1
有趣的事实:x86-64 Sysv调用约定正在为内联优化
我也没有检查其他编译器。
内衬
(我怀疑GCC会采取任何不同的行动,如果你这样做的话
英特尔的Icelake微体系结构将具有“短代表”功能,这可能会降低
memmove/memcpy策略:顺便说一句,glibc的memcpy使用了一种非常好的策略来处理对重叠不敏感的小输入:两个加载—两个可能重叠的存储,对于最多2个寄存器宽的副本。例如,这意味着来自4..7字节的任何输入都以相同的方式分支。 glibc的asm源对该策略有一个很好的描述: https://code.woboq.org/userspace/glibc/sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S.html#19 .
对于大输入,它使用SSE XMM寄存器、AVX YMM寄存器或
微码启动开销对于将它用于当前CPU上通常较小的拷贝是一个很大的问题。 如果当前CPU上的平均拷贝大小可能是8到16个字节,并且/或者不同的计数会导致分支预测失误,那么这可能比字节循环要好。不是这样的 好的 但不那么糟糕。
字节循环转换为字节循环的最后一道窥视孔优化
如果编译器更直接地了解它,并考虑将其用于
在旧的CPU上,
这个
字节循环的唯一优点是代码大小小;它几乎是最底层的;像glibc这样的智能策略对于较小但未知的拷贝大小会更好。但这太多的代码无法内联,函数调用确实有一些开销(溢出调用会阻塞寄存器并阻塞红色区域,再加上
尤其是在一个不经常运行的“冷”函数中(所以您不想在它上面花费大量的代码大小,增加程序的I-cache内存、TLB位置、要从磁盘加载的页面等)。如果手工编写ASM,您通常会对预期的大小分布有更多的了解,并且能够在回滚到其他内容的情况下内联一条快速路径。
记住,编译器将在一个程序中对潜在的多个循环做出决定,并且大多数程序中的大多数代码都不在热循环中。它不应该让他们都膨胀。
这就是为什么GCC默认
不幸的是,它不像GCC的自动矢量化代码那样高效或紧凑。它在16字节SSE的循环清理代码(完全展开15字节的副本)上花费了大量的代码大小。对于32字节的AVX矢量,我们得到一个总的字节 环 处理剩余元素。(对于一个17字节的拷贝,这与1个xmm向量+1个字节或glibc样式的16字节拷贝重叠相比是相当糟糕的)。对于GCC7和更早的版本,它执行相同的完全展开,直到作为循环序言的对齐边界,所以它是膨胀的两倍。
IDK如果按配置优化可以优化gcc的策略,例如,当每次调用的计数都很小时,更倾向于使用更小/更简单的代码,因此无法实现自动向量化的代码。或者,如果代码是“冷”的,并且在整个程序的每次运行中只运行一次或根本不运行一次,则更改策略。或者如果计数通常是16或24或其他值,则最后一个值为标量
我可能会报告一个gcc错过的优化错误,关于在重叠检查后检测memcpy,而不是将其完全留给自动矢量器。和/或关于使用
很多软件都是用
|
![]() |
Imran · Nim编译器优化标志 7 年前 |
![]() |
Mário Feroldi · 在运行时调用代码中未调用的函数 7 年前 |
![]() |
Artemis · 寄存器与指令之间的差异 7 年前 |
![]() |
pandascope · golang编译器是否使用常数折叠? 7 年前 |
![]() |
Marc · C优化:为什么编译器不将对象视为常量? 7 年前 |