Combien de temps système l’ajout de l’option -fPIC

Question

Je teste un code simple qui calcule la fractale de Mandelbrot. J’ai vérifié ses performances en fonction du nombre d’itérations dans la fonction qui vérifie si un point appartient ou non à l’ensemble de Mandelbrot. Ce qui est étonnant, c’est que -fPIC une grande différence de temps après l’ajout du drapeau -fPIC . D’après ce que j’ai lu, les frais généraux sont généralement négligeables et les frais généraux les plus élevés que j’ai constatés étaient d’environ 6%. Mon est autour de 30%. Tout conseil sera apprécié!

Détails de mon projet

J’utilise le drapeau -O3 , gcc 4.7.2, Ubuntu 12.04.2, x86_64. Les résultats se présentent comme suit

     #iter C (fPIC) CC / C (fPIC)
     1 0,01 0,01 1,00 
     100 0,04 0,03 0,75 
     200 0,06 0,04 0,67 
     500 0,15 0,1 0,67 
     1000 0,28 0,19 0,68
     2000 0,56 0,37 0,66 
     4000 1,11 0,72 0,65 
     8000 2,21 1,47 0,67
    16000 4,42 2,88 0,65 
    32000 8,8 5,77 0,66 
    64000 17,6 11,53 0,66

Les commandes que j’utilise:

 gcc -O3 -fPIC fractalMain.c fractal.c -o ffpic gcc -O3 fractalMain.c fractal.c -of 

Code: fractalMain.c

 #include  #include  #include  #include "fractal.h" int main() { int iterNumber[] = {1, 100, 200, 500, 1000, 2000, 4000, 8000, 16000, 32000, 64000}; int it; for(it = 0; it < 11; ++it) { clock_t start = clock(); fractal(iterNumber[it]); clock_t end = clock(); double millis = (end - start)*1000 / CLOCKS_PER_SEC/(double)1000; printf("Iter: %d, time: %lf \n", iterNumber[it], millis); } return 0; } 

Code: fractal.h

 #ifndef FRACTAL_H #define FRACTAL_H void fractal(int iter); #endif 

Code: fractal.c

 #include  #include  #include "fractal.h" void multiplyComplex(double a_re, double a_im, double b_re, double b_im, double* res_re, double* res_im) { *res_re = a_re*b_re - a_im*b_im; *res_im = a_re*b_im + a_im*b_re; } void sqComplex(double a_re, double a_im, double* res_re, double* res_im) { multiplyComplex(a_re, a_im, a_re, a_im, res_re, res_im); } bool isInSet(double P_re, double P_im, double C_re, double C_im, int iter) { double zPrev_re = P_re; double zPrev_im = P_im; double zNext_re = 0; double zNext_im = 0; double* p_zNext_re = &zNext_re; double* p_zNext_im = &zNext_im; int i; for(i = 1; i  4) { return false; } zPrev_re = zNext_re; zPrev_im = zNext_im; } return true; } bool isMandelbrot(double P_re, double P_im, int iter) { return isInSet(0, 0, P_re, P_im, iter); } void fractal(int iter) { int noIterations = iter; double xMin = -1.8; double xMax = 1.6; double yMin = -1.3; double yMax = 0.8; int xDim = 512; int yDim = 384; double P_re, P_im; int nop; int x, y; for(x = 0; x < xDim; ++x) for(y = 0; y < yDim; ++y) { P_re = (double)x*(xMax-xMin)/(double)xDim+xMin; P_im = (double)y*(yMax-yMin)/(double)yDim+yMin; if(isMandelbrot(P_re, P_im, noIterations)) nop = x+y; } printf("%d", nop); } 

Histoire derrière la comparaison

