Función en línea v. Macro en C-¿Qué es la altura(memoria/velocidad)?
performance optimization (9)
La mejor manera de responder a su pregunta es comparar ambos enfoques para ver cuál es realmente más rápido en su aplicación, usando sus datos de prueba. Las predicciones sobre el rendimiento son notoriamente poco confiables, excepto en los niveles más groseros.
Dicho esto, espero que no haya una diferencia significativa entre una macro y una llamada de función realmente en línea. En ambos casos, debe terminar con el mismo código de ensamblaje debajo del capó.
Busqué en Home los pros / contras de funciones en línea macros v. Funciones en línea.
Encontré la siguiente discusión: Pros y contras de diferentes funciones macro / métodos en línea en C
... pero no respondió mi principal pregunta candente.
A saber, ¿cuál es la sobrecarga en c de usar una función macro (con variables, posiblemente otras llamadas a función) v. Una función en línea, en términos de uso de memoria y velocidad de ejecución?
¿Hay alguna diferencia dependiente del compilador en la sobrecarga? Tengo tanto icc como gcc a mi disposición.
Mi fragmento de código que estoy modularizando es:
double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3);
double RepulsiveTerm = AttractiveTerm * AttractiveTerm;
EnergyContribution +=
4 * Epsilon * (RepulsiveTerm - AttractiveTerm);
Mi razón para convertirlo en una macro / función en línea es para poder colocarlo en un archivo ca y luego compilar de forma condicional otras funciones / macros similares pero ligeramente diferentes.
p.ej:
double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3);
double RepulsiveTerm = pow(SigmaSquared/RadialDistanceSquared,9);
EnergyContribution +=
4 * Epsilon * (RepulsiveTerm - AttractiveTerm);
(tenga en cuenta la diferencia en la segunda línea ...)
Esta función es central para mi código y se llama miles de veces por paso en mi programa y mi programa realiza millones de pasos. Por lo tanto, quiero tener la mínima sobrecarga posible, por lo tanto, estoy perdiendo el tiempo preocupándome por la sobrecarga de inline v. Transformando el código en una macro.
En base a la discusión anterior, ya me he dado cuenta de otros pros / contras (tipo de independencia y los errores resultantes de eso) de las macros ... pero lo que quiero saber más, y actualmente no sé, es el RENDIMIENTO.
¡Sé que algunos de ustedes, veteranos de C, tendrán una gran idea para mí!
Las macros, incluidas las macros similares a funciones, son simples sustituciones de texto, y como tal pueden morderte el culo si no eres realmente cuidadoso con tus parámetros. Por ejemplo, la popular macro SQUARE:
#define SQUARE(x) ((x)*(x))
puede ser un desastre esperando que ocurra si lo llamas como SQUARE(i++)
. Además, las macros de función no tienen ningún concepto de alcance y no admiten variables locales; el truco más popular es algo así como
#define MACRO(S,R,E,C) /
do /
{ /
double AttractiveTerm = pow((S)/(R),3); /
double RepulsiveTerm = AttractiveTerm * AttractiveTerm; /
(C) = 4 * (E) * (RepulsiveTerm - AttractiveTerm); /
} while(0)
lo que, por supuesto, hace que sea difícil asignar un resultado como x = MACRO(a,b);
.
La mejor apuesta desde el punto de vista de la exactitud y la mantenibilidad es hacer que sea una función y especificar en inline
. Las macros no son funciones, y no deben confundirse con ellas.
Una vez que hayas hecho eso, mide el rendimiento y encuentra dónde está el cuello de botella real antes de hackearlo (la llamada a pow
ciertamente sería un candidato para racionalizar).
Llamar a una función en línea puede o no generar una llamada a función, que típicamente incurre en una cantidad muy pequeña de sobrecarga. Las situaciones exactas bajo las cuales una función en inline
entra en línea varían dependiendo del compilador; la mayoría hace un esfuerzo de buena fe para alinear las funciones pequeñas (al menos cuando la optimización está habilitada), pero no es necesario que lo hagan (C99, §6.7.4):
Hacer que una función sea una función en línea sugiere que las llamadas a la función sean lo más rápidas posible. El grado en que tales sugerencias son efectivas está definido por la implementación.
Es menos probable que una macro incurra en tal sobrecarga (aunque de nuevo, hay poco para evitar que un compilador haga algo de alguna manera, el estándar no define a qué programas de código de máquina deben expandirse, solo el comportamiento observable de un programa compilado).
Usa lo que sea más limpio. Perfil. Si es importante, haz algo diferente.
Además, qué dijo Fizzer ; las llamadas a pow (y división) suelen ser más costosas que la sobrecarga de llamada de función. Minimizarlos es un buen comienzo:
double ratio = SigmaSquared/RadialDistanceSquared;
double AttractiveTerm = ratio*ratio*ratio;
EnergyContribution += 4 * Epsilon * AttractiveTerm * (AttractiveTerm - 1.0);
¿ EnergyContribution
está formado solo por términos que se parecen a esto? Si es así, saca el 4 * Epsilon
y guarda dos multiplicaciones por iteración:
double ratio = SigmaSquared/RadialDistanceSquared;
double AttractiveTerm = ratio*ratio*ratio;
EnergyContribution += AttractiveTerm * (AttractiveTerm - 1.0);
// later, once you''ve done all of those terms...
EnergyContribution *= 4 * Epsilon;
Revise el estándar de codificación CERT Secure hablando de macros y funciones en línea en términos de seguridad y generación de errores. No recomiendo usar macros funcionales porque: - Menos perfiles - Menos rastreable - Más difícil de depurar - Podría provocar errores graves
Si hace random-pause , lo que probablemente verá es que el 100% (menos épsilon) del tiempo está dentro de la función pow
, así que cómo llegó allí básicamente no hace diferencia.
Suponiendo que lo encuentres, lo primero que debes hacer es deshacerte de las llamadas a pow
que encuentres en la pila. (En general, lo que hace es tomar el log
del primer argumento, multiplicarlo por el segundo argumento, y exp
de eso, o algo que haga lo mismo. El log
y exp
bien podrían hacerse por algún tipo de serie que involucre mucha aritmética. Busca casos especiales, por supuesto, pero aún tomará más tiempo de lo que lo haría.) Solo eso debería darle una aceleración de orden de magnitud.
Luego haga la pausa aleatoria de nuevo. Ahora vas a ver algo más que te llevará mucho tiempo. No puedo adivinar qué será, ni tampoco nadie más, pero probablemente puedas reducir eso también. Solo sigue haciéndolo hasta que no puedas más.
Puede suceder en el camino que elija utilizar una macro, y puede ser un poco más rápido que una función en línea. Eso es para que juzgues cuando llegues allí.
Son las llamadas a pow () que desea eliminar. Esta función toma exponentes generales de coma flotante y es ineficaz para elevar a exponentes integrales. Reemplazar estas llamadas con, por ej.
inline double cube(double x)
{
return x * x * x;
}
es lo único que hará una diferencia significativa en su desempeño aquí.
Tal como lo entiendo de algunos tipos que escriben compiladores, una vez que llamas a una función desde dentro, no es muy probable que tu código se inserte de todos modos. Pero, es por eso que no deberías usar una macro. Las macros eliminan la información y dejan al compilador con muchas menos opciones para optimizar. Con los compiladores de múltiples pasos y las optimizaciones de todo el programa, sabrán que al crear su código se producirá una predicción de bifurcación fallida o una falta de caché u otras fuerzas de magia negra que las CPU modernas utilizan para avanzar rápidamente. Creo que todos tienen razón al señalar que el código anterior no es óptimo de todos modos, por lo que es donde debería estar el enfoque.
Una macro no es realmente una función. lo que usted defina como una macro se publica literalmente en su código, antes de que el compilador lo vea, por el preprocesador. El preprocesador es solo una herramienta de ingenieros de software que permite varias abstracciones para estructurar mejor su código.
Una función en línea o de otra manera que el compilador conoce y puede tomar decisiones sobre qué hacer con ella. Una palabra clave en inline
suministrada por el usuario es solo una sugerencia y el compilador puede ignorarla. Es este exceso que en la mayoría de los casos daría como resultado un mejor código.
Otro efecto secundario de que el compilador sea consciente de las funciones es que podría forzar al compilador a tomar ciertas decisiones, por ejemplo, deshabilitar la alineación de su código, lo que podría permitirle depurar mejor o crear un perfil de su código. Probablemente haya muchos otros casos de uso que las funciones en línea habilitan en comparación con las macros.
Sin embargo, las macros son extremadamente poderosas, y para respaldar esto, citaría google test y google mock. Hay muchas razones para usar macros: D.
Las operaciones matemáticas simples que están encadenadas entre sí mediante el uso de funciones a menudo son invocadas por el compilador, especialmente si la función solo se llama una vez en el paso de traducción. Por lo tanto, no me sorprendería que el compilador tome decisiones en línea para usted, independientemente del clima en que se suministre la palabra clave o no.
Sin embargo, si el compilador no puede, puede anular manualmente los segmentos de su código. Si lo aplana, quizás las macros sirvan como una buena abstracción, después de todo, presentan una semántica similar a una función "real".
Lo escencial
Entonces, ¿desea que el compilador tenga conocimiento de ciertos límites lógicos para que pueda producir un mejor código físico, o si desea tomar decisiones de fuerza en el compilador aplanándolo manualmente o mediante el uso de macros. La industria se inclina hacia la primera.
Me inclinaría hacia el uso de macros en este caso, simplemente porque es rápido y sucio, sin tener que aprender mucho más. Sin embargo, como las macros son una abstracción de ingeniería de software, y debido a que le preocupa el código que genera el compilador, si el problema fuera un poco más avanzado, usaría las plantillas de C ++, ya que fueron diseñadas para las inquietudes que considera.
como otros han dicho, depende principalmente del compilador.
Apuesto a que "pow" te cuesta más que cualquier inline o macro te salvará :)
Creo que es más limpio si es una función en línea en lugar de una macro.
el almacenamiento en caché y la canalización son realmente donde obtendrá buenas ganancias si está ejecutando esto en un procesador moderno. es decir. eliminar declaraciones de ramificación como ''si'' hacen enormes diferencias (se puede hacer mediante una serie de trucos)