4 ms·
The author says "In C99 the restrict keyword was added, which we could use here to encode that src and dst are different from all other references. This mecha
by ers35 15y ago
The author says "In C99 the restrict keyword was added, which we could use
here to encode that src and dst are different from all other references.
This mechanism helps in some cases, but not in our example."
Doesn't the example below show otherwise? Note the presence of movdqu in both
memcopy_normal and memcopy_restrict and how GCC emits five LEA instructions
without restrict, but only two when restrict is present.
This does not support the author's assertion that "Aliasing information is
the only one, where I am certain about speed improvements, because it is
impossible to reach Fortran-speed in C."
#include "stddef.h"
void* memcopy_normal(void* dst, const void* src, size_t count) {
while (count--) *(char*)dst++ = *(char*)src++;
return dst;
}
void* memcopy_restrict(void* restrict dst, const void* restrict src, size_t count) {
while (count--) *(char* restrict)dst++ = *(char* restrict)src++;
return dst;
}
> gcc -S -masm=intel -std=c99 -m64 -march=corei7 -O3 restrict2.c
.file "restrict2.c"
.intel_syntax noprefix
.text
.p2align 4,,15
.globl memcopy_normal
.type memcopy_normal, @function
memcopy_normal:
.LFB0:
.cfi_startproc
test rdx, rdx
mov rax, rdi
je .L2
lea r10, [rdx-1]
mov r8, rdx
shr r8, 4
mov r9, r8
sal r9, 4
test r9, r9
je .L7
lea rcx, [rsi+16]
cmp rdx, 15
lea r11, [rax+16]
seta dil
cmp rax, rcx
seta cl
cmp rsi, r11
seta r11b
or ecx, r11d
test dil, cl
je .L7
xor ecx, ecx
xor edi, edi
.p2align 4,,10
.p2align 3
.L4:
movdqu xmm0, XMMWORD PTR [rsi+rcx]
add rdi, 1
movdqu XMMWORD PTR [rax+rcx], xmm0
add rcx, 16
cmp r8, rdi
ja .L4
lea r8, [rax+r9]
add rsi, r9
sub r10, r9
cmp rdx, r9
je .L5
.L3:
lea r9, [r10+1]
xor ecx, ecx
.p2align 4,,10
.p2align 3
.L6:
movzx edi, BYTE PTR [rsi+rcx]
mov BYTE PTR [r8+rcx], dil
add rcx, 1
cmp r9, rcx
jne .L6
.L5:
add rax, rdx
.L2:
rep
ret
.L7:
mov r8, rax
jmp .L3
.cfi_endproc
.LFE0:
.size memcopy_normal, .-memcopy_normal
.p2align 4,,15
.globl memcopy_restrict
.type memcopy_restrict, @function
memcopy_restrict:
.LFB1:
.cfi_startproc
push r12
.cfi_def_cfa_offset 16
.cfi_offset 12, -16
test rdx, rdx
mov rax, rdi
push rbp
.cfi_def_cfa_offset 24
.cfi_offset 6, -24
push rbx
.cfi_def_cfa_offset 32
.cfi_offset 3, -32
je .L12
lea r10, [rdx-1]
mov r11, rsi
mov r8, rsi
neg r11
and r11d, 15
cmp r11, rdx
cmova r11, rdx
test r11, r11
je .L13
xor ecx, ecx
.p2align 4,,10
.p2align 3
.L14:
movzx r9d, BYTE PTR [r8]
add rcx, 1
add r8, 1
sub r10, 1
mov BYTE PTR [rdi], r9b
add rdi, 1
cmp r11, rcx
ja .L14
cmp rdx, r11
je .L15
.L13:
mov r12, rdx
sub r12, r11
mov rbx, r12
shr rbx, 4
mov rbp, rbx
sal rbp, 4
test rbp, rbp
je .L16
add rsi, r11
xor ecx, ecx
add r11, rax
xor r9d, r9d
.p2align 4,,10
.p2align 3
.L17:
movdqa xmm0, XMMWORD PTR [rsi+rcx]
add r9, 1
movdqu XMMWORD PTR [r11+rcx], xmm0
add rcx, 16
cmp rbx, r9
ja .L17
add rdi, rbp
add r8, rbp
sub r10, rbp
cmp r12, rbp
je .L15
.L16:
lea r9, [r10+1]
xor ecx, ecx
.p2align 4,,10
.p2align 3
.L18:
movzx esi, BYTE PTR [r8+rcx]
mov BYTE PTR [rdi+rcx], sil
add rcx, 1
cmp r9, rcx
jne .L18
.L15:
add rax, rdx
.L12:
pop rbx
.cfi_def_cfa_offset 24
pop rbp
.cfi_def_cfa_offset 16
pop r12
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE1:
.size memcopy_restrict, .-memcopy_restrict
.ident "GCC: (GNU) 4.6.3"
.section .note.GNU-stack,"",@progbits
- cliffbean 15y agoYes, the author is wrong there; restrict is valid and useful in that memcpy example. It is true though that restrict doesn't solve all of C's aliasing problems though. For example: #include <stdlib.h> struct array_3d { float * restrict data; size_t xmin, ymin, zmin; size_t xmax, ymax, zmax; size_t allocated; }; static inline float * array_3d_elem_addr(struct array_3d *a3d, size_t x, size_t y, size_t z) { return a3d->data + z + y * (a3d->zmax - a3d->zmin) + x * (a3d->zmax - a3d->zmin) * (a3d->ymax - a3d->ymin); } void array_3d_add(struct array_3d *restrict dst, struct array_3d *restrict src) { for (size_t x = dst->xmin; x != dst->xmax; ++x) for (size_t y = dst->ymin; y != dst->ymax; ++y) for (size_t z = dst->zmin; z != dst->zmax; ++z) *array_3d_elem_addr(dst, x, y, z) += *array_3d_elem_addr(src, x, y, z); } No amount of restrict keywords can tell the compiler that src->data and dst->data don't alias here (putting restrict on the 'data' member declaration inside array_3d doesn't mean anything here, because of the way restrict is defined). GCC manages to vectorize this loop, but only conditionally, with runtime alias checks. That's ok for this trivial example, but it isn't always viable in more complex examples. It's also worth noting that there are ways to rewrite this code so that it does fully vectorize, though again it's helped by the example being so trivial. The main point is that in Fortran, the alias rules are strong by default, without the programmer having to do acrobatics. Edit: formatting fixes
- cliffbean 15y agoAlso note that this restriction is in C itself. This is how restrict is defined in the C standard. It's not specific to GCC or any other compiler.
- DarkShikari 15y agoSadly, from what I recall, gcc has some internal limitations which prevent it from making significant use of "restrict". mans or astrange can probably elaborate.
- beza1e1 15y agomovdq is only used, if appropriate. There is a check, whether the pointer are at least 16 bytes apart.