optimización novtable


El compilador de Microsoft le permite agregar la extensión "novtable" para el atributo __declspec al declarar una clase.

El objetivo declarado es reducir significativamente el tamaño del código generado. En experimentos con nuestros componentes, la disminución fue de 0.6 a 1.2 por ciento del tamaño de la DLL.

Aplicabilidad: clases no destinadas a instanciar directamente de ellas.

Por ejemplo: puramente clases de interfaz.

En código, se ve así:

struct __declspec(novtable) IDrawable { virtual void Draw() const = 0; }; 

Nota: la palabra clave struct se utilizó para declarar una clase de interfaz para eliminar el ejemplo de detalles irrelevantes del artículo; mientras que en el caso de usar una clase, uno tendría que usar public para indicar la "publicidad" de los métodos. Por la misma razón, no agregaré un destructor virtual a la clase de interfaz en este artículo.

El nombre "novtable" promete que no habrá una tabla virtual ... Pero, ¿cómo funciona el mecanismo para invocar funciones virtuales en el siguiente código:

 //   ,   IDrawable: class Rectangle : public IDrawable { virtual void Draw() const override { } int width; int height; }; … IDrawable* drawable = new Rectangle; drawable->Draw(); //   Rectangle::Draw … 


Recuerde lo que se agrega al declarar una función virtual en una clase:

  1. Definición de una tabla de funciones virtuales. Una instancia de esta tabla se usa para todas las instancias de la clase.
  2. Se agrega un puntero a la tabla de funciones virtuales a los miembros de datos de la clase.
  3. El código para inicializar este puntero en el constructor de la clase.

Por lo tanto, en nuestro ejemplo, habrá una declaración de dos tablas de funciones virtuales: para IDrawable y para Rectangle. Al crear un objeto Rectangle, el constructor IDrawable es el primero en ejecutarse, lo que inicializa un puntero a su tabla de funciones virtuales. Esquemáticamente, se ve así:


Dado que la función de dibujo en IDrawable se declara puramente virtual (se indica "= 0" en lugar del cuerpo de la función), la dirección de la función de llamada pura generada por el compilador se escribe en la tabla de funciones virtuales.

Luego se ejecuta el constructor Rectangle, que inicializa el mismo puntero, pero a su tabla de funciones virtuales:



¿Qué hace "novtable" y por qué Microsoft promete reducir el tamaño del código?


Es la definición innecesaria de la tabla de funciones virtuales de IDrawable y la inicialización del puntero en el constructor de IDrawable que se excluyen del código resultante al agregar "novtable".

En este caso, al construir un IDrawable, el puntero a la tabla de funciones virtuales contendrá un valor impredecible. Pero esto no debería molestarnos, ya que crear una implementación con acceso a funciones virtuales antes de la construcción completa del objeto suele ser un error. Si, por ejemplo, se llama a una función no virtual de esta clase en el constructor de la clase base, que a su vez llama a una función virtual, entonces la función purecall se llamará sin novtable, y el comportamiento impredecible con novtable; Ninguna de las opciones puede ser aceptable.

Tenga en cuenta que no solo hay una disminución en el tamaño, sino también cierta aceleración del programa.

RTTI


Como sabe, std :: dynamic_cast le permite convertir punteros y enlaces de una instancia de una clase a un puntero y un enlace a otro, si estas clases están vinculadas jerárquicamente y son polimórficas (contienen una tabla de funciones virtuales). A su vez, el operador typeid le permite obtener información sobre un objeto en tiempo de ejecución utilizando el puntero (enlace) a ese objeto que se le pasó. Estas capacidades son proporcionadas por el mecanismo RTTI, que utiliza información de tipo ubicada con referencia a la tabla vtable de la clase. Los detalles de la estructura y la ubicación dependen del compilador. En el caso del compilador de Microsoft, se ve así:



Por lo tanto, si durante la compilación se ordena al compilador que habilite RTTI, novtable también excluye la creación de una definición type_info para IDrawable y los datos de servicio necesarios para ello.
Tenga en cuenta que si de alguna manera proporciona el conocimiento de que un puntero referenciado (enlace) a una clase base indica una implementación de una derivada, entonces std :: static_cast es más eficiente y no requiere RTTI.

Microsoft específico


Además de MSVC, esta característica con la misma sintaxis está presente en Clang cuando se compila en Windows.

Conclusiones


  1. __declspec (novtable): no afecta la cantidad de memoria ocupada por las instancias de clase.
  2. La reducción del tamaño y la aceleración del programa se asegura eliminando la definición de tablas de funciones virtuales no utilizadas, la sobrecarga de RTTI y eliminando el código de inicialización para el puntero a la tabla de funciones virtuales en los constructores de clase de interfaz.

Source: https://habr.com/ru/post/442340/


All Articles