2012-03-10 34 views
6

He estado investigando el cambio de mi método de asignación de sobrecarga de simpling nuevo a usar múltiples asignadores a través de la base de código. Sin embargo, ¿cómo puedo usar de manera eficiente múltiples asignadores? La única manera que pude idear a través de mi investigación fue hacer que los asignadores sean globales. Aunque, esto parecía tener problemas, ya que normalmente es una "mala idea" utilizar muchos elementos globales.Uso de múltiples asignadores de manera eficiente

Estoy buscando información sobre cómo usar múltiples asignadores de manera eficiente. Por ejemplo, puedo tener un uso de asignador solo para un subsistema particular, y un asignador diferente para un subsistema diferente. No estoy seguro de si la única forma de hacerlo es mediante el uso de múltiples asignadores globales, por lo que espero una mejor visión y diseño.

+2

¿Por qué un asignador debe ser global? Siempre que cada unidad asignada tenga una referencia a su propio asignador para que pueda liberarse correctamente, ¿qué importancia tiene el asignador en realidad? –

+0

Sin embargo, ¿dónde se iría el asignador para la unidad asignada? Me parece que debería ser global. – chadb

Respuesta

8

En C++ 2003, el modelo del asignador está roto y no hay realmente una solución adecuada. Para C++ 2011, el modelo del asignador fue corregido y puede tener asignadores por instancia que se propagan a objetos contenidos (a menos que, por supuesto, elija reemplazarlos). Generalmente, para que esto sea útil, probablemente desee utilizar un tipo de asignador dinámicamente polimórfico que no se requiere que sea el predeterminado std::allocator<T> (y generalmente esperaría que no fuera dinámicamente polimórfico, aunque esta podría ser la mejor opción de implementación). Sin embargo, [casi] todas las clases de la biblioteca estándar de C++ que asignan memoria son plantillas que toman el tipo de asignador como argumento de plantilla (por ejemplo, IOStreams son una excepción, pero generalmente no asignan ninguna cantidad interesante de memoria para justificar agregar soporte de asignador)

En varios de sus comentarios insiste en que los asignantes efectivamente deben ser globales: eso definitivamente no es correcto. Cada tipo de asignador almacena una copia del asignador dado (al menos, si tiene datos de nivel de instancia, si no hay nada que almacenar como es, por ejemplo, el caso con el asignador predeterminado usando operator new() y operator delete()) . Esto significa efectivamente que el mecanismo de asignación otorgado a un objeto debe mantenerse mientras exista un asignador activo que lo use. Este puede hacerse usando un objeto global, pero también se puede hacer usando, p. recuento de referencia o asociación del asignador con un objeto que contiene todos los objetos a los que está asignado. Por ejemplo, si cada "documento" (piense XML, Excel, Pages, cualquier archivo de estructura) pasa un asignador a sus miembros, el asignador puede vivir como miembro del documento y destruirse cuando el documento se destruye después de que se destruye todo su contenido . Esta parte del modelo de asignador debería funcionar con clases anteriores a C++ 2011, siempre que también tomen un argumento de asignación. Sin embargo, en las clases anteriores a C++ 2011, el asignador no se pasará a los objetos contenidos. Por ejemplo, si asigna un asignador a std::vector<std::string>, la versión de C++ 2011 creará el std::string utilizando el asignador asignado al std::vector<std::string> debidamente convertido para ocuparse de std::string s.Esto no sucederá con los asignadores anteriores a C++ 2011.

Para utilizar realmente los asignadores en un subsistema, deberá pasarlos de manera explícita, ya sea explícitamente como argumento para sus funciones y/o clases o implícitamente por medio de objetos que reconocen el asignador que sirven como contexto. Por ejemplo, si usa cualquiera de los contenedores estándar como [parte del] contexto pasado, puede obtener el asignador usado utilizando su método get_allocator().

+0

