【发布时间】:2021-11-28 20:28:45
【问题描述】:
我在 C 中做了一个bubble sort 实现,并在测试它的性能时注意到-O3 标志使它运行得比没有标志还要慢!同时-O2 让它运行得比预期的快很多。
没有优化:
time ./sort 30000
./sort 30000 1.82s user 0.00s system 99% cpu 1.816 total
-O2:
time ./sort 30000
./sort 30000 1.00s user 0.00s system 99% cpu 1.005 total
-O3:
time ./sort 30000
./sort 30000 2.01s user 0.00s system 99% cpu 2.007 total
代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
int n;
void bubblesort(int *buf)
{
bool changed = true;
for (int i = n; changed == true; i--) { /* will always move at least one element to its rightful place at the end, so can shorten the search by 1 each iteration */
changed = false;
for (int x = 0; x < i-1; x++) {
if (buf[x] > buf[x+1]) {
/* swap */
int tmp = buf[x+1];
buf[x+1] = buf[x];
buf[x] = tmp;
changed = true;
}
}
}
}
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "Usage: %s <arraysize>\n", argv[0]);
return EXIT_FAILURE;
}
n = atoi(argv[1]);
if (n < 1) {
fprintf(stderr, "Invalid array size.\n");
return EXIT_FAILURE;
}
int *buf = malloc(sizeof(int) * n);
/* init buffer with random values */
srand(time(NULL));
for (int i = 0; i < n; i++)
buf[i] = rand() % n + 1;
bubblesort(buf);
return EXIT_SUCCESS;
}
为-O2(来自godbolt.org)生成的汇编语言:
bubblesort:
mov r9d, DWORD PTR n[rip]
xor edx, edx
xor r10d, r10d
.L2:
lea r8d, [r9-1]
cmp r8d, edx
jle .L13
.L5:
movsx rax, edx
lea rax, [rdi+rax*4]
.L4:
mov esi, DWORD PTR [rax]
mov ecx, DWORD PTR [rax+4]
add edx, 1
cmp esi, ecx
jle .L2
mov DWORD PTR [rax+4], esi
mov r10d, 1
add rax, 4
mov DWORD PTR [rax-4], ecx
cmp r8d, edx
jg .L4
mov r9d, r8d
xor edx, edx
xor r10d, r10d
lea r8d, [r9-1]
cmp r8d, edx
jg .L5
.L13:
test r10b, r10b
jne .L14
.L1:
ret
.L14:
lea eax, [r9-2]
cmp r9d, 2
jle .L1
mov r9d, r8d
xor edx, edx
mov r8d, eax
xor r10d, r10d
jmp .L5
-O3 也一样:
bubblesort:
mov r9d, DWORD PTR n[rip]
xor edx, edx
xor r10d, r10d
.L2:
lea r8d, [r9-1]
cmp r8d, edx
jle .L13
.L5:
movsx rax, edx
lea rcx, [rdi+rax*4]
.L4:
movq xmm0, QWORD PTR [rcx]
add edx, 1
pshufd xmm2, xmm0, 0xe5
movd esi, xmm0
movd eax, xmm2
pshufd xmm1, xmm0, 225
cmp esi, eax
jle .L2
movq QWORD PTR [rcx], xmm1
mov r10d, 1
add rcx, 4
cmp r8d, edx
jg .L4
mov r9d, r8d
xor edx, edx
xor r10d, r10d
lea r8d, [r9-1]
cmp r8d, edx
jg .L5
.L13:
test r10b, r10b
jne .L14
.L1:
ret
.L14:
lea eax, [r9-2]
cmp r9d, 2
jle .L1
mov r9d, r8d
xor edx, edx
mov r8d, eax
xor r10d, r10d
jmp .L5
似乎对我来说唯一显着的不同是明显尝试使用SIMD,这似乎应该是一个很大的改进,但我也无法判断它到底是什么尝试使用那些pshufd 指令...这只是 SIMD 的失败尝试吗?或者也许这两条额外的指令只是为了消除我的指令缓存?
计时是在 AMD Ryzen 5 3600 上完成的。
【问题讨论】:
-
@Abel:
gcc -Ofast只是-O3 -ffast-math的快捷方式,但这里没有 FP 数学。如果您要尝试任何东西,请尝试-O3 -march=native让它使用 AVX2,以防 GCC 的矢量化策略可以帮助更广泛的矢量而不是伤害,无论它试图做什么。虽然我不这么认为;它只是进行 64 位加载和随机播放,甚至没有使用 SSE2 的 128 位。 -
至少在旧版本的 gcc 上,
-Os(优化空间)有时会产生最快的代码,因为 x86-64 上指令缓存的大小。我不知道这在这里是否重要,或者它是否仍然适用于当前版本的 gcc,但尝试和比较可能会很有趣。 -
@DavidConrad:-Os 会让 GCC 选择不自动矢量化,所以它与我期望的
-O2大致相同,而不是用商店转发摊位自取其辱并在检测到分支错误预测之前增加了延迟。 -
您应该包含您的实际编译器输出的汇编代码,而不是来自 godbolt.org。
-
@user253751:不同意;只要查询者在 Godbolt 上选择了与本地相同的 GCC 版本,因此指令相同,Godbolt 对指令的良好过滤就更好了。并且在 Godbolt 上链接 source+asm 可以让任何想要查看其他 GCC 版本/选项的人更好。
标签: c gcc x86-64 cpu-architecture compiler-optimization