Ejemplos convincentes de asignadores C++ personalizados?


¿Cuáles son algunas razones realmente buenas para deshacerse de std::allocator en favor de una solución personalizada? ¿Ha encontrado alguna situación en la que fuera absolutamente necesario para la corrección, el rendimiento, la escalabilidad, etc.? ¿Algún ejemplo realmente inteligente?

Los asignadores personalizados siempre han sido una característica de la Biblioteca Estándar que no he tenido mucha necesidad. Me preguntaba si alguien aquí podría proporcionar algunos ejemplos convincentes para justificar su existencia.

Author: Naaff, 2009-05-05

16 answers

Como menciono aquí , he visto que el asignador STL personalizado de Intel TBB mejora significativamente el rendimiento de una aplicación multiproceso simplemente cambiando una única

std::vector<T>

A

std::vector<T,tbb::scalable_allocator<T> >

(esta es una forma rápida y conveniente de cambiar el asignador para usar el ingenioso hilo de TBB: montones privados; ver página 7 en este documento )

 97
Author: timday,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:32

Un área donde los asignadores personalizados pueden ser útiles es el desarrollo de juegos, especialmente en consolas de juegos, ya que tienen solo una pequeña cantidad de memoria y no tienen intercambio. En tales sistemas, debe asegurarse de tener un control estricto sobre cada subsistema, de modo que un sistema no crítico no pueda robar la memoria de uno crítico. Otras cosas como los asignadores de grupos pueden ayudar a reducir la fragmentación de la memoria. Puede encontrar un documento largo y detallado sobre el tema en:

EASTL Standard Electronic Arts Standard Biblioteca de plantillas

 71
Author: Grumbel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-16 21:28:38

Estoy trabajando en un asignador mmap que permite a los vectores utilizar la memoria de un archivo mapeado en memoria. El objetivo es tener vectores que utilizan almacenamiento que están directamente en la memoria virtual mapeada por mmap. Nuestro problema es mejore la lectura de archivos realmente grandes (>10GB) en memoria sin copia sobrecarga, por lo tanto necesito este asignador personalizado.

Hasta ahora tengo el esqueleto de un asignador personalizado (que deriva de std::allocator), creo que es un buen comienzo punto para escribir propio asignadores. Siéntase libre de usar esta pieza de código de la manera que quieras:

#include <memory>
#include <stdio.h>

namespace mmap_allocator_namespace
{
        // See StackOverflow replies to this answer for important commentary about inheriting from std::allocator before replicating this code.
        template <typename T>
        class mmap_allocator: public std::allocator<T>
        {
public:
                typedef size_t size_type;
                typedef T* pointer;
                typedef const T* const_pointer;

                template<typename _Tp1>
                struct rebind
                {
                        typedef mmap_allocator<_Tp1> other;
                };

                pointer allocate(size_type n, const void *hint=0)
                {
                        fprintf(stderr, "Alloc %d bytes.\n", n*sizeof(T));
                        return std::allocator<T>::allocate(n, hint);
                }

                void deallocate(pointer p, size_type n)
                {
                        fprintf(stderr, "Dealloc %d bytes (%p).\n", n*sizeof(T), p);
                        return std::allocator<T>::deallocate(p, n);
                }

                mmap_allocator() throw(): std::allocator<T>() { fprintf(stderr, "Hello allocator!\n"); }
                mmap_allocator(const mmap_allocator &a) throw(): std::allocator<T>(a) { }
                template <class U>                    
                mmap_allocator(const mmap_allocator<U> &a) throw(): std::allocator<T>(a) { }
                ~mmap_allocator() throw() { }
        };
}

Para usar esto, declare un contenedor STL de la siguiente manera:

using namespace std;
using namespace mmap_allocator_namespace;

vector<int, mmap_allocator<int> > int_vec(1024, 0, mmap_allocator<int>());

Se puede usar, por ejemplo, para registrar cada vez que se asigna memoria. Qué es necesario es la estructura rebind, de lo contrario el contenedor vectorial utiliza las superclases allocate / deallocate método.

