trucos optimizar dev como atajos c++ pointers optimization portability

dev - como optimizar en c++



¿Qué tan portátil es usar el bit bajo de un puntero como indicador? (4)

Si hay, por ejemplo, una clase que requiere un puntero y un bool . Para simplificar, se utilizará un puntero int en los ejemplos, pero el tipo de puntero es irrelevante siempre que apunte a algo cuyo size() sea ​​mayor que 1.

La definición de la clase con { bool , int *} miembros de datos dará como resultado que la clase tenga un tamaño que sea el doble del tamaño del puntero y una gran cantidad de espacio desperdiciado

Si el puntero no apunta a un char (u otros datos de size(1) ), probablemente el bit bajo siempre será cero. La clase podría definirse con {int *} o por conveniencia: union { int *, uintptr_t }

El bool se implementa estableciendo / borrando el bit bajo del puntero según el valor del bool lógico y borrando el bit cuando necesita usar el puntero.

La forma definida:

struct myData { int * ptr; bool flag; }; myData x; // initialize x.ptr = new int; x.flag = false; // set flag true x.flag = true; // set flag false x.flag = false; // use ptr *(x.ptr)=7; // change ptr x = y; // y is another int *

Y la forma propuesta:

union tiny { int * ptr; uintptr_t flag; }; tiny x; // initialize x.ptr = new int; // set flag true x.flag |= 1; // set flag false x.flag &= ~1; // use ptr tiny clean=x; // note that clean will likely be optimized out clean.flag &= ~1; // back to original value as assigned to ptr *(clean.ptr)=7; // change ptr bool flag=x.flag; x.ptr = y; // y is another int * x.flag |= flag;

Esto parece ser un comportamiento indefinido, pero ¿qué tan portátil es esto?


Cumplir con esas reglas y debería ser muy portátil.


En "teoría": es un comportamiento indefinido por lo que sé.

En "realidad": ¿funcionará en las máquinas x86 / x64 de todos los días, y probablemente ARM también?
Realmente no puedo hacer una declaración más allá de eso.


Es muy portátil y, además, puede assert cuando acepta el puntero en bruto para asegurarse de que cumple con el requisito de alineación. Esto asegurará contra el compilador futuro insondable que de alguna manera te desordena.

Las únicas razones para no hacerlo son el costo de legibilidad y el mantenimiento general asociado con cosas "piratas" como esa. Me rehuiría a hacerlo a menos que haya una ganancia clara por hacer. Pero a veces vale totalmente la pena.


Siempre que restaure el bit de orden inferior del puntero antes de intentar usarlo como un puntero, es probable que sea "razonablemente" portátil, siempre que su sistema, su implementación de C ++ y su código cumplan ciertas suposiciones.

No puedo necesariamente darte una lista completa de suposiciones, pero fuera de mi cabeza:

  • No estás apuntando a nada cuyo tamaño sea de 1 byte. Esto excluye char , unsigned char , signed char , int8_t , y uint8_t . (Y eso supone que CHAR_BIT == 8 ; en sistemas exóticos con, por ejemplo, bytes de 16 bits o de 32 bits, se podrían excluir otros tipos).
  • Los objetos cuyo tamaño es de al menos 2 bytes siempre se alinean en una dirección par. Tenga en cuenta que x86 no requiere esto; puede acceder a un int 4 bytes en una dirección impar, pero será un poco más lento. Pero los compiladores generalmente organizan que los objetos se almacenen en direcciones pares. Otras arquitecturas pueden tener diferentes requisitos.
  • Un puntero a una dirección par tiene su bit de orden inferior establecido en 0.

Para el último supuesto, en realidad tengo un contraejemplo concreto. En los sistemas vectoriales Cray (J90, T90 y SV1 son los que he usado) una dirección de máquina apunta a una palabra de 64 bits, pero el compilador de C en los conjuntos de CHAR_BIT == 8 . Los punteros de bytes se implementan en el software, con el desplazamiento de bytes de 3 bits dentro de una palabra almacenada en los 3 bits de alto orden de lo contrario no utilizados del puntero de 64 bits. Por lo tanto, un puntero a un objeto alineado de 8 bytes podría tener fácilmente su bit de orden inferior establecido en 1.

Ha habido implementaciones de Lisp ( example que utiliza los 2 bits de punteros de orden inferior para almacenar una etiqueta de tipo. Recuerdo vagamente que esto causa problemas graves durante la transferencia).

En pocas palabras: probablemente pueda salirse con la suya para la mayoría de los sistemas. Las arquitecturas futuras son en gran medida impredecibles, y puedo imaginar fácilmente que su esquema se rompe en la próxima Gran Nueva Cosa.

Algunas cosas a considerar:

¿Puede almacenar los valores booleanos en un vector de bits fuera de su clase? (Mantener la asociación entre el puntero y el bit correspondiente en el vector de bits se deja como ejercicio).

Considere agregar código a todas las operaciones de puntero que fallan con un mensaje de error si alguna vez ve un puntero con su bit de orden inferior establecido en 1. Use #ifdef para eliminar el código de verificación en su versión de producción. Si comienza a tener problemas en alguna plataforma, cree una versión de su código con las comprobaciones habilitadas y vea qué sucede.

Sospecho que, a medida que su aplicación crece (rara vez se reducen), querrá almacenar más que solo un bool junto con su puntero. Si eso sucede, el problema del espacio desaparece, porque de todos modos ya está utilizando ese espacio adicional.