Complex multiplication in gcc
Sean McAllister
smcallis@gmail.com
Mon Jul 17 16:52:00 GMT 2017
When generating code for a simple inner loop (instantiated with
std::complex<float>)
template <typename cx>
void __attribute__((noinline)) benchcore(const cx* __restrict__ aa,
const cx* __restrict__ bb, const cx* __restrict__ cc, cx* __restrict__
dd, cx uu, cx vv, size_t nn) {
for (ssize_t ii=0; ii < nn; ii++) {
dd[ii] = (
aa[ii]*uu +
bb[ii]*vv +
cc[ii]
);
}
}
g++ generates the following assembly code (g++ 7.1.0) (compiled with:
g++ -I. test.cc -O3 -ggdb3 -o test)
Dump of assembler code for function benchcore<std::complex<float>
>(std::complex<float> const*, std::complex<float> const*,
std::complex<float> const*, std::complex<float>*, std::complex<float>,
std::complex<float>, unsigned long):
0x00000000004029d0 <+0>: push %r15
0x00000000004029d2 <+2>: push %r14
0x00000000004029d4 <+4>: push %r13
0x00000000004029d6 <+6>: push %r12
0x00000000004029d8 <+8>: push %rbp
0x00000000004029d9 <+9>: push %rbx
0x00000000004029da <+10>: sub $0x38,%rsp
0x00000000004029de <+14>: test %r8,%r8
0x00000000004029e1 <+17>: movq %xmm0,0x28(%rsp)
0x00000000004029e7 <+23>: movq %xmm1,0x20(%rsp)
0x00000000004029ed <+29>: movss 0x28(%rsp),%xmm4
0x00000000004029f3 <+35>: movss 0x2c(%rsp),%xmm5
0x00000000004029f9 <+41>: movss 0x20(%rsp),%xmm6
0x00000000004029ff <+47>: movss 0x24(%rsp),%xmm7
0x0000000000402a05 <+53>: movss %xmm4,(%rsp)
0x0000000000402a0a <+58>: movss %xmm5,0x4(%rsp)
0x0000000000402a10 <+64>: movss %xmm6,0x8(%rsp)
0x0000000000402a16 <+70>: movss %xmm7,0xc(%rsp)
0x0000000000402a1c <+76>: je 0x402abd
<benchcore<std::complex<float> >(std::complex<float> const*,
std::complex<float> const*, std::complex<float> const*,
std::complex<float>*, std::complex<float>, std::complex<float>,
unsigned long)+237>
0x0000000000402a22 <+82>: mov %r8,%r13
0x0000000000402a25 <+85>: mov %rcx,%r15
0x0000000000402a28 <+88>: mov %rdx,%r14
0x0000000000402a2b <+91>: mov %rsi,%r12
0x0000000000402a2e <+94>: mov %rdi,%rbp
0x0000000000402a31 <+97>: xor %ebx,%ebx
0x0000000000402a33 <+99>: nopl 0x0(%rax,%rax,1)
0x0000000000402a38 <+104>: movss 0x4(%r12,%rbx,8),%xmm3
0x0000000000402a3f <+111>: movss (%r12,%rbx,8),%xmm2
0x0000000000402a45 <+117>: movss 0x8(%rsp),%xmm0
0x0000000000402a4b <+123>: movss 0xc(%rsp),%xmm1
0x0000000000402a51 <+129>: callq 0x400aa0 <__mulsc3@plt>
0x0000000000402a56 <+134>: movq %xmm0,0x18(%rsp)
0x0000000000402a5c <+140>: movss 0x4(%rsp),%xmm3
0x0000000000402a62 <+146>: movss 0x4(%rbp,%rbx,8),%xmm1
0x0000000000402a68 <+152>: movss 0x0(%rbp,%rbx,8),%xmm0
0x0000000000402a6e <+158>: movss (%rsp),%xmm2
0x0000000000402a73 <+163>: callq 0x400aa0 <__mulsc3@plt>
0x0000000000402a78 <+168>: movq %xmm0,0x10(%rsp)
0x0000000000402a7e <+174>: movss 0x18(%rsp),%xmm1
0x0000000000402a84 <+180>: movss 0x1c(%rsp),%xmm0
0x0000000000402a8a <+186>: addss 0x14(%rsp),%xmm0
0x0000000000402a90 <+192>: addss 0x10(%rsp),%xmm1
0x0000000000402a96 <+198>: addss 0x4(%r14,%rbx,8),%xmm0
0x0000000000402a9d <+205>: addss (%r14,%rbx,8),%xmm1
0x0000000000402aa3 <+211>: movss %xmm0,0x4(%r15,%rbx,8)
0x0000000000402aaa <+218>: movss %xmm1,(%r15,%rbx,8)
0x0000000000402ab0 <+224>: add $0x1,%rbx
0x0000000000402ab4 <+228>: cmp %r13,%rbx
0x0000000000402ab7 <+231>: jne 0x402a38
<benchcore<std::complex<float> >(std::complex<float> const*,
std::complex<float> const*, std::complex<float> const*,
std::complex<float>*, std::complex<float>, std::complex<float>,
unsigned long)+104>
0x0000000000402abd <+237>: add $0x38,%rsp
0x0000000000402ac1 <+241>: pop %rbx
0x0000000000402ac2 <+242>: pop %rbp
0x0000000000402ac3 <+243>: pop %r12
0x0000000000402ac5 <+245>: pop %r13
0x0000000000402ac7 <+247>: pop %r14
0x0000000000402ac9 <+249>: pop %r15
0x0000000000402acb <+251>: retq
End of assembler dump.
The interesting part is the two calls to __mulsc3, which the docs
indicate computes complex multiplication according to Annex G of the
C99 standard. This leads me to two questions.
First, disassembling __mulsc3 doesn't seem to contain anything:
(gdb) disassemble __mulsc3
Dump of assembler code for function __mulsc3@plt:
0x0000000000400aa0 <+0>: jmpq *0x2035d2(%rip) # 0x604078
0x0000000000400aa6 <+6>: pushq $0xc
0x0000000000400aab <+11>: jmpq 0x4009d0
End of assembler dump.
What's the cause of this?
Second, since I don't think I'll convince anyone to generate
non-standard conforming code by default, could the default performance
of complex multiplication be enhanced significantly by performing the
isnan() checks required by Annex G and only calling the function to
fix the results if they fail? That would move the function call
overhead out of the critical path at least.
More information about the Gcc
mailing list