c++ - entre - estructuras anidadas lenguaje c
¿Varios niveles de clases base ralentizan una clase/estructura en c++? (12)
¿Tener varios niveles de clases base ralentiza una clase? A deriva B deriva C deriva D deriva F deriva G, ...
¿La herencia múltiple ralentiza una clase?
Aunque no estoy del todo seguro, creo que a menos que esté usando métodos virtuales, el compilador debería poder optimizarlo lo suficientemente bien como para que la herencia no importe demasiado.
Sin embargo, si está llamando a funciones en la clase base que llaman funciones en su clase base, etc., puede afectar el rendimiento.
En esencia, depende en gran medida de cómo haya estructurado su árbol de herencia.
Casi todas las respuestas apuntan a si los métodos virtuales serían o no más lentos en el ejemplo del OP, pero creo que el OP simplemente pregunta si tener un nivel de herencia en sí mismo es lento. La respuesta es, por supuesto, no, ya que todo esto sucede en tiempo de compilación en C ++. Sospecho que la pregunta se deriva de la experiencia con los lenguajes de guiones donde dichos gráficos de herencia pueden ser dinámicos, y en ese caso, potencialmente podría ser más lento.
Las llamadas a funciones no virtuales no tienen absolutamente ningún rendimiento en el tiempo de ejecución, de acuerdo con el mantra c ++ que no debe pagar por lo que no usa. En una llamada a función virtual, generalmente paga por una búsqueda de puntero adicional, independientemente de cuántos niveles de herencia o número de clases base tenga. Por supuesto, esto es toda la implementación definida.
Editar: Como se señaló en otra parte, en algunos escenarios de herencia múltiple, se requiere un ajuste al puntero ''this'' antes de realizar la llamada. Raymond Chen describe cómo funciona esto para los objetos COM. Básicamente, llamar a una función virtual en un objeto que hereda desde múltiples bases puede requerir una sustracción adicional y una instrucción jmp además de la búsqueda adicional del puntero requerida para una llamada virtual.
Las llamadas virtuales en sí mismas consumen más tiempo que las llamadas normales porque tienen que buscar la dirección de la función real para llamar desde el vtable
Además, las optimizaciones del compilador como la alineación pueden ser difíciles de realizar debido al requisito de búsqueda. Las situaciones en las que no es posible realizar la línea interna pueden generar una sobrecarga considerable debido a las operaciones de apilar y saltar
Aquí hay un estudio adecuado que dice que la sobrecarga puede ser tan alta como 50% http://www.cs.ucsb.edu/~urs/oocsb/papers/oopsla96.pdf
Aquí hay otro recurso que analiza un efecto secundario de tener una gran biblioteca de clases virtuales http://keycorner.org/pub/text/doc/kde-slow.txt
El envío de llamadas virtuales con múltiples herencias es específico del compilador, por lo que la implementación también tendrá un efecto en este caso.
Con respecto a su pregunta específica de tener un gran número de clases base, generalmente el diseño de memoria de un objeto de clase tendría los ptr vtbl para todas las otras clases constituyentes dentro de él.
Consulte esta página para ver un diseño de muestra vtable - http://www.codesourcery.com/public/cxx-abi/cxx-vtable-ex.html
Por lo tanto, una llamada a un método implementado por una clase más profunda en la jerarquía todavía sería solo una sola indirección y no varias indirecciones, como parece pensar. La llamada no tiene que navegar de clase a clase para finalmente encontrar la función exacta para llamar.
Sin embargo, si está utilizando composición en lugar de herencia, cada llamada de puntero sería una llamada virtual y esa sobrecarga estaría presente y si dentro de esa llamada virtual si esa clase usa más composición, se realizarían más llamadas virtuales. Ese tipo de diseño sería más lento dependiendo de la cantidad de llamadas que hizo.
Llamar a una función virtual es un poco más lento que llamar a una función no virtual. Sin embargo, no creo que importe cuán profundo es el árbol de tu herencia.
Pero esta no es la diferencia de la que normalmente debería preocuparse.
No hay diferencia de velocidad entre las llamadas virtuales en diferentes niveles, ya que todas se aplanan en el vtable (apuntando a las versiones más derivadas de los métodos reemplazados). Entonces, llamar ((A *) inst) -> Método () cuando inst es una instancia de B es la misma sobrecarga que cuando inst es una instancia de D.
Ahora, una llamada virtual es más costosa que una llamada no virtual, pero esto se debe a la desreferencia del puntero y no a la función de qué tan profunda es realmente la jerarquía de clase.
Sí, si lo referencia de esta manera:
// F is-a E,
// E is-a D and so on
A* aObject = new F();
aObject->CallAVirtual();
Entonces estás trabajando con un puntero a un objeto de tipo A. Dado que está llamando a una función que es virtual, tiene que buscar la tabla de funciones (vtable) para obtener los punteros correctos. Hay algunos gastos generales para eso, sí.
Si no hay funciones virtuales, entonces no debería. Si hay, entonces, hay un impacto en el rendimiento al llamar a las funciones virtuales, ya que se llaman a través de punteros de función u otros métodos indirectos (depende de la situación). Sin embargo, no creo que el impacto esté relacionado con la profundidad de la jerarquía de herencia.
Brian, para ser claro y responder a tu comentario. Si no hay funciones virtuales en ningún lugar en su árbol de herencia, entonces no hay impacto en el rendimiento.
Tener constructores no triviales en un árbol de herencia profunda puede ralentizar la creación de objetos, cuando cada creación de un niño genera llamadas de función a todos los constructores padres hasta la base.
[Las jerarquías de herencia profunda] aumentan en gran medida la carga de mantenimiento añadiendo complejidad innecesaria, lo que obliga a los usuarios a aprender las interfaces de muchas clases, incluso cuando lo único que quieren hacer es utilizar una clase derivada específica. También puede tener un impacto en el uso de la memoria y el rendimiento del programa agregando tablas virtuales e indirectas innecesarias a las clases que realmente no las necesitan. Si se encuentra creando con frecuencia jerarquías de herencia profunda, debe revisar su estilo de diseño para ver si ha recogido este mal hábito. Las jerarquías profundas rara vez se necesitan y casi nunca son buenas. Y si no cree en eso, pero piensa que "OO simplemente no es OO sin mucha herencia", entonces un buen contraejemplo a considerar es la biblioteca estándar [C ++] en sí misma. - Herb Sutter
- ¿La herencia múltiple ralentiza una clase?
Como se mencionó varias veces, una jerarquía de herencia única profundamente anidada no debería imponer ninguna sobrecarga adicional para una llamada virtual (por encima de la sobrecarga impuesta para cualquier llamada virtual).
Sin embargo, cuando se trata de herencia múltiple, a veces hay una sobrecarga adicional muy pequeña cuando se llama a la función virtual a través de un puntero de clase base. En este caso, algunas implementaciones hacen que la función virtual pase por un pequeño procesador que ajusta el puntero ''this'' desde
(static_cast<Base*>( this) == this)
No es necesariamente cierto dependiendo del diseño del objeto.
Tenga en cuenta que todo esto depende mucho de la implementación.
Consulte "Modelo de objetos dentro de C ++" de Lippman, capítulo 4.2 - Funciones de miembros virtuales / Funciones virtuales en MI
Como señaló Corey Ross, el vtable se conoce en tiempo de compilación para cualquier clase derivada de hoja, por lo que el costo de la llamada virtual realmente debería ser el mismo independientemente de la estructura de la jerarquía.
Esto, sin embargo, no puede decirse para dynamic_cast
. Si considera cómo podría implementar dynamic_cast
, ¡un enfoque básico tendrá una búsqueda O (n) a través de su jerarquía!
En el caso de una jerarquía de herencia múltiple, también está pagando un pequeño costo para convertir entre las diferentes clases en la jerarquía:
sturct A { int i; };
struct B { int j; };
struct C : public A, public B { int k ; };
// Let''s assume that the layout of C is: { [ int i ] [ int j ] [int k ] }
void foo (C * c) {
A * a = c; // Probably has zero cost
B * b = c; // Compiler needed to add sizeof(A) to ''c''
c = static_cast<B*> (b); // Compiler needed to take sizeof(A)'' from ''b''
}