Il peut sembler un peu artificiel d’append l’indicateur -fPIC lors de la -fPIC exécutable (comme dans l’un des commentaires). Quelques mots d’explication: d’abord, j’ai seulement compilé le programme en tant qu’exécutable et j’ai voulu le comparer à mon code Lua, qui appelle la fonction isMandelbrot à partir de C. J’ai donc créé un object partagé pour l’appeler de lua – et avait de grandes différences de temps. Mais je ne comprenais pas pourquoi ils grandissaient avec le nombre d’itérations. -fPIC découvert que c’était à cause de -fPIC . Lorsque je crée un petit programme c qui appelle mon script lua (si bien que je fais la même chose, je n’ai pas besoin de .so), les temps sont très similaires à C (sans -fPIC ). Je l’ai donc vérifiée dans quelques configurations au cours des derniers jours et elle affiche systématiquement deux ensembles de résultats très similaires: plus rapide sans -fPIC et plus lent avec cela.

Il s’avère que lorsque vous comstackz sans l’option -fPIC , multiplyComplex , sqComplex , isInSet et isMandelbrot sont automatiquement isMandelbrot par le compilateur. Si vous définissez ces fonctions comme statiques, vous obtiendrez probablement les mêmes performances lors de la compilation avec -fPIC car le compilateur sera libre d’effectuer l’inline.

La raison pour laquelle le compilateur ne parvient pas à intégrer automatiquement les fonctions d’assistance est liée à l’interposition de symboles. Un code indépendant de la position est nécessaire pour accéder à toutes les données globales indirectement, c’est-à-dire via la table de décalage globale. La même contrainte s’applique aux appels de fonction, qui doivent passer par le tableau de liaison des procédures. Étant donné qu’un symbole peut être interposé par un autre lors de l’exécution (voir LD_PRELOAD ), le compilateur ne peut pas simplement supposer qu’il est sans danger d’insérer en ligne une fonction avec une visibilité globale.

La même hypothèse peut être faite si vous comstackz sans -fPIC , c’est-à-dire que le compilateur peut supposer en toute sécurité qu’un symbole global défini dans l’exécutable ne peut pas être interposé car la scope de la recherche commence par l’exécutable lui-même, suivi de toutes les autres bibliothèques, y compris les préchargés.

Pour une compréhension plus approfondie, consultez le document suivant.

Comme d’autres personnes l’ont déjà souligné, -fPIC oblige GCC à désactiver de nombreuses optimisations, telles que l’inclusion et le clonage. J’aimerais souligner plusieurs façons de surmonter ceci:

  • utilisez -fvisibility=hidden et __atsortingbute__((visibility("default"))) pour exporter uniquement les fonctions nécessaires de la bibliothèque et masquer le rest; cela permettrait à GCC d’optimiser les fonctions cachées
  • utilisez des alias de symboles privés ( __atsortingbute__((alias ("__f"))); ) pour faire référence à des fonctions de bibliothèque à partir de la bibliothèque; ce serait à nouveau délier les mains de GCC
  • la suggestion précédente peut être automatisée avec le drapeau -fno-semantic-interposition ajouté dans les dernières versions de GCC

Comme d’autres l’ont mentionné dans la section commentaires de votre message d’ouverture, comstackr avec -flto devrait aider à réduire la différence de temps d’exécution que vous voyez pour ce cas particulier, car les optimisations de temps de lien de gcc permettront probablement de comprendre que c’est ok pour intégrer quelques fonctions;)

En général, les optimisations de temps de liaison peuvent entraîner des réductions massives de la taille du code (~ 6%). Le lien vers les optimisations de temps de liaison en papier , et donc aussi le temps d’exécution (une plus grande partie de votre programme est inséré dans le cache). Notez également que -fPIC est généralement vu comme une fonctionnalité permettant une sécurité -fPIC et toujours activée dans Android . Cette question sur SO discute brièvement aussi. En outre, juste pour vous dire que -fpic est la version la plus rapide de -fPIC , donc si vous devez utiliser -fPIC essayez -fpic place – liez à gcc docs . Pour x86, cela ne fera peut-être pas une différence, mais vous devez vérifier cela vous-même / demander à l’aide de gcc-help.