2010-03-12 29 views
6

estoy leyendo algo de código C así:Struct en C, ¿son eficientes?

double function(int lena,double xa,double ya, double za, double *acoefs, ..., 
       int lenb,double xb,double yb, double zb, double *bcoefs, ..., 
       same for c, 
       same for d) 

Esta función se llama en el código mor de 100.000 veces por lo que es el rendimiento crítico.

Estoy tratando de extender este código, pero quiero saber si es eficiente o no (y lo mucho que esto influye en la velocidad) para encapsular todos los parámetros en una estructura como esta

struct PGTO { int len; double x,y,z ; double *acoefs } 

y luego accede a los parámetros en la función.

+11

manera fácil de averiguar: perfil y prueba – cobbal

+1

El código compilado debe ser el mismo para la convención de llamada estándar x86 y ARM. – kennytm

+0

¿El '...' indica varargs o omitió algunos parámetros? – bk1e

Respuesta

5

Visual C++ 2008 64 bit parece pasar la estructura haciendo que el llamante asigne espacio para una copia de la estructura en su pila y copie los elementos de datos en ella y pase la dirección de esa copia a la función por valor .

Este sencillo ejemplo compilado de la siguiente manera -

struct data { 
    int a; 
    int b; 
    int c; 
    char d; 
    float f; 
}; 




double f2(data d) 
{ 
    return d.a+d.b+d.c+d.d+d.f; 
} 

recopile con este -

movsx eax, BYTE PTR [rcx+12] 
add eax, DWORD PTR [rcx+8] 
add eax, DWORD PTR [rcx+4] 
add eax, DWORD PTR [rcx] 
movd xmm0, eax 
cvtdq2ps xmm0, xmm0 
addss xmm0, DWORD PTR [rcx+16] 
unpcklps xmm0, xmm0 
cvtps2pd xmm0, xmm0 
ret 0 

Así que, básicamente, cuando se pasa los elementos individuales de la persona que llama les inserta en la pila y la función de los accesos en relación con la pila. Cuando pasa un strcuture, la persona que llama copia los elementos de datos en un bloque de memoria en la pila y luego pasa la dirección de eso a la función, que accede a ellos en relación con eso.

La segunda forma tiene un par más de instrucciones de lenguaje de ensamblaje pero eso, por lo que puedo ver en mis pruebas, no hay una diferencia significativa en la eficiencia entre los dos métodos a excepción de algunos microsegundos. En todas mis pruebas, el compilador quería alinear todas las llamadas de todos modos a menos que lo forzara a llamarlo a través de un puntero, por lo que es posible que no pase nada en la pila en absoluto en el programa real.

En mi opinión el uso de una estructura es clara y no parece haber diferencias significativas en la velocidad así que a por que uno :)

Como siempre, en caso de duda sobre el rendimiento que necesita para el perfil de su configuración exacta

5

La respuesta a esta pregunta depende en gran medida de la plataforma y sus convenciones de llamadas. También depende de si la función podría estar en línea y otras optimizaciones del compilador.

Diría que, en muchas plataformas, la versión de estructura podría ser más eficiente, ya que hay menos parámetros para copiar en la pila si está pasando un puntero a la estructura.

De todos modos, la pregunta no puede responderse fácilmente. Entonces, como con todas las consideraciones de rendimiento: ¡solo prueba y perfil!

+1

Si pasa un puntero a la estructura, está pasando por referencia, no por valor. –

0

Es probable que los compiladores modernos produzcan un código idéntico en ambos casos. Entonces, a menos que su compilador no sea lo último en tecnología, el único (pero importante) beneficio que obtendrá con las estructuras es una mejor legibilidad.

+2

No, no puede. Las convenciones de llamadas requieren que los argumentos se pasen en la pila. El compilador no puede doblar las convenciones de llamada a menos que la función se declare 'estática'. Ese no suele ser el caso. –

+0

Tienes razón, me olvidé de eso. –

+0

@¿Cómo es esa diferencia entre estructuras y parámetros individuales? En ambos casos, los parámetros se copiarán en la pila. –

0

Lo mejor es hacer un perfil y probar.

Pero, una cosa, los parámetros menores se copiarán en la pila cuando pase un puntero a struct. Además, su llamada a la función puede parecer más ordenada.

4

En primer lugar, el uso de estructuras parece ser un camino correcto en este caso exacto. El código será mucho más fácil de leer y escuchar, también se volverá menos desordenado.

Pasar un puntero a una estructura es generalmente más barato que pasar muchos parámetros.

+3

No si necesita completar la estructura cada vez que llama a la función. – lhf

+0

Claro, pero, una vez más, depende del código de llamada y del resto de la fuente :) Hay un montón de factores a considerar. –

0

Sí, lo son. Apostaría un millón de dólares, si tuviera tanto, que la diferencia de rendimiento es insignificante.

0

Pasar una estructura o la lista de argumentos no debe marcar una gran diferencia. La convención de llamadas requiere que ambas se pasen por valor y que se pasen a través de la pila (a menos que la función se pueda declarar static).

Puede refactorizar el código para habilitar el paso por referencia (pasando un puntero a una estructura). Pero probablemente sea un rediseño importante, probablemente haga que su código sea más complicado y menos legible.

+0

¿La "convención de llamadas" significa todas las convenciones de llamadas? ¿Todas las convenciones de llamadas que has visto? La convención de llamadas 'cdecl'? –

1

Supongo que depende de la frecuencia con la que se debe completar la estructura.

Pasar un solo puntero a una estructura (4 bytes) tomará menos instrucciones que pasar un int, 3 dobles y un puntero (32 bytes), pero si debe completar la estructura con esos 32 bytes antes de hacer el llamada, entonces has perdido la ventaja.

En cualquier caso, el rendimiento siempre es relativo, por lo que la única manera de saber si vale la pena es ver qué porcentaje de tiempo se pasa al pasar esos argumentos. Para hacer eso, simplemente lo pongo en funcionamiento y lo paro aleatoriamente 10 o 20 veces. A menos que lo atrape más del 10% del tiempo pasando esos argumentos, es probable que haya problemas más grandes que podrían corregirse primero.