2010-08-19 12 views
6

Estoy trabajando en un grupo de memoria para un motor de juegos pequeños.¿Qué considerar con respecto a la alineación cuando se diseña un grupo de memoria?

El uso principal será como un almacenamiento segregado; un grupo contiene un objeto de un tipo y tamaño específico. Actualmente, las agrupaciones se pueden usar para almacenar cualquier cosa, pero las asignaciones se realizarán en bloques de un tamaño específico. La mayor parte de la necesidad de memoria se asignará a la vez, pero se puede habilitar el "crecimiento excesivo" si es necesario para ayudar en la sintonización (tamaño casi fijo).

El problema es que comencé a sentirme un tanto paranoico al contemplar la alineación de la memoria. Solo estoy acostumbrado a la administración de memoria sin procesar en procesadores de 8 bits donde todo está alineado por bytes.

Voy a dejar que el usuario (me) especifique el tamaño deseado de los bloques, que en el caso de almacenamiento segregados sería el tamaño de los objetos que voy a almacenar en él.

El enfoque actual es asignar un trozo de memoria blocks * (desired_size + header_size) grande y colocar los objetos en él, con un encabezado para cada bloque; Obviamente, los objetos se ubicarían directamente detrás de este encabezado.

¿Qué debo tener en cuenta con respecto a la alineación de memoria en mi escenario?

La respuesta que he encontrado hasta el momento es que el tiempo que representa desired_sizen -byte datos alineados; el encabezado está correctamente alineado y empaquetado por el compilador, así como los datos reales, todo lo almacenado en el bloque será n -byte alineado.

n es cualquier límite requerido por la plataforma. Me estoy orientando a x86 por el momento, pero no me gusta hacer ninguna suposición sobre la plataforma en mi código.

Algunos de los recursos que he utilizado:

Editar

Subido código de ejemplo pequeña que puede nadie útiles tan confundido como mí en el futuro here.

Respuesta

6

Las asignaciones con malloc están garantizadas para alinearse para cualquier tipo proporcionado por el compilador y, por lo tanto, cualquier objeto [*].

El peligro es cuando su encabezado tiene un requisito de alineación menor que el requisito de alineación máxima para su implementación. Entonces su tamaño podría no ser un múltiplo del máximo. alineación, por lo que cuando intente usar/fundir buf + header_size como un puntero a algo que tiene tiene el máximo. alineación, está desalineada. En lo que respecta a C, es un comportamiento indefinido. En Intel, funciona pero es más lento. En algunos ARM provoca una excepción de hardware. En algunos ARM, silenciosamente da la respuesta incorrecta. Entonces, si no quiere hacer suposiciones acerca de la plataforma en su código, debe lidiar con eso.

Básicamente, hay tres trucos que yo sepa para asegurar que su cabecera no causa desalineación:

  • Uso Pragma una alineación específica de la implementación de forzar el asunto.
  • Utilice el conocimiento específico de la plataforma sobre el diseño y la alineación de la estructura para asegurarse de que su tamaño sea un múltiplo del requisito de alineación máxima en el sistema. Típicamente esto significa, "pegar un extra int en relleno si es necesario para convertirlo en un múltiplo 8 en lugar de solo un 4".
  • Haga que el encabezado sea union de cada tipo estándar, junto con la estructura que realmente desea usar. Funciona bien en C, pero tendría problemas en C++ si su encabezado no es válido para ser miembro de uniones.

Como alternativa, puede simplemente definir header_size no ser sizeof(header), pero para ser de ese tamaño se redondearán a un múltiplo de algún poder grueso de 2 que es "suficientemente bueno". Si desperdicia un poco de memoria, que así sea, y siempre puede tener un "encabezado de portabilidad" que define este tipo de cosas de una manera que no es puramente independiente de la plataforma, sino que hace que sea más fácil adaptarse a las nuevas plataformas.

[*] con una excepción común siendo los tipos SIMD sobredimensionados. Dado que no son estándares, y sería un derroche alinear cada asignación solo por ellos, se les quita la mano a un lado, y se necesitan funciones de asignación especiales para ellos.

+0