Actualización: El asignador de asignación de memoria ya está disponible en https://github.com/johannesthoma/mmap_allocator y es LGPL. Siéntase libre de úsalo para tus proyectos.

 57
Author: Johannes Thoma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-10 16:27:29

Estoy trabajando con un motor de almacenamiento MySQL que utiliza c++ para su código. Estamos usando un asignador personalizado para usar el sistema de memoria MySQL en lugar de competir con MySQL por la memoria. Nos permite asegurarnos de que estamos usando la memoria que el usuario configuró para usar MySQL, y no "extra".

 24
Author: Thomas Jones-Low,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-05 20:00:21

Puede ser útil usar asignadores personalizados para usar un grupo de memoria en lugar del montón. Ese es un ejemplo entre muchos otros.

Para la mayoría de los casos, esta es sin duda una optimización prematura. Pero puede ser muy útil en ciertos contextos (dispositivos embebidos, juegos, etc.).

 15
Author: Martin Cote,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-05 19:52:33

No he escrito código C++ con un asignador STL personalizado, pero puedo imaginar un servidor web escrito en C++, que utiliza un asignador personalizado para la eliminación automática de los datos temporales necesarios para responder a una solicitud HTTP. El asignador personalizado puede liberar todos los datos temporales a la vez una vez que se ha generado la respuesta.

Otro posible caso de uso para un asignador personalizado (que he utilizado) es escribir una prueba unitaria para demostrar que el comportamiento de una función no depende de alguna parte de su entrada. El asignador personalizado puede llenar la región de memoria con cualquier patrón.

 7
Author: pts,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-05 19:52:47

Estoy usando asignadores personalizados aquí; incluso se podría decir que era para trabajar alrededor de otra gestión de memoria dinámica personalizada.

Background: tenemos sobrecargas para malloc, calloc, free, y las diversas variantes de operator new y delete, y el enlazador felizmente hace que STL las use para nosotros. Esto nos permite hacer cosas como agrupación automática de objetos pequeños, detección de fugas, relleno de aloc, relleno libre, asignación de relleno con centinelas, alineación de línea de caché para ciertos alocs y retraso libre.

El problema es que nos estamos ejecutando en un entorno embebido't no hay suficiente memoria para hacer la contabilidad de detección de fugas correctamente durante un período prolongado. Al menos, no en la RAM estándar there hay otro montón de RAM disponible en otros lugares, a través de funciones de asignación personalizadas.

Solución: escriba un asignador personalizado que use el montón extendido, y úselo solo en el interior de la arquitectura de seguimiento de fugas de memoria... Todo lo demás por defecto a las sobrecargas nuevas/eliminadas normales que hacen el seguimiento de fugas. Esto evita el seguimiento del rastreador en sí (y proporciona un poco de funcionalidad de empaque adicional también, sabemos el tamaño de los nodos del rastreador).

También usamos esto para mantener los datos de perfiles de costos de función, por la misma razón; escribir una entrada para cada llamada y devolución de función, así como los interruptores de hilo, puede ser costoso rápidamente. Custom allocator nuevamente nos da asignaciones más pequeñas en un área de memoria de depuración más grande.

 5
Author: leander,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-05 20:23:00

Cuando se trabaja con GPU u otros coprocesadores, a veces es beneficioso asignar estructuras de datos en la memoria principal de una manera especial. Esta forma especial de asignar memoria puede implementarse en un asignador personalizado de una manera conveniente.

