- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
问题:明显多出一行代码会使程序加速近两倍。
这个问题很难表述为原始问题,它来自边界检查消除算法。所以,只是一些我无法理解的简单测试。
明显多出一行代码会使程序加速近两倍。
有以下来源:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
long i = 0, a = 0, x = 0;
int up = 200000000;
int *values = malloc(sizeof(int)*up);
for (i = 0; i < up ; ++i)
{
values[i]=i % 2;
}
for (i = 0; i < up ; ++i)
{
x = (a & i);
#ifdef FAST
x = 0;
#endif
a += values[x];
}
printf ("a=%ld\n", a);
return 0;
}/*main*/
在此示例中,“a”的值始终为 0。该行x = 0;是额外的。
但是,(看——没有任何优化!)
$gcc -O0 -o short short.c && time ./short
a=0
真正的0m2.808s
用户0m2.196s
系统 0m0.596s
$gcc -O0 -DFAST -o short short.c && time ./short
a=0
真实 0m1.869s
用户 0m1.260s
系统 0m0.608s
而且,这在许多编译器/优化选项和程序变体上是可重现的。
此外,除了将这个愚蠢的额外 0 放入某个寄存器之外,它们实际上产生了相同的汇编代码!例如:
gcc -S -O0 -DFAST short.c && mv short.s shortFAST.s
gcc -S -O0 short.c && mv short.s shortSLOW.s
diff shortFAST.s shortSLOW.s
55d54
< movq $0, -24(%rbp)
不久之后——对某些(所有我能够测试的)其他编译器/语言(包括 Java JIT)也产生了同样的影响。唯一共享的东西——x86-64 架构。在 Intel 和 AMD 处理器上进行了测试...
最佳答案
简短回答:存储 0 消除了其中一个循环中的先读后写依赖性。
详细信息:
我认为这是一个有趣的问题,虽然您关注的是 O0 优化级别,但在 O3 上也可以看到相同的加速。但是查看 O0 可以更容易地关注处理器正在做什么来优化代码而不是编译器,因为正如您所指出的那样,生成的汇编代码仅相差 1 条指令。
感兴趣的循环的汇编代码如下所示
movq $0, -32(%rbp)
jmp .L4
.L5:
movq -32(%rbp), %rax
movq -24(%rbp), %rdx
andq %rdx, %rax
movq %rax, -16(%rbp)
movq $0, -16(%rbp) ;; This instruction in FAST but not SLOW
movq -16(%rbp), %rax
leaq 0(,%rax,4), %rdx
movq -8(%rbp), %rax
addq %rdx, %rax
movl (%rax), %eax
cltq
addq %rax, -24(%rbp)
addq $1, -32(%rbp)
.L4:
movl -36(%rbp), %eax
cltq
cmpq -32(%rbp), %rax
jg .L5
在我的系统上使用 perf stat
运行我得到以下结果:
慢速代码的结果
Performance counter stats for './slow_o0':
1827.438670 task-clock # 0.999 CPUs utilized
155 context-switches # 0.085 K/sec
1 CPU-migrations # 0.001 K/sec
195,448 page-faults # 0.107 M/sec
6,675,246,466 cycles # 3.653 GHz
4,391,690,661 stalled-cycles-frontend # 65.79% frontend cycles idle
1,609,321,845 stalled-cycles-backend # 24.11% backend cycles idle
7,157,837,211 instructions # 1.07 insns per cycle
# 0.61 stalled cycles per insn
490,110,757 branches # 268.195 M/sec
178,287 branch-misses # 0.04% of all branches
1.829712061 seconds time elapsed
快速代码的结果
Performance counter stats for './fast_o0':
1109.451910 task-clock # 0.998 CPUs utilized
95 context-switches # 0.086 K/sec
1 CPU-migrations # 0.001 K/sec
195,448 page-faults # 0.176 M/sec
4,067,613,078 cycles # 3.666 GHz
1,784,131,209 stalled-cycles-frontend # 43.86% frontend cycles idle
438,447,105 stalled-cycles-backend # 10.78% backend cycles idle
7,356,892,998 instructions # 1.81 insns per cycle
# 0.24 stalled cycles per insn
489,945,197 branches # 441.610 M/sec
176,136 branch-misses # 0.04% of all branches
1.111398442 seconds time elapsed
因此您可以看到,即使“快速”代码执行的指令更多,它的停顿也更少。当乱序 CPU(如大多数 x64 架构)正在执行代码时,它会跟踪指令之间的依赖关系。如果操作数准备就绪,则等待指令可以被另一条指令绕过。
在这个例子中,关键点可能是这个指令序列:
andq %rdx, %rax
movq %rax, -16(%rbp)
movq $0, -16(%rbp) ;; This instruction in FAST but not SLOW
movq -16(%rbp), %rax
leaq 0(,%rax,4), %rdx
movq -8(%rbp), %rax
在快速代码中,movq -8(%rbp), %rax
指令将从 movq $0, -16(%rbp)
中获取结果转发给它它将能够更快地执行。而较慢的版本将不得不等待 movq %rax, -16(%rbp)
,它在循环迭代之间具有更多的依赖性。
请注意,如果不了解更多有关特定微架构的信息,此分析可能过于简单。但我怀疑根本原因是这种依赖性,执行 0 的存储(movq $0, -16(%rbp)
指令)允许 CPU 在执行代码序列时执行更积极的推测。
关于c - gcc 简单算术循环性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26474282/
我正在尝试在 Conda 环境中编译一些代码,在那里我 之前安装的编译包gcc_linux-64 . 然而,即使在停用和重新激活环境之后,gcc还在/usr/bin/gcc . 我该怎么做才能让 Co
关闭。这个问题是opinion-based .它目前不接受答案。 想要改进这个问题? 更新问题,以便 editing this post 可以用事实和引用来回答它. 关闭 7 年前。 Improve
这其实是两个问题: 1 - 在我的 debian amd64 系统上,我似乎无法构建与 gmp/mpfr/mpc 动态链接的交叉 GCC。即使我删除 --disable-shared,它也总是静态链接
研究ELF格式的结果,可以看到目标文件中有一个符号对应每个函数,对应的符号表项的值为st_size,表示大小的功能。 问题是,即使我更改了目标文件中特定函数的 st_size 并链接了它,但可执行文件
海湾合作委员会的 documentation for #line directives说他们是这样的: #line "myfile.cpp" 123 但是当我用 g++ 5.1 检查输出时,它们实际上
我正在使用 as 和 gcc 来汇编和创建 ARM 汇编程序的可执行文件,正如 this 所推荐的那样教程,如下: 给定一个汇编源文件,program.s,我运行: as -o program.o p
long long x; double n; x=long long(n); 这不起作用。什么是正确的方法? 最佳答案 显而易见的: x = (long long) n; 关于gcc - 转换为长长
我想知道用于 gcc 的原子内置函数的头文件是什么? 我想使用这 2 个函数为我当前创建的线程库实现互斥锁。 bool __sync_bool_compare_and_swap (type *ptr,
它出现在 another question :gcc调用的程序和部件是什么? (特别是在编译 C 或 C++ 时)以便有人可以设计一些拦截和更改流程的方案以用于各种自定义编码目的? 最佳答案 编译器二
可能吗?我想使用 gcc喜欢 assembler并在将其编译为 ubuntu 上的可执行文件后。 我尝试过这个: gcc a.asm -o out.o 来自 out.o文件编译成.out可执行文件。
我写了一个简单的 C 程序 test.c : #include #include int add(int a, int b); int main() { int i=5,j=10;
即。所以如果你使用任何八进制文字,它会给你一个警告。 微软编译器的同样问题。 如果没有,是否有任何其他工具可以检测八进制文字。 (vim 似乎有一个很酷的技巧,它突出了第一个领先的将不同的颜色归零,但
我在旧线程中搜索。但没有找到任何线程回答我的问题。 gcc 是否像 vc++ 一样支持函数级链接? 如果是,我应该提供什么选项来链接目标文件和库? 最佳答案 看起来 gcc 不直接支持函数级链接。您可
也许标题并没有把问题说得那么准确:我知道当我运行 gcc foo.c 时,GCC 会调用其他为它完成所有工作的子程序,从而生成主 gcc 程序只是一个界面。但这究竟是如何完成的呢? 它是否使用syst
我听说最近版本的 gcc 非常擅长将通过函数指针的调用转换为直接调用。但是,我在网上或快速浏览 gcc 的源代码上找不到任何关于它的信息。有谁知道这是否真的是真的,如果是这样,它使用什么算法来做到这一
gcc/g++ 链接器选项“-Map”生成的“.map”文件用于什么? 以及如何阅读它们? 最佳答案 我建议为您投入生产的任何软件生成一个映射文件并保留一份副本。 它可用于破译崩溃报告。根据系统的不同
gcc信息文件在有关x86-64特定标志的部分中说 其他事情: There is no `-march=generic' option because `-march' ind
我想知道 gcc 链接器选项(例如:-Wl,options)是否可以更改编译后的可执行文件中的汇编指令,因为如果您使用某些 gcc 优化选项会发生这种情况? 当您比较编译后的二进制文件(例如比较签名)
是否有GCC编译指示会停止,暂停或中止编译过程? 我正在使用gcc 4.1,但也希望在gcc 3.x版本上也可以使用该编译指示。 最佳答案 您可能需要#error: edd@ron:/tmp$ g++
当我使用gcc编译C程序时我通常使用 -g 将一些调试信息放入 elf 文件中这样 gdb 就可以在需要时帮助我。 但是,我注意到有些程序使用 -ggdb,因为它应该使调试信息对 gdb 更加友好。
我是一名优秀的程序员,十分优秀!