我在2009年首先注意到GCC(至少在我的项目和我的机器上)如果我优化尺寸(-Os
)而不是速度(-O2
或-O3
),则会产生明显更快的代码,我一直想知道为什么.
我设法创建(相当愚蠢)代码,显示这种令人惊讶的行为,并且足够小,无法在此处发布.
const int LOOP_BOUND = 200000000;
__attribute__((noinline))
static int add(const int& x, const int& y) {
return x + y;
}
__attribute__((noinline))
static int work(int xval, int yval) {
int sum(0);
for (int i=0; i<LOOP_BOUND; ++i) {
int x(xval+sum);
int y(yval+sum);
int z = add(x, y);
sum += z;
}
return sum;
}
int main(int , char* argv[]) {
int result = work(*argv[1], *argv[2]);
return result;
}
Run Code Online (Sandbox Code Playgroud)
如果我用-Os
它编译它,执行这个程序需要0.38秒,如果用-O2 …
我试图为此刷新GCC手册页,但仍然没有得到它,真的.
-march
和之间有什么区别-mtune
?
什么时候才使用-march
,而不是两者兼而有之?是否有可能只是-mtune
?
我想检查boost::variant
在我的代码中应用的程序集输出,以便查看哪些中间调用被优化掉了.
当我编译以下示例(使用GCC 5.3 g++ -O3 -std=c++14 -S
)时,似乎编译器优化了所有内容并直接返回100:
(...)
main:
.LFB9320:
.cfi_startproc
movl $100, %eax
ret
.cfi_endproc
(...)
Run Code Online (Sandbox Code Playgroud)
#include <boost/variant.hpp>
struct Foo
{
int get() { return 100; }
};
struct Bar
{
int get() { return 999; }
};
using Variant = boost::variant<Foo, Bar>;
int run(Variant v)
{
return boost::apply_visitor([](auto& x){return x.get();}, v);
}
int main()
{
Foo f;
return run(f);
}
Run Code Online (Sandbox Code Playgroud)
但是,完整的程序集输出包含的内容远远超过上面的摘录,对我而言,它看起来永远不会被调用.有没有办法告诉GCC/clang删除所有"噪音"并输出程序运行时实际调用的内容?
完整装配输出:
.file "main1.cpp"
.section .rodata.str1.8,"aMS",@progbits,1
.align 8
.LC0:
.string "/opt/boost/include/boost/variant/detail/forced_return.hpp"
.section .rodata.str1.1,"aMS",@progbits,1
.LC1: …
Run Code Online (Sandbox Code Playgroud) 我是指令优化的新手.
我对一个简单的函数dotp进行了简单的分析,该函数用于获取两个浮点数组的点积.
C代码如下:
float dotp(
const float x[],
const float y[],
const short n
)
{
short i;
float suma;
suma = 0.0f;
for(i=0; i<n; i++)
{
suma += x[i] * y[i];
}
return suma;
}
Run Code Online (Sandbox Code Playgroud)
我用昂纳雾在网络上提供的测试框架testp.
在这种情况下使用的数组是对齐的:
int n = 2048;
float* z2 = (float*)_mm_malloc(sizeof(float)*n, 64);
char *mem = (char*)_mm_malloc(1<<18,4096);
char *a = mem;
char *b = a+n*sizeof(float);
char *c = b+n*sizeof(float);
float *x = (float*)a;
float *y = (float*)b;
float *z = (float*)c;
Run Code Online (Sandbox Code Playgroud)
然后我调用函数dotp,n = 2048,repeat …
有没有一种方法来指示GCC(版本我用4.8.4),以展开在底层函数while循环完全,即剥离这个循环?循环的迭代次数在编译时是已知的:58.
我先解释一下我的尝试.
通过检查GAS输出:
gcc -fpic -O2 -S GEPDOT.c
Run Code Online (Sandbox Code Playgroud)
使用12个寄存器XMM0 - XMM11.如果我将标志-funroll-loops传递给gcc:
gcc -fpic -O2 -funroll-loops -S GEPDOT.c
Run Code Online (Sandbox Code Playgroud)
循环只展开两次.我检查了GCC优化选项.GCC表示-funroll-loops也会打开-frename-registers,所以当GCC展开一个循环时,它先前选择的寄存器分配是使用"遗留"寄存器.但是XMM12只剩下4个 - XMM15,所以GCC最多只能展开2次.如果有48个而不是16个XMM寄存器可供使用,GCC将毫无困难地展开while循环4次.
然而,我做了另一个实验.我首先手动两次展开while循环,获得一个函数GEPDOT_2.然后两者之间没有任何区别
gcc -fpic -O2 -S GEPDOT_2.c
Run Code Online (Sandbox Code Playgroud)
和
gcc -fpic -O2 -funroll-loops -S GEPDOT_2.c
Run Code Online (Sandbox Code Playgroud)
由于GEPDOT_2已用完所有寄存器,因此不执行展开.
GCC确实注册了重命名,以避免引入潜在的错误依赖.但我确信在我的GEPDOT中没有这样的潜力; 即使有,也不重要.我尝试自己展开循环,展开4次比展开2次更快,比没有展开更快.当然我可以手动展开更多次,但这很乏味.GCC可以帮我吗?谢谢.
// C file "GEPDOT.c"
#include <emmintrin.h>
void GEPDOT (double *A, double *B, double *C) {
__m128d A1_vec = _mm_load_pd(A); A += 2;
__m128d B_vec = _mm_load1_pd(B); B++;
__m128d C1_vec = A1_vec * B_vec; …
Run Code Online (Sandbox Code Playgroud) 对于大学课程,我喜欢比较使用 gcc/clang 与汇编编写和编译的功能相似程序的代码大小。在重新评估如何进一步缩小某些可执行文件的大小的过程中,当我 2 年前组装/链接的完全相同的汇编代码在重新构建后现在已经增长了 10 倍以上时,我简直不敢相信自己的眼睛适用于多个程序,不仅是 helloworld):
$ make
as -32 -o helloworld-asm-2020.o helloworld-asm-2020.s
ld -melf_i386 -o helloworld-asm-2020 helloworld-asm-2020.o
$ ls -l
-rwxr-xr-x 1 xxx users 708 Jul 18 2018 helloworld-asm-2018*
-rwxr-xr-x 1 xxx users 8704 Nov 25 15:00 helloworld-asm-2020*
-rwxr-xr-x 1 xxx users 4724 Nov 25 15:00 helloworld-asm-2020-n*
-rwxr-xr-x 1 xxx users 4228 Nov 25 15:00 helloworld-asm-2020-n-sstripped*
-rwxr-xr-x 1 xxx users 604 Nov 25 15:00 helloworld-asm-2020.o*
-rw-r--r-- 1 xxx users 498 Nov 25 14:44 helloworld-asm-2020.s
Run Code Online (Sandbox Code Playgroud)
汇编代码是:
.code32
.section …
Run Code Online (Sandbox Code Playgroud) 我在llvm clang Apple LLVM 8.0.0版(clang-800.0.42.1)上反汇编代码:
int main() {
float a=0.151234;
float b=0.2;
float c=a+b;
printf("%f", c);
}
Run Code Online (Sandbox Code Playgroud)
我编译时没有-O规范,但我也试过-O0(给出相同)和-O2(实际上计算值并存储它预先计算)
产生的反汇编如下(我删除了不相关的部分)
-> 0x100000f30 <+0>: pushq %rbp
0x100000f31 <+1>: movq %rsp, %rbp
0x100000f34 <+4>: subq $0x10, %rsp
0x100000f38 <+8>: leaq 0x6d(%rip), %rdi
0x100000f3f <+15>: movss 0x5d(%rip), %xmm0
0x100000f47 <+23>: movss 0x59(%rip), %xmm1
0x100000f4f <+31>: movss %xmm1, -0x4(%rbp)
0x100000f54 <+36>: movss %xmm0, -0x8(%rbp)
0x100000f59 <+41>: movss -0x4(%rbp), %xmm0
0x100000f5e <+46>: addss -0x8(%rbp), %xmm0
0x100000f63 <+51>: movss %xmm0, -0xc(%rbp)
...
Run Code Online (Sandbox Code Playgroud)
显然它正在做以下事情: