- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我尝试使用 GCC 和内在函数优化许多紧密循环。例如考虑以下功能。
void triad(float *x, float *y, float *z, const int n) {
float k = 3.14159f;
int i;
__m256 k4 = _mm256_set1_ps(k);
for(i=0; i<n; i+=8) {
_mm256_store_ps(&z[i], _mm256_add_ps(_mm256_load_ps(&x[i]), _mm256_mul_ps(k4, _mm256_load_ps(&y[i]))));
}
}
这会产生一个像这样的主循环
20: vmulps ymm0,ymm1,[rsi+rax*1]
25: vaddps ymm0,ymm0,[rdi+rax*1]
2a: vmovaps [rdx+rax*1],ymm0
2f: add rax,0x20
33: cmp rax,rcx
36: jne 20
但是cmp
指令是不必要的。我们可以设置基本指针 (rsi
, rdi),而不是让
和 rax
从零开始并在 sizeof(float)*n
结束rdx
) 到数组的末尾并将 rax
设置为 -sizeof(float)*n
然后测试零。我可以像这样用我自己的汇编代码来做到这一点
.L2 vmulps ymm1, ymm2, [rdi+rax]
vaddps ymm0, ymm1, [rsi+rax]
vmovaps [rdx+rax], ymm0
add rax, 32
jne .L2
但我无法让 GCC 执行此操作。我现在有几个测试,这会产生重大影响。直到最近,GCC 和内在函数已经很好地切断了我,所以我想知道是否有编译器开关或重新排序/更改我的代码的方法,以便 GCC 不生成 cmp
指令。
我尝试了以下但它仍然生成 cmp
。我尝试过的所有变体仍然会产生 cmp
。
void triad2(float *x, float *y, float *z, const int n) {
float k = 3.14159f;
float *x2 = x+n;
float *y2 = y+n;
float *z2 = z+n;
int i;
__m256 k4 = _mm256_set1_ps(k);
for(i=-n; i<0; i+=8) {
_mm256_store_ps(&z2[i], _mm256_add_ps(_mm256_load_ps(&x2[i]), _mm256_mul_ps(k4, _mm256_load_ps(&y2[i]))));
}
}
编辑:我感兴趣的是为适合 L1 缓存的数组(实际上是 n=2048
)最大化这些函数的指令级并行性 (ILP)。尽管展开可用于提高带宽,但它会降低 ILP(假设无需展开即可获得全带宽)。
编辑:这是 Core2(Nehalem 之前)、IvyBridge 和 Haswell 系统的结果表。 Intrinsics是使用intrinsics的结果,unroll1是我没有使用cmp
的汇编代码,unroll16是我的汇编代码展开16次。百分比是峰值性能的百分比(频率*num_bytes_cycle,其中 num_bytes_cycle 对于 SSE 为 24,对于 AVX 为 48,对于 FMA 为 96)。
SSE AVX FMA
intrinsic 71.3% 90.9% 53.6%
unroll1 97.0% 96.1% 63.5%
unroll16 98.6% 90.4% 93.6%
ScottD 96.5%
32B code align 95.5%
对于 SSE,我在不展开的情况下获得的结果几乎与展开时一样好,但前提是我不使用 cmp
。在 AVX 上,我在不展开和不使用 cmp
的情况下获得了最佳结果。有趣的是,在 IB 上展开实际上更糟。在 Haswell 上,我通过展开获得了迄今为止最好的结果。这就是为什么我问这个 question .可以在那个问题中找到测试它的源代码。
编辑:
根据 ScottD 的回答,现在我的 Core2 系统(Nehalem 64 位模式之前)的内在函数几乎达到了 97%。我不确定为什么 cmp
实际上很重要,因为无论如何每次迭代都需要 2 个时钟周期。对于 Sandy Bridge,事实证明效率损失是由于代码对齐而不是额外的 cmp
。在 Haswell 上,无论如何只有展开才有效。
最佳答案
这个怎么样。编译器是 gcc 4.9.0 mingw x64:
void triad(float *x, float *y, float *z, const int n) {
float k = 3.14159f;
intptr_t i;
__m256 k4 = _mm256_set1_ps(k);
for(i = -n; i < 0; i += 8) {
_mm256_store_ps(&z[i+n], _mm256_add_ps(_mm256_load_ps(&x[i+n]), _mm256_mul_ps(k4, _mm256_load_ps(&y[i+n]))));
}
}
gcc -c -O3 -march=corei7 -mavx2 triad.c
0000000000000000 <triad>:
0: 44 89 c8 mov eax,r9d
3: f7 d8 neg eax
5: 48 98 cdqe
7: 48 85 c0 test rax,rax
a: 79 31 jns 3d <triad+0x3d>
c: c5 fc 28 0d 00 00 00 00 vmovaps ymm1,YMMWORD PTR [rip+0x0]
14: 4d 63 c9 movsxd r9,r9d
17: 49 c1 e1 02 shl r9,0x2
1b: 4c 01 ca add rdx,r9
1e: 4c 01 c9 add rcx,r9
21: 4d 01 c8 add r8,r9
24: c5 f4 59 04 82 vmulps ymm0,ymm1,YMMWORD PTR [rdx+rax*4]
29: c5 fc 58 04 81 vaddps ymm0,ymm0,YMMWORD PTR [rcx+rax*4]
2e: c4 c1 7c 29 04 80 vmovaps YMMWORD PTR [r8+rax*4],ymm0
34: 48 83 c0 08 add rax,0x8
38: 78 ea js 24 <triad+0x24>
3a: c5 f8 77 vzeroupper
3d: c3 ret
与您手写的代码一样,gcc 在循环中使用了 5 条指令。 gcc 代码使用 scale=4,而你的代码使用 scale=1。我能够让 gcc 在 5 指令循环中使用 scale=1,但 C 代码很笨拙,循环中的 2 条 AVX 指令从 5 字节增加到 6 字节。
关于c - 在 GCC 中生成没有 cmp 指令的循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25921612/
两种符号-cmp +chroma和 -cmp chroma适合我。它们之间有什么区别吗? 我目前的猜测是否定的,因为 all -cmp arguments can be presented in di
我正在实现一个游戏。我有一个状态树和一个基于 set<> 的优先级队列,用于根据成本对状态进行排序。为此,我将 { bool operator()(const State* lhs, const
和有什么区别 extern int (*func)(void); 和 extern int *func(void); 谢谢 最佳答案 extern int (*func)(void); 声明 func
我必须用汇编写一个函数来完成下面的c代码。 int main(){ int hist[26]={0}; int i; charHistogram("This is a string", hist);
我用我想要循环的次数填充ecx,dec重新调整ecx并如果不为零则跳转 到返回:。 现在的问题是,为什么不: cmp ecx, 0jnz back 之前必需的。jnz 如何自动知道跳转时要比较哪个寄存
我的 CMPedometer 没有运行。 运行之前和之后的代码,但它本身不起作用。我没有收到任何警告或异常。我正在真实的 5s 上进行测试。 我尝试过 querydata 和 startpedomet
我使用下面的代码向端点发送 CMP 证书请求: public static void main(String[] args) { try { System.out.pr
我正在使用 gdb 来调试我的代码,仍然是初学者 我想知道如何获取实际地址 例如,给出以下汇编代码: cmp %eax, 0x4(%rbp,%rbx,4) 我想知道与 %eax 进行比较的内容,换句话
所以我尝试练习在 python 中将函数和关键字作为参数传递,但我得到了一个奇怪的结果。我有以下代码: def myeval(f, *args, **kwargs): return f(*ar
我在 x86 处理器中使用 cmp 命令并且工作正常(二进制文件是使用 gcc 生成的)但是在arm cortex a9中使用它时,它没有给出正确的输出(二进制文件是使用交叉gcc生成的) 使用 cm
我收到以下错误: Assembler messages: Error: operand type mismatch for `cmp' 我的代码中唯一的 cmp 是: "cmpl %eax, $15\
我对组装很陌生,现在我想了解如何cmp作品。这是 wiki 中写的内容: cmp arg2, arg1 Performs a comparison operation between arg1 and
我在比较单个单词(2 个字节)时遇到了 CMP 指令的问题。 以下是我的main.asm: [org 0x7c00] mov bx, HELLO_MSG call print_string mov b
我想知道为什么 cmp 指令需要特定的参数顺序条件。 例如,我已经尝试过这两种方法。 cmpl %eax, $'A' cmpl $'A', %eax 第一行返回错误,表示操作数类型不匹配。第二线效果很
问题 以下两条 x86 指令之间有什么(重要的)区别? 39 /r CMP r/m32,r32 Compare r32 with r/m32 3B /r CMP r32,r/m32
我想知道为什么 cmp 指令需要特定的参数顺序条件。 例如,我已经尝试过这两种方法。 cmpl %eax, $'A' cmpl $'A', %eax 第一行返回错误,表示操作数类型不匹配。第二线效果很
所以我正在阅读一些 assembly source code出于学习目的,遇到了一些非常奇怪的事情(或者我可能只是一个新手): .ver: mov al, [redoxfs.header +
cmp file1 file2 当文件相同时不执行任何操作。那么如何在 shell 脚本中打印出相同的文件呢? 最佳答案 如果文件相同,cpm 的退出状态为零,否则为非零。因此,您可以使用类似 cmp
我希望获得 Bash 脚本循环方面的帮助,该循环将显示两个二进制文件之间的所有差异,仅使用 cmp file1 file2 它只显示了我想使用 cmp 的第一个更改,因为它给出了偏移量和每个更改所在的
有人能告诉我 cmp 命令输出中的“行”号代表什么吗?我问这个是因为,首先,我无法在任何地方找到它的解释。其次,我得到了比较一组文件的结果,其中“char”输出相同(如预期)但“line”输出差异很大
我是一名优秀的程序员,十分优秀!