Le mot-clé fournit des avantages significatifs en limiter gcc / g ++?
-
21-09-2019 - |
Question
Quelqu'un at-il déjà vu des chiffres / analyses sur si oui ou non l'utilisation du C / C ++ de mot-clé restrict
dans gcc / g ++ réelle fournit une augmentation significative des performances en réalité (et pas seulement en théorie)?
J'ai lu divers articles recommandant / dénigrant son utilisation, mais je ne l'ai pas couru à travers les nombres réels démontrant pratiquement soit des arguments côtés.
EDIT
Je sais que restrict
n'est pas officiellement partie de C ++, mais il est pris en charge par certains compilateurs et j'ai lu un document par Christer Ericson qui recommande fortement son utilisation.
La solution
Le mot-clé restreindre fait une différence.
J'ai vu des améliorations du facteur 2 et plus dans certaines situations (traitement d'images). La plupart du temps, la différence est pas grand bien. Environ 10%.
Voici un petit exemple qui illustre la différence. J'ai écrit une matrice de vecteur 4x4 * très basique transformée en un test. Notez que je dois forcer la fonction ne doit pas être inline. Sinon GCC détecte qu'il n'y a pas de pointeurs aliasing dans mon code de référence et ne restreignent faire une différence en raison de inline.
Je aurais pu déplacer la fonction de transformation à un autre fichier ainsi.
#include <math.h>
#ifdef USE_RESTRICT
#else
#define __restrict
#endif
void transform (float * __restrict dest, float * __restrict src,
float * __restrict matrix, int n) __attribute__ ((noinline));
void transform (float * __restrict dest, float * __restrict src,
float * __restrict matrix, int n)
{
int i;
// simple transform loop.
// written with aliasing in mind. dest, src and matrix
// are potentially aliasing, so the compiler is forced to reload
// the values of matrix and src for each iteration.
for (i=0; i<n; i++)
{
dest[0] = src[0] * matrix[0] + src[1] * matrix[1] +
src[2] * matrix[2] + src[3] * matrix[3];
dest[1] = src[0] * matrix[4] + src[1] * matrix[5] +
src[2] * matrix[6] + src[3] * matrix[7];
dest[2] = src[0] * matrix[8] + src[1] * matrix[9] +
src[2] * matrix[10] + src[3] * matrix[11];
dest[3] = src[0] * matrix[12] + src[1] * matrix[13] +
src[2] * matrix[14] + src[3] * matrix[15];
src += 4;
dest += 4;
}
}
float srcdata[4*10000];
float dstdata[4*10000];
int main (int argc, char**args)
{
int i,j;
float matrix[16];
// init all source-data, so we don't get NANs
for (i=0; i<16; i++) matrix[i] = 1;
for (i=0; i<4*10000; i++) srcdata[i] = i;
// do a bunch of tests for benchmarking.
for (j=0; j<10000; j++)
transform (dstdata, srcdata, matrix, 10000);
}
Résultats: (sur mon 2 Ghz Core Duo)
nils@doofnase:~$ gcc -O3 test.c
nils@doofnase:~$ time ./a.out
real 0m2.517s
user 0m2.516s
sys 0m0.004s
nils@doofnase:~$ gcc -O3 -DUSE_RESTRICT test.c
nils@doofnase:~$ time ./a.out
real 0m2.034s
user 0m2.028s
sys 0m0.000s
Au cours du pouce 20% plus rapide exécution, sur que système.
Pour montrer combien il dépend de l'architecture que j'ai laissé le même code exécuté sur un processeur embarqué Cortex-A8 (ajusté de la boucle compter un peu parce que je ne veux pas attendre aussi longtemps):
root@beagleboard:~# gcc -O3 -mcpu=cortex-a8 -mfpu=neon -mfloat-abi=softfp test.c
root@beagleboard:~# time ./a.out
real 0m 7.64s
user 0m 7.62s
sys 0m 0.00s
root@beagleboard:~# gcc -O3 -mcpu=cortex-a8 -mfpu=neon -mfloat-abi=softfp -DUSE_RESTRICT test.c
root@beagleboard:~# time ./a.out
real 0m 7.00s
user 0m 6.98s
sys 0m 0.00s
Ici, la différence est à seulement 9% (même compilateur btw.)
Autres conseils
Le mot-clé restreindre des avantages significatifs en fournir gcc / g ++?
peut réduire le nombre d'instructions comme indiqué sur l'exemple ci-dessous, afin de l'utiliser autant que possible.
GCC 4.8 Linux x86-64 exmample
Entrée:
void f(int *a, int *b, int *x) {
*a += *x;
*b += *x;
}
void fr(int *restrict a, int *restrict b, int *restrict x) {
*a += *x;
*b += *x;
}
Compiler et décompiler:
gcc -g -std=c99 -O0 -c main.c
objdump -S main.o
Avec -O0
, ils sont les mêmes.
Avec -O3
:
void f(int *a, int *b, int *x) {
*a += *x;
0: 8b 02 mov (%rdx),%eax
2: 01 07 add %eax,(%rdi)
*b += *x;
4: 8b 02 mov (%rdx),%eax
6: 01 06 add %eax,(%rsi)
void fr(int *restrict a, int *restrict b, int *restrict x) {
*a += *x;
10: 8b 02 mov (%rdx),%eax
12: 01 07 add %eax,(%rdi)
*b += *x;
14: 01 06 add %eax,(%rsi)
Pour les non-initiés, le appelant la convention est:
-
rdi
= premier paramètre -
rsi
= deuxième paramètre -
rdx
= troisième paramètre
3 instructions au lieu de 4
.Bien sûr, les instructions peuvent avoir des latences , mais cela donne une bonne idée.
Pourquoi GCC a pu optimiser cela?
Le code ci-dessus était pris de la Wikipedia exemple qui est très éclairante.
Ensemble de pseudo pour f
:
load R1 ← *x ; Load the value of x pointer
load R2 ← *a ; Load the value of a pointer
add R2 += R1 ; Perform Addition
set R2 → *a ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because a may be equal to x.
load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b
Pour fr
:
load R1 ← *x
load R2 ← *a
add R2 += R1
set R2 → *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b
Est-il vraiment plus vite?
ermmm ... pas pour ce test simple:
.text
.global _start
_start:
mov $0x10000000, %rbx
mov $x, %rdx
mov $x, %rdi
mov $x, %rsi
loop:
# START of interesting block
mov (%rdx),%eax
add %eax,(%rdi)
mov (%rdx),%eax # Comment out this line.
add %eax,(%rsi)
# END ------------------------
dec %rbx
cmp $0, %rbx
jnz loop
mov $60, %rax
mov $0, %rdi
syscall
.data
x:
.int 0
Et puis:
as -o a.o a.S && ld a.o && time ./a.out
sur Ubuntu 14.04 AMD64 CPU Intel i5-3210M.
Je vous avoue que je ne comprends toujours pas les processeurs modernes. Faites-moi savoir si vous:
- a trouvé une faille dans ma méthode
- a trouvé un cas de test assembleur où il devient beaucoup plus rapide
- comprendre pourquoi il n'y avait pas une différence
L'article Démystifier Le les mots-clés Restreint fait référence au document Pourquoi programmeur-Aliasing est spécifié Bad Idea ( pdf) qui dit généralement il ne permet pas et fournit des mesures pour étayer cette thèse.
Notez que les compilateurs C qui permettent le mot-clé restrict
peut encore l'ignorer. Tel est le cas par exemple ici .
ce C-programme. Sans restrict
il a fallu 12.640 secondes pour terminer, avec restrict
12,516. On dirait que peut Enregistrer certains temps.