programa - sintaxis de c++
¿Los compiladores de C++ optimizan el paso por const de los parámetros POD para pasar al copiar? (3)
Hay 2 temas.
En primer lugar, el compilador no convertirá paso por ref a paso por valor, especialmente si complexComputation
no es static
(es decir, puede ser usado por objetos externos).
La razón es la compatibilidad API. Para la CPU, no existe tal cosa como una "referencia". El compilador convertirá referencias a punteros. Los parámetros se pasan en la pila o a través del registro, por lo que un código que llame a complexComputation
probablemente se llamará como (se supone que double
es de longitud 4 por un momento):
str x1, [r7, #0x20]
str y1, [r7, #0x24]
str x2, [r7, #0x50]
str y2, [r7, #0x54]
push r7, #0x20 ; push address of p1 onto the stack
push r7, #0x50 ; push address of p2 onto the stack
call complexComputation
Sólo 8 bytes se insertan en la pila.
Pasar por copia, por otro lado, empujará toda la estructura en la pila, por lo que el código de ensamblaje se verá como
push x1 ; push a copy of p1.x onto the stack
push y1 ; push a copy of p1.y onto the stack
push x2 ; push a copy of p2.x onto the stack
push y2 ; push a copy of p2.y onto the stack
call complexComputation
Tenga en cuenta que esta vez se insertan 16 bytes en la pila, y el contenido son los números, no los punteros. Si complexComputation
cambia su parámetro pasando la semántica, la entrada se convertirá en basura y su programa puede fallar.
Por otro lado, la optimización.
double complexComputation(const Point& p1, const Point& p2) {
double x1 = p1.x; double x2 = p2.x;
double y1 = p1.y; double y2 = p2.y;
// x1, x2, y1, y2 stored in registers and used frequently in computations
}
se puede hacer fácilmente, ya que el compilador puede reconocer qué variables se usan con mucha frecuencia y almacenarlas en registros reservados (por ejemplo, r4 ~ r13 en la arquitectura ARM, y muchos de los registros sXX / dXX) para un acceso más rápido.
Después de todo, si desea saber si un compilador ha hecho algo, siempre puede desmontar los objetos resultantes y comparar.
Considera lo siguiente:
struct Point {double x; double y;};
double complexComputation(const& Point p1, const Point& p2)
{
// p1 and p2 used frequently in computations
}
¿Los compiladores optimizan el paso por referencia en el pase por copia para evitar la referencia frecuente? En otras palabras, convierta complexComputation
en esto:
double complexComputation(const& Point p1, const Point& p2)
{
double x1 = p1.x; double x2 = p2.x;
double y1 = p1.y; double y2 = p2.y;
// x1, x2, y1, y2 stored in registers and used frequently in computations
}
Dado que Point es un POD, no puede haber ningún efecto secundario al hacer una copia detrás de la parte posterior de la persona que llama, ¿verdad?
Si ese es el caso, entonces siempre puedo pasar los objetos POD por referencia constante, sin importar cuán pequeños sean, y no tengo que preocuparme por la semántica de paso óptima. ¿Derecha?
EDITAR: Estoy interesado en el compilador GCC en particular. Supongo que podría tener que escribir un código de prueba y mirar el ASM.
Su compilador puede elevar absolutamente las variables de los miembros del Punto en registros si es necesario. Sin embargo, esto no es lo mismo que el compilador que convierte la llamada de función en pasar por valor.
Debe inspeccionar el ensamblaje generado para ver qué optimizaciones se están realizando.
Y FWIW, la regla general que utilizo es pasar todos los tipos primativos por valor y todas las clases / UDT (POD o no) por referencia constante cuando pueda, y dejar que el compilador ordene lo mejor que puede hacer. No debemos preocuparnos por los detalles de lo que está haciendo el compilador, es mucho más inteligente que nosotros.