También podría agregar que lo mismo es cierto con todas las funciones de asignación (así '' operator new' and kin.) – GManNickG

+0

Gracias por la respuesta. ¿Cómo se determina el máximo? alineación de un tipo? Si entendí correctamente tu segundo párrafo, supongamos x86 aquí por simplicidad, esto significa que si el encabezado tiene 4 bytes, estará alineado a 4 bytes, y los datos serán de 8 bytes, estarán alineados a 8 bytes. Esto significa que necesito rellenar el encabezado con 4 bytes para que los datos estén alineados correctamente, ¿correcto? – Skurmedel

+0

He estado pensando en su propuesta "lo suficientemente buena", suena pragmático; Me gusta pragmático. ¿Cómo sabría qué es lo suficientemente bueno? ¿Esto solo se aseguraría de que el encabezado esté alineado al máximo? alineación de la plataforma o sería dependiente del tamaño real de los datos almacenados (por lo que es menos deseable)? – Skurmedel

2

Su compilador ya alineará los miembros de los objetos y las estructuras que se almacenarán en la agrupación. Usar el empaque predeterminado que sea apropiado para su arquitectura, generalmente 8 para un núcleo de 32 bits. Solo debe asegurarse de que la dirección que genere de su grupo de servidores esté alineada en consecuencia. Que en un sistema operativo de 32 bits debe ser un múltiplo de 8 bytes.

La alineación incorrecta de los objetos puede ser muy costosa cuando cruzan un límite de la línea de caché de la CPU. Un doble que se extiende a dos líneas de caché tarda tanto como tres veces más en leerse o escribirse.

+0

En realidad, el compilador no alineará necesariamente los miembros. Por ejemplo, la alineación de .in VC++ está sujeta a la opción '#pragma pack' o'/Zp'. El código que trata con los búferes IO (red, disco) con frecuencia fuerza '#pragma pack (1)' para compactar los datos en el disco o en el cable. Y hay más opciones para controlar la alineación, como '__declspec (alinear)', http://msdn.microsoft.com/en-us/library/2e70t5y1%28VS.80%29.aspx http://msdn.microsoft. com/en-us/library/83ythb65% 28v = VS.100% 29.aspx –

+2

Suspiro, sí, puede anularlo. La suposición implícita es que sabes lo que estás haciendo cuando lo haces. No tendríamos esta pregunta si el OP ya lo sabía. –

+0

El compilador alinea SIEMPRE a los miembros. Lo único es que puede anularlo para alinearlo de manera diferente. Eso no es lo mismo que no alinearlos. – Puppy

1

Use http://msdn.microsoft.com/en-us/library/bb982233.aspx - std :: alignment_of. Esto asegura que por cada T que asigne a su grupo, sabrá la alineación y puede asegurarse de que encaja. No voy a pretender saber/entender la lógica real que utilizaría para convertir esta información en garantías de alineación, pero existe y está disponible para su uso.

+1

Boost lo tiene también. – GManNickG

+0

Gracias por la respuesta, probablemente sea útil. – Skurmedel

1

Por lo que sé, boost :: pool se basa en el 'asignador de pequeños objetos' de alexandrescu explicado en su libro "diseño moderno de C++". Tengo que leer el libro (ya que también está escribiendo esto para aprender)

+0

Gracias, veré si puedo comprarlo como un libro electrónico en alguna parte. – Skurmedel

2

He alterado la alineación de la memoria también. Lo primero que debe entender es que cada objeto tiene sus propios requisitos de alineación de memoria, que es (como máximo) el tamaño del objeto en sí. Afortunadamente, a menudo es más pequeño.

Hay instalaciones de dos tipos para ayudar a escribir asignadores de memoria correctas (C++ 0x, que se puede encontrar en std::tr1 en la mayoría STL):

  • std::alignment_of da la alineación como un valor de tiempo de compilación
  • std::aligned_storage da un almacenamiento alineadas según sus parámetros

Trabajar con los dos de ellos, obtendrá lo que necesita.

Sin embargo, su diseño es un poco fuera de base, no por mucho, porque su encabezado no tendrá el mismo requisito de alineación (en general) que los objetos almacenados.

template <class T, size_t N> 
struct Block 
{ 
    // Header bits 

    typedef std::aligned_storage< 
     N*sizeof(T), 
     std::alignment_of<T>::value 
    >::type buffer_type; 
    buffer_type mBuffer; 
}; 

Dos notas:

  • Block sí pueden tener una alineación diferente, pero no importa
  • usted podría utilizar sizeof para la alineación, que está garantizado para trabajar, aunque un poco derrochador

Finalmente, también puede tener éxito sin todos estos, y algunos aritmética de punteros, pero ya que se ofrece ....

+0

Gracias por la respuesta, parece que serán útiles. Aunque creo que podría pasar la alineación en su lugar, ya que me gustaría mantener el grupo no específico de tipo; pero esto solo significa que usaré alignment_of en otro lugar. – Skurmedel

+0

Las cosas se vuelven un poco más complicadas si vas por el camino genérico, debido a la fragmentación. No dude en crear un primer conjunto específico de tipo (o al menos específico de alineación) y, a continuación, envolverlo con uno genérico que solo administre una colección de ellos. –

+0

Matthieu, una buena idea. Evitar la fragmentación fue uno de los puntos de tener un grupo en primer lugar, ya que sospecho que tendré una cantidad bastante masiva de asignaciones menores. – Skurmedel

Cuestiones relacionadas