I am running a math-oriented computation that spends a significant amount of its time doing memcpy
, always copying 80 bytes from one location to the next, an array of 20 32-bit int
s. The total computation takes around 4-5 days using both cores of my i7, so even a 1% speedup results in about an hour saved.
By using the memcpy
in this paper by Intel, I was able to speed up by about 25%, and also dropping the size argument and simply declaring inside seems to have some small effect. However, I feel I am not utilising the fact that my copying operations are always the same size. That said, I can't come up with a better way.
void *memcpyi80(void* __restrict b, const void* __restrict a){
size_t n = 80;
char *s1 = b;
const char *s2 = a;
for(; 0<n; --n)*s1++ = *s2++;
return b;
}
Some other things that may be useful for optimization:
I use an Intel Core i7-2620M, based on Sandy Bridge. I don't care about portability at all.
I only care about the 16 least significant bits of every int. The other 16 are useless to me and are permanently zeroed out.
Even though I copy 20 32-bit ints per memcpy invocation, I only care about the first 17. I have added 3 as it helps with alignment and therefore speed.
I use GCC 4.6 on Windows 7.
Any ideas?
UPDATE:
I think this is the assembly output (never done this before, there may be more than you need):
memcpyi80:
pushq %r12
.seh_pushreg %r12
pushq %rbp
.seh_pushreg %rbp
pushq %rdi
.seh_pushreg %rdi
pushq %rsi
.seh_pushreg %rsi
pushq %rbx
.seh_pushreg %rbx
.seh_endprologue
movq %rdx, %r9
movq %rcx, %rax
negq %r9
andl $15, %r9d
je .L165
movzbl (%rdx), %ecx
leaq -1(%r9), %r10
movl $79, %esi
andl $7, %r10d
cmpq $1, %r9
movl $79, %ebx
leaq 1(%rdx), %r8
movl $1, %r11d
movb %cl, (%rax)
leaq 1(%rax), %rcx
jbe .L159
testq %r10, %r10
je .L160
cmpq $1, %r10
je .L250
cmpq $2, %r10
je .L251
cmpq $3, %r10
je .L252
cmpq $4, %r10
je .L253
cmpq $5, %r10
je .L254
cmpq $6, %r10
je .L255
movzbl (%r8), %r8d
movl $2, %r11d
movb %r8b, (%rcx)
leaq 2(%rax), %rcx
leaq 2(%rdx), %r8
.L255:
movzbl (%r8), %ebx
addq $1, %r11
addq $1, %r8
movb %bl, (%rcx)
addq $1, %rcx
.L254:
movzbl (%r8), %r10d
addq $1, %r11
addq $1, %r8
movb %r10b, (%rcx)
addq $1, %rcx
.L253:
movzbl (%r8), %edi
addq $1, %r11
addq $1, %r8
movb %dil, (%rcx)
addq $1, %rcx
.L252:
movzbl (%r8), %ebp
addq $1, %r11
addq $1, %r8
movb %bpl, (%rcx)
addq $1, %rcx
.L251:
movzbl (%r8), %r12d
addq $1, %r11
addq $1, %r8
movb %r12b, (%rcx)
addq $1, %rcx
.L250:
movzbl (%r8), %ebx
addq $1, %r8
movb %bl, (%rcx)
movq %rsi, %rbx
addq $1, %rcx
subq %r11, %rbx
addq $1, %r11
cmpq %r11, %r9
jbe .L159
.p2align 4,,10
.L160:
movzbl (%r8), %r12d
movb %r12b, (%rcx)
movzbl 1(%r8), %ebp
movb %bpl, 1(%rcx)
movzbl 2(%r8), %edi
movb %dil, 2(%rcx)
movzbl 3(%r8), %ebx
movb %bl, 3(%rcx)
leaq 7(%r11), %rbx
addq $8, %r11
movzbl 4(%r8), %r10d
movb %r10b, 4(%rcx)
movq %rsi, %r10
movzbl 5(%r8), %r12d
subq %rbx, %r10
movq %r10, %rbx
movb %r12b, 5(%rcx)
movzbl 6(%r8), %ebp
movb %bpl, 6(%rcx)
movzbl 7(%r8), %edi
addq $8, %r8
movb %dil, 7(%rcx)
addq $8, %rcx
cmpq %r11, %r9
ja .L160
.L159:
movl $80, %r12d
subq %r9, %r12
movq %r12, %rsi
shrq $4, %rsi
movq %rsi, %rbp
salq $4, %rbp
testq %rbp, %rbp
je .L161
leaq (%rdx,%r9), %r10
addq %rax, %r9
movl $1, %r11d
leaq -1(%rsi), %rdi
vmovdqa (%r10), %xmm0
movl $16, %edx
andl $7, %edi
cmpq $1, %rsi
vmovdqu %xmm0, (%r9)
jbe .L256
testq %rdi, %rdi
je .L162
cmpq $1, %rdi
je .L244
cmpq $2, %rdi
je .L245
cmpq $3, %rdi
je .L246
cmpq $4, %rdi
je .L247
cmpq $5, %rdi
je .L248
cmpq $6, %rdi
je .L249
vmovdqa 16(%r10), %xmm3
movl $2, %r11d
movl $32, %edx
vmovdqu %xmm3, 16(%r9)
.L249:
vmovdqa (%r10,%rdx), %xmm4
addq $1, %r11
vmovdqu %xmm4, (%r9,%rdx)
addq $16, %rdx
.L248:
vmovdqa (%r10,%rdx), %xmm5
addq $1, %r11
vmovdqu %xmm5, (%r9,%rdx)
addq $16, %rdx
.L247:
vmovdqa (%r10,%rdx), %xmm0
addq $1, %r11
vmovdqu %xmm0, (%r9,%rdx)
addq $16, %rdx
.L246:
vmovdqa (%r10,%rdx), %xmm1
addq $1, %r11
vmovdqu %xmm1, (%r9,%rdx)
addq $16, %rdx
.L245:
vmovdqa (%r10,%rdx), %xmm2
addq $1, %r11
vmovdqu %xmm2, (%r9,%rdx)
addq $16, %rdx
.L244:
vmovdqa (%r10,%rdx), %xmm3
addq $1, %r11
vmovdqu %xmm3, (%r9,%rdx)
addq $16, %rdx
cmpq %r11, %rsi
jbe .L256
.p2align 4,,10
.L162:
vmovdqa (%r10,%rdx), %xmm2
addq $8, %r11
vmovdqu %xmm2, (%r9,%rdx)
vmovdqa 16(%r10,%rdx), %xmm1
vmovdqu %xmm1, 16(%r9,%rdx)
vmovdqa 32(%r10,%rdx), %xmm0
vmovdqu %xmm0, 32(%r9,%rdx)
vmovdqa 48(%r10,%rdx), %xmm5
vmovdqu %xmm5, 48(%r9,%rdx)
vmovdqa 64(%r10,%rdx), %xmm4
vmovdqu %xmm4, 64(%r9,%rdx)
vmovdqa 80(%r10,%rdx), %xmm3
vmovdqu %xmm3, 80(%r9,%rdx)
vmovdqa 96(%r10,%rdx), %xmm2
vmovdqu %xmm2, 96(%r9,%rdx)
vmovdqa 112(%r10,%rdx), %xmm1
vmovdqu %xmm1, 112(%r9,%rdx)
subq $-128, %rdx
cmpq %r11, %rsi
ja .L162
.L256:
addq %rbp, %rcx
addq %rbp, %r8
subq %rbp, %rbx
cmpq %rbp, %r12
je .L163
.L161:
movzbl (%r8), %edx
leaq -1(%rbx), %r9
andl $7, %r9d
movb %dl, (%rcx)
movl $1, %edx
cmpq %rbx, %rdx
je .L163
testq %r9, %r9
je .L164
cmpq $1, %r9
je .L238
cmpq $2, %r9
je .L239
cmpq $3, %r9
je .L240
cmpq $4, %r9
je .L241
cmpq $5, %r9
je .L242
cmpq $6, %r9
je .L243
movzbl 1(%r8), %edx
movb %dl, 1(%rcx)
movl $2, %edx
.L243:
movzbl (%r8,%rdx), %esi
movb %sil, (%rcx,%rdx)
addq $1, %rdx
.L242:
movzbl (%r8,%rdx), %r11d
movb %r11b, (%rcx,%rdx)
addq $1, %rdx
.L241:
movzbl (%r8,%rdx), %r10d
movb %r10b, (%rcx,%rdx)
addq $1, %rdx
.L240:
movzbl (%r8,%rdx), %edi
movb %dil, (%rcx,%rdx)
addq $1, %rdx
.L239:
movzbl (%r8,%rdx), %ebp
movb %bpl, (%rcx,%rdx)
addq $1, %rdx
.L238:
movzbl (%r8,%rdx), %r12d
movb %r12b, (%rcx,%rdx)
addq $1, %rdx
cmpq %rbx, %rdx
je .L163
.p2align 4,,10
.L164:
movzbl (%r8,%rdx), %r9d
movb %r9b, (%rcx,%rdx)
movzbl 1(%r8,%rdx), %r12d
movb %r12b, 1(%rcx,%rdx)
movzbl 2(%r8,%rdx), %ebp
movb %bpl, 2(%rcx,%rdx)
movzbl 3(%r8,%rdx), %edi
movb %dil, 3(%rcx,%rdx)
movzbl 4(%r8,%rdx), %r10d
movb %r10b, 4(%rcx,%rdx)
movzbl 5(%r8,%rdx), %r11d
movb %r11b, 5(%rcx,%rdx)
movzbl 6(%r8,%rdx), %esi
movb %sil, 6(%rcx,%rdx)
movzbl 7(%r8,%rdx), %r9d
movb %r9b, 7(%rcx,%rdx)
addq $8, %rdx
cmpq %rbx, %rdx
jne .L164
.L163:
popq %rbx
popq %rsi
popq %rdi
popq %rbp
popq %r12
ret
.L165:
movq %rdx, %r8
movl $80, %ebx
jmp .L159
.seh_endproc
.p2align 4,,15
.globl memcpyi
.def memcpyi; .scl 2; .type 32; .endef
.seh_proc memcpyi
UPDATE:
By building on Peter Alexander's solution and combining it with ideas from around the thread, I have produced this:
void memcpyi80(void* __restrict b, const void* __restrict a){
__m128 *s1 = b;
const __m128 *s2 = a;
*s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++;
}
The speedup is small but measurable (about 1%). Now I guess my next temptation is to find how to use __m256 AVX types so I can do it in 3 steps rather than 5.
UPDATE:
The __m256 type requires alignment on the 32-bit barrier, which makes things slower, so it seems __m128 is a sweet spot.
memcpy
) can be optimized? What I see a lot is people think the problem is here when it's not. It's there. \$\endgroup\$-O3 -march=sandybridge
. \$\endgroup\$