La razón por la que la asignación personalizada a través del tiempo de ejecución del acelerador puede ser beneficiosa cuando se usan aceleradores es la siguiente:

  1. a través de la asignación personalizada el tiempo de ejecución del acelerador o controlador se notifica de la bloque de memoria
  2. además, el sistema operativo puede asegurarse de que el bloque de memoria asignado esté bloqueado por páginas (algunos llaman a esto memoria fija), es decir, el subsistema de memoria virtual del sistema operativo no puede mover o eliminar la página dentro o fuera de la memoria
  3. si 1. y 2. hold y se solicita una transferencia de datos entre un bloque de memoria bloqueado por página y un acelerador, el tiempo de ejecución puede acceder directamente a los datos en la memoria principal ya que sabe dónde está y puede estar seguro el sistema operativo no se movió/eliminó
  4. esto guarda una copia de memoria que se produciría con la memoria que se asignó de una manera no bloqueada por página: los datos deben copiarse en la memoria principal a un área de ensayo bloqueada por página desde el acelerador puede inicializar la transferencia de datos (a través de DMA)
 5
Author: Sebastian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-28 21:42:58

Estoy usando un asignador personalizado para contar el número de asignaciones/desasignaciones en una parte de mi programa y medir cuánto tiempo lleva. Hay otras formas en que esto podría lograrse, pero este método es muy conveniente para mí. Es especialmente útil que pueda usar el asignador personalizado solo para un subconjunto de mis contenedores.

 4
Author: Jørgen Fogh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-27 12:09:47

Una situación esencial: Al escribir código que debe funcionar a través de los límites de los módulos (EXE/DLL), es esencial mantener sus asignaciones y eliminaciones sucediendo en un solo módulo.

Donde me encontré con esto fue una arquitectura de plugin en Windows. Es esencial que, por ejemplo, si pasa una cadena std:: a través del límite DLL, cualquier reasignación de la cadena se produzca desde el montón del que se originó, NO desde el montón de la DLL, que puede ser diferente*.

*Es más complicado que esto en realidad, como si estuvieras enlazando dinámicamente a la CRT esto podría funcionar de todos modos. Pero si cada DLL tiene un enlace estático al CRT, se dirige a un mundo de dolor, donde ocurren continuamente errores de asignación fantasma.

 3
Author: Stephen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-17 02:47:31

Un ejemplo del tiempo que he usado estos fue trabajar con sistemas embebidos con recursos muy limitados. Digamos que tienes 2k de ram gratis y tu programa tiene que usar algo de esa memoria. Es necesario almacenar 4-5 secuencias en algún lugar que no está en la pila y, además, necesita tener un acceso muy preciso sobre dónde se almacenan estas cosas, esta es una situación en la que es posible que desee escribir su propio asignador. Las implementaciones predeterminadas pueden fragmentar la memoria, esto podría ser inaceptable si no tiene suficiente memoria y no puede reiniciar el programa.

Un proyecto en el que estaba trabajando estaba usando AVR-GCC en algunos chips de baja potencia. Tuvimos que almacenar 8 secuencias de longitud variable pero con un máximo conocido. La implementación de la biblioteca estándar de la administración de memoria es una envoltura delgada alrededor de malloc / free que realiza un seguimiento de dónde colocar los elementos anteponiendo cada bloque de memoria asignado con un puntero justo después del final de esa pieza asignada de memoria. Cuando se asigna una nueva pieza de memoria, el asignador estándar tiene que caminar sobre cada una de las piezas de memoria para encontrar el siguiente bloque que está disponible donde cabrá el tamaño de memoria solicitado. En una plataforma de escritorio esto sería muy rápido para estos pocos elementos, pero hay que tener en cuenta que algunos de estos microcontroladores son muy lentos y primitivos en comparación. Además, el problema de fragmentación de la memoria era un problema masivo que significaba que realmente no teníamos más remedio que tomar un enfoque diferente.

Así que lo que hicimos fue implementar nuestro propio grupo de memoria. Cada bloque de memoria era lo suficientemente grande como para encajar en la secuencia más grande que necesitaríamos. Esto asignaba bloques de memoria de tamaño fijo antes de tiempo y marcaba qué bloques de memoria estaban actualmente en uso. Hicimos esto manteniendo un entero de 8 bits donde cada bit representado si se utilizó un bloque determinado. Cambiamos el uso de memoria aquí por intentar hacer todo el proceso más rápido, lo que en nuestro caso fue justificado como estábamos empujando este microprocesador del microcontrolador cerca de él es capacidad de proceso máxima.

