C ++: ¿Por qué pasar-por-valor es generalmente más eficiente que pasar-por-referencia para tipos incorporados (es decir, tipo C)

como lo que se indica en el título

Un proveedor de comstackdores normalmente implementaría una referencia como puntero. Los punteros tienden a ser del mismo tamaño o más grandes que muchos de los tipos incorporados. Para estos tipos incorporados, se pasaría la misma cantidad de datos ya sea que pasara por valor o por referencia. En la función, para obtener los datos reales, sin embargo, necesitaría desreferenciar este puntero interno. Esto puede agregar una instrucción al código generado, y también tendrá dos ubicaciones de memoria que pueden no estar en la memoria caché. La diferencia no será mucha, pero podría medirse en bucles ajustados.

Un proveedor de comstackdores puede optar por ignorar las referencias const (ya veces también las referencias no const) cuando se utilizan en tipos incorporados, todo dependiendo de la información disponible para el comstackdor cuando se trata de la función y sus llamadores.

Para los tipos de pod como int, char, short y float, el tamaño de los datos es del mismo tamaño (o menor) que la dirección pasada para hacer referencia a los datos reales. Buscar el valor en la dirección referenciada es un paso innecesario y agrega un costo adicional.

Por ejemplo, tome las siguientes funciones foo y bar

 void foo(char& c) {...} void bar(char c) {...} 

Cuando se llama a foo pasa una dirección por valor de 32bits o 64bits, dependiendo de su plataforma. Cuando utiliza c dentro de foo tiene el costo de buscar el valor de los datos que se encuentran en la dirección pasada.

Al llamar a la bar se transfiere un valor del tamaño de char y no hay una sobrecarga de búsqueda de dirección.

En la práctica, las implementaciones de C ++ generalmente implementan pass-by-reference pasando un puntero bajo el capó (asumiendo que la llamada no está en línea).

Por lo tanto, no existe un mecanismo inteligente que permita que la referencia pasajera sea más rápida, ya que no es más rápido pasar un puntero que pasar un valor pequeño. Y pasar por valor también puede beneficiarse de una mejor optimización una vez que esté en la función. Por ejemplo:

 int foo(const int &a, int *b) { int c = a; *b = 2; return c + a; } 

Para todo lo que sabe el comstackdor, b apunta a a , que se llama “aliasing”. Si se hubiera pasado por valor, esta función podría optimizarse al equivalente de *b = 2; return 2*a; *b = 2; return 2*a; . En la línea de instrucciones de una CPU moderna, esto podría ser más como “iniciar una carga, iniciar b almacenar, esperar a que se cargue, multiplicar por 2, esperar a que b almacene, devolver”, en lugar de “iniciar una carga, iniciar el almacenamiento b , espere a que se cargue, espere a que b se guarde, inicie una carga, espere a que se cargue, agregue aac, devuelva “, y comenzará a ver por qué el potencial de aliasing puede tener un efecto significativo en el rendimiento. En algunos casos, si no necesariamente un gran efecto en este caso.

Por supuesto, el aliasing solo impide la optimización en los casos en los que cambia el efecto de la función para alguna entrada posible. Pero solo porque su intención para la función es que el aliasing no afecte los resultados, no necesariamente significa que el comstackdor puede suponer que no: a veces, en su progtwig, no se produce un aliasing, pero el comstackdor no lo hace no lo sé Y no tiene que haber un segundo parámetro de puntero, cada vez que su función llame al código que el optimizador “no puede ver”, debe suponer que cualquier referencia podría cambiar.