【发布时间】:2014-07-01 22:23:03
【问题描述】:
我正在尝试将我的代码优化到最后一个可能的循环,并且想知道循环类型在用于数组索引时是否会影响性能?
我用以下程序做了一些实验,它只是用 0 填充数组:
int main(int argc, char **argv)
{
typedef int CounterType;
typedef int64_t CounterType;
CounterType N = atoi(argv[1]);
uint8_t volatile dummy[N + 16];
__m128i v = _mm_set1_epi8(0);
for (int j = 0; j < 1000000; ++j)
{
#pragma nounroll
for (CounterType i = 0; i <= N; i+= CounterType(16))
{
_mm_storeu_si128((__m128i *)&dummy[i], v);
}
}
return 0;
}
通过使用不同的循环计数器类型(CounterType)和不同的编译器, 我已经使用硬件性能计数器(“perf stat a.out 32768”)记录了内部循环的汇编代码和性能。我在 Xeon 5670 上运行。
GCC4.9,整数
.L3
movups %xmm0, (%rax)
addq $16, %rax
movl %eax, %edx
subl %esi, %edx
cmpl %ecx, %edx
jle .L3
4,127,525,521 cycles # 2.934 GHz
12,304,723,292 instructions # 2.98 insns per cycle
GCC4.9,int64
.L7
movups %xmm0, (%rcx,%rax)
addq $16, %rax
cmpq %rax, %rdx
jge .L7
4,123,315,191 cycles # 2.934 GHz
8,206,745,195 instructions # 1.99 insns per cycle
ICC11,int64
..B1.6:
movdqu %xmm0, (%rdx,%rdi)
addq $16, %rdx
incq %rcx
cmpq %rbx, %rcx
jb ..B1.6 # Prob 82% #24.5
2,069,719,166 cycles # 2.934 GHz
5,130,061,268 instructions
(因为微操作融合而更快?)
ICC11,整数
..B1.6: # Preds ..B1.4 ..B1.6
movdqu %xmm0, (%rdx,%rbx) #29.38
addq $16, %rdx #24.37
cmpq %rsi, %rdx #24.34
jle ..B1.6 # Prob 82% #24.34
4,136,109,529 cycles # 2.934 GHz
8,206,897,268 instructions
ICC13, int & int64
movdqu %xmm0, (%rdi,%rax) #29.38
addq $16, %rdi #24.37
cmpq %rsi, %rdi #24.34
jle ..B1.7
4,123,963,321 cycles # 2.934 GHz
8,206,083,789 instructions # 1.99 insns per cycle
数据似乎表明 int64 更快。也许这是因为它与指针大小匹配,因此避免了任何转换。但我不相信这个结论。另一种可能性可能是编译器在某些情况下决定在存储之前进行循环比较,以增加 1 条额外指令为代价实现更多并行性(由于 X86 2 操作数指令具有破坏性)。但这只是偶然的,而不是从根本上由循环变量类型引起的。
有人可以解释这个谜团(最好了解编译器转换)吗?
在 CUDA C 最佳实践指南中还声称,有符号循环计数器比无符号循环计数器更容易生成代码。但这在这里似乎无关紧要,因为在内部循环中没有用于地址计算的乘法运算,因为该表达式变成了一个归纳变量。但显然在 CUDA 中,它更喜欢使用乘加来计算地址,因为 MADD 是 1 条指令,就像 add 一样,它可以将寄存器使用减少 1。
【问题讨论】:
-
索引数组的首选类型是(unsigned)
size_t。 -
您使用了什么级别的优化?
-O3?
标签: c performance optimization assembly x86