Hay varias otras veces que puedo ver escribiendo su propio asignador personalizado en el contexto de sistemas embebidos, por ejemplo, si la memoria para la secuencia no está en la ram principal, como podría ser el caso con frecuencia en estas plataformas.

 3
Author: shuttle87,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-11 16:36:08

Para la memoria compartida es vital que no solo la cabeza del contenedor, sino también los datos que contiene se almacenen en la memoria compartida.

El asignador de Boost::Interprocess es un buen ejemplo. Sin embargo, como puede leer aquí este allone no es suficiente, para que todos los contenedores STL sean compatibles con la memoria compartida (Debido a diferentes desplazamientos de asignación en diferentes procesos, los punteros podrían "romperse").

 2
Author: ted,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-25 14:12:41

Enlace obligatorio a la charla CppCon 2015 de Andrei Alexandrescu sobre asignadores:

Https://www.youtube.com/watch?v=LIb3L4vKZ7U

Lo bueno es que simplemente diseñarlos te hace pensar en ideas de cómo los usarías: -)

 2
Author: einpoklum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-14 22:54:37

Hace algún tiempo encontré esta solución muy útil para mí: Fast C++11 allocator for STL containers. Acelera ligeramente los contenedores STL en VS2017 (~5x), así como en GCC (~7x). Es un asignador de propósito especial basado en el grupo de memoria. Se puede utilizar con contenedores STL solo gracias al mecanismo que está pidiendo.

 2
Author: no one special,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-05 15:35:51

Yo personalmente uso Loki::Allocator / SmallObject para optimizar el uso de memoria para objetos pequeños - muestra una buena eficiencia y un rendimiento satisfactorio si tiene que trabajar con cantidades moderadas de objetos realmente pequeños (de 1 a 256 bytes). Puede ser hasta ~30 veces más eficiente que la asignación estándar de C++ new/delete si hablamos de asignar cantidades moderadas de objetos pequeños de muchos tamaños diferentes. Además, hay una solución específica de VC llamada "QuickHeap", que brinda el mejor rendimiento posible (las operaciones allocate y deallocate solo leen y escriben la dirección del bloque que se está asignando/devolviendo al montón, respectivamente en hasta 99.(9) % casos-depende de la configuración y la inicialización), pero a un costo de una sobrecarga notable - necesita dos punteros por extensión y un extra por cada nuevo bloque de memoria. Es una solución más rápida posible para trabajar con enormes (10 000++) cantidades de objetos que se crean y eliminan si no necesita una gran variedad de tamaños de objetos (crea un grupo individual para cada tamaño de objeto, de 1 a 1023 bytes en la implementación actual, por lo que los costos de inicialización pueden disminuir el aumento del rendimiento general, pero se puede seguir adelante y asignar/desasignar algunos objetos ficticios antes de que la aplicación entre en su fase(s) crítica (s) de rendimiento).

El problema con la implementación estándar de C++ new/delete es que generalmente es solo una envoltura para C malloc/asignación gratuita, y funciona bien para bloques de memoria más grandes, como 1024+ bytes. Tiene una sobrecarga notable en términos de rendimiento y, a veces, memoria adicional utilizada para el mapeo también. Por lo tanto, en la mayoría de los casos, los asignadores personalizados se implementan de manera que maximicen el rendimiento y/o minimicen la cantidad de memoria adicional necesaria para asignar objetos pequeños (≤1024 bytes).

 1
Author: Fractal Multiversity,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-02 18:21:16

En una simulación gráfica, he visto asignadores personalizados utilizados para

  1. Restricciones de alineación que std::allocator no soporta directamente.
  2. Minimizando la fragmentación mediante el uso de grupos separados para asignaciones de corta duración (solo este marco) y de larga duración.
 1
Author: Adrian McCarthy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-15 00:48:50