c performance gcc

¿Cuánta sobrecarga puede agregar la marca-fPIC?



performance gcc (3)

Como otros han discutido en la sección de comentarios de su publicación de apertura, compilar con -flto debería ayudar a reducir la diferencia en los tiempos de ejecución que está viendo para este caso en particular, ya que las optimizaciones de tiempo de enlace de gcc probablemente descubrirán que en realidad está bien para integrar un par de funciones;)

En general, las optimizaciones de tiempo de enlace podrían llevar a reducciones masivas en el tamaño del código (~ 6%) al papel en las optimizaciones de tiempo de enlace en oro , y por lo tanto también al tiempo de ejecución (más de su programa cabe en el caché). También tenga en cuenta que -fPIC se ve principalmente como una característica que permite una seguridad más estricta y siempre está habilitada en Android . Esta pregunta sobre SO también discute brevemente. Además, solo para informarle, -fpic es la versión más rápida de -fPIC , por lo que si debe usar -fPIC intente -fpic en -fpic lugar, enlace a los documentos de gcc . Para x86 puede que no haga una diferencia, pero necesita comprobarlo por usted mismo / preguntar en gcc-help.

Pregunta

Estoy probando un código simple que calcula el fractal de Mandelbrot. He estado verificando su rendimiento dependiendo de la cantidad de iteraciones en la función que verifica si un punto pertenece al conjunto de Mandelbrot o no. Lo sorprendente es que estoy obteniendo una gran diferencia en los tiempos después de agregar la -fPIC . Por lo que leo, los gastos generales suelen ser insignificantes y los gastos generales más elevados que encontré fueron aproximadamente el 6%. Mi alrededor del 30%. ¡Cualquier consejo será apreciado!

Detalles de mi proyecto

Yo uso la bandera -O3 , gcc 4.7.2, Ubuntu 12.04.2, x86_64. Los resultados se ven como sigue.

#iter C (fPIC) C C/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

Comandos que utilizo:

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

Código: fractalMain.c

#include <time.h> #include <stdio.h> #include <stdbool.h> #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; }

Código: fractal.h

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

Código: fractal.c

#include <stdio.h> #include <stdbool.h> #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 <= iter; ++i) { sqComplex(zPrev_re, zPrev_im, p_zNext_re, p_zNext_im); zNext_re = zNext_re + C_re; zNext_im = zNext_im + C_im; if(zNext_re*zNext_re+zNext_im*zNext_im > 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); }

Historia detrás de la comparación.

Puede parecer un poco artificial agregar el indicador -fPIC al -fPIC ejecutable (según uno de los comentarios). Unas pocas palabras de explicación: primero compilé el programa como ejecutable y quise compararlo con mi código Lua, que llama a la función isMandelbrot desde C. Así que creé un objeto compartido para llamarlo desde lua y tuve grandes diferencias de tiempo. Pero no podía entender por qué crecían con varias iteraciones. Al final descubrí que era por el -fPIC . Cuando creo un pequeño programa c que llama a mi script lua (así que efectivamente hago lo mismo, solo que no necesito el .so) - los tiempos son muy similares a los de C (sin -fPIC ). Así que lo he comprobado en algunas configuraciones en los últimos días y muestra de manera consistente dos conjuntos de resultados muy similares: más rápido sin -fPIC y más lento con él.


Como ya han señalado otras personas, -fPIC obliga a GCC a desactivar muchas optimizaciones, por ejemplo, en línea y clonación. Me gustaría señalar varias maneras de superar esto:

  • use -fvisibility=hidden y __attribute__((visibility("default"))) para exportar solo las funciones necesarias de la biblioteca y ocultar el resto; Esto permitiría a GCC optimizar funciones ocultas
  • use alias de símbolos privados ( __attribute__((alias ("__f"))); ) para referirse a las funciones de la biblioteca desde dentro de la biblioteca; Esto desataría de nuevo las manos de GCC.
  • la sugerencia anterior se puede automatizar con el -fno-semantic-interposition que se agregó en las versiones recientes de GCC

Resulta que cuando compilas sin la opción sqComplex , el isInSet isMandelbrot automáticamente multiplyComplex , sqComplex , isInSet y isMandelbrot . Si define esas funciones como estáticas, es probable que obtenga el mismo rendimiento al compilar con -fPIC porque el compilador será libre de realizar inline.

La razón por la que el compilador no puede integrar automáticamente las funciones de ayuda tiene que ver con la interposición de símbolos. Se requiere un código de posición independiente para acceder a todos los datos globales de manera indirecta, es decir, a través de la tabla de compensación global. La misma restricción se aplica a las llamadas de función, que tienen que pasar por la tabla de vinculación de procedimientos. Como un símbolo puede ser interpuesto por otro en tiempo de ejecución (ver LD_PRELOAD ), el compilador no puede simplemente asumir que es seguro integrar una función con visibilidad global.

Se puede hacer la misma suposición si compila sin -fPIC , es decir, el compilador puede asumir con seguridad que un símbolo global definido en el ejecutable no puede interponerse porque el alcance de búsqueda comienza con el ejecutable mismo, que es seguido por todas las demás bibliotecas, incluyendo Los precargados.

Para una comprensión más completa, eche un vistazo al siguiente paper .