如果启用 OpenMP,gcc 必须生成适用于仅在运行时已知的任意数量线程的不同代码。
在这种特殊情况下,看一下输出gcc -S
(通过标签稍微缩短)。
没有 OpenMP:
.loc 1 38 0 discriminator 2 # Line 38 is J[k]+=1;
movsd 8(%rsp), %xmm1
cvttsd2si %xmm0, %edx
cvttsd2si %xmm1, %eax
movsd .LC3(%rip), %xmm0
imull $1000, %eax, %eax
addl %edx, %eax
cltq
salq $3, %rax
leaq 0(%r13,%rax), %rdx
.loc 1 40 0 discriminator 2 # Line 40 is Jb[k]+=1;
addq %r12, %rax
.loc 1 29 0 discriminator 2
cmpq $8000000, %r15
.loc 1 38 0 discriminator 2
addsd (%rdx), %xmm0
movsd %xmm0, (%rdx)
.loc 1 40 0 discriminator 2
movsd .LC3(%rip), %xmm0
addsd (%rax), %xmm0
movsd %xmm0, (%rax)
循环展开使得这变得相当复杂。
With -fopenmp
:
movsd (%rsp), %xmm2
cvttsd2si %xmm0, %eax
cvttsd2si %xmm2, %ecx
imull $1000, %ecx, %ecx
addl %eax, %ecx
movslq %ecx, %rcx
salq $3, %rcx
movq %rcx, %rsi
addq 16(%rbp), %rsi
movq (%rsi), %rdx
movsd 8(%rsp), %xmm1
jmp .L4
movq %rax, %rdx
movq %rdx, (%rsp)
movq %rdx, %rax
movsd (%rsp), %xmm3
addsd %xmm1, %xmm3
movq %xmm3, %rdi
lock cmpxchgq %rdi, (%rsi)
cmpq %rax, %rdx
jne .L9
.loc 1 40 0
addq 24(%rbp), %rcx
movq (%rcx), %rdx
jmp .L5
.p2align 4,,10
.p2align 3
movq %rax, %rdx
movq %rdx, (%rsp)
movq %rdx, %rax
movsd (%rsp), %xmm4
addsd %xmm1, %xmm4
movq %xmm4, %rsi
lock cmpxchgq %rsi, (%rcx)
cmpq %rax, %rdx
jne .L10
addq $8, %r12
cmpq %r12, %rbx
jne .L6
我不会尝试解释或理解这里发生的所有细节,但这对于消息来说不是必需的:编译器必须使用可能更昂贵的不同原子指令,尤其是lock cmpxchgq
.
除了这个基本问题之外,OpenMP 可能会以任何可以想象的方式干扰优化器,例如干扰展开。我还看到了一个奇怪的案例,英特尔编译器实际上为 OpenMP 循环生成了更高效的串行代码。
附:认为自己很幸运——情况可能会更糟。如果编译器无法将原子指令映射到硬件指令,则必须使用锁,这会更慢。