Muy interesante, no había pensado en el recuento de referencias. Como voy a crear mi propio asignador, ¿debería hacerlo heredar de algo como weak_reference para permitir el recuento de referencias? – chadb

+1

Personalmente, usaría una 'std :: shared_ptr ' como miembro de un asignador 'my_allocator'. La lógica de asignación real se ubicaría en las clases derivadas de 'my_allocation_base'. Si solo tiene un enfoque de asignación, puede, por supuesto, poner la lógica directamente en el objeto apuntado. El uso de 'weak_reference' (no sé qué es esto) suena como si no funcionara: desea tener una referencia real al objeto de asignación de cada objeto que aún tiene memoria asignada correspondientemente y el recuento de referencias se reduce una vez que fue liberado. –

2

Puede usar la colocación new. Esto se puede utilizar para especificar una región de memoria o para sobrecargar el tipo static void* operator new(ARGS). Globales no son necesarios, y realmente una mala idea aquí, si la eficiencia es importante y sus problemas son exigentes. Debería aferrarse a uno o más asignadores, por supuesto.

Lo mejor que puede hacer es comprender sus problemas y crear estrategias para sus asignadores según los patrones en su programa y en el uso real. El propósito general malloc es muy bueno en lo que hace, por lo tanto siempre use eso como una línea de base para medir. Si no conoce sus patrones de uso, es probable que su asignador sea más lento que malloc.

Además, tenga en cuenta que estos tipos que utilice perderán compatibilidad con los contenedores estándar, a menos que utilice un localizador global o de hilos local y personalizado para contenedores estándar, lo que vence rápidamente el propósito en muchos contextos. La alternativa es escribir también sus propios asignadores y contenedores.

+0

¿Cómo se deben guardar los asignados? Mencionaste que no deberían ser globales, pero ¿dónde debería estar entonces? (Además, no me preocupa la compatibilidad con los contenedores estándar, como ya tengo el mío). – chadb

+0

@chadb Depende del problema. Uso tanto la referencia de miembro (por ejemplo, con asignaciones contadas de referencia) como la referencia externa (por ejemplo, un asignador para un gráfico cuyos nodos son gestionados por un asignador). Los asignadores locales de subprocesos (a los que se accede mediante el subproceso o sus datos) son otro enfoque, aunque en este caso prefiero la referencia externa. – justin

+0

¿Qué tal específicamente para un asignador utilizado para todo un subsistema (cuya raíz no está en un singleton)? Ese parece ser mi caso de uso principal en este momento, como mencioné en la publicación original, creo. ¿Dónde se almacenaría el asignador para eso? No puedo pensar en ningún caso además de un global allí. – chadb

1

Algunos usos para asignadores múltiples incluyen uso reducido de CPU, fragmentación reducida y menos fallas de caché. Entonces la solución realmente depende de qué tipo y dónde está el cuello de botella de asignación.

El uso de CPU se mejorará al tener montones sin bloqueo para hilos activos, eliminando la sincronización. Esto se puede hacer en su asignador de memoria con almacenamiento local de subprocesos.

La fragmentación se mejorará asignando distintas asignaciones de diferentes acumulaciones de vida: la asignación de IO de fondo en un montón separado de la tarea activa de los usuarios asegurará que los dos no se confundan entre sí. Esto probablemente se logra al tener una pila para sus montones y empujar/hacer estallar cuando se encuentra en diferentes ámbitos funcionales.

Las fallas de caché se mejorarán al mantener las asignaciones dentro de un sistema en conjunto. Tener las asignaciones de Quadtree/Octree provienen de su propio montón que garantizará que exista una ubicación a la vista de las consultas de frustrum. Esto se realiza mejor al sobrecargar operador nuevo y eliminar operador para las clases específicas (OctreeNode).

+0

Quizás algo que se malinterpretó, sin embargo, mi pregunta era principalmente cómo usar múltiples asignadores, no por qué. – chadb

Cuestiones relacionadas