Supongamos que hay dos hilos que se comunican enviando mensajes de datos de forma asíncrona entre sí. Cada hilo tiene algún tipo de cola de mensajes.
Mi pregunta es de muy bajo nivel: ¿Cuál puede ser la forma más eficiente de administrar la memoria? Se me ocurren varias soluciones:
- El remitente crea el objeto a través de
new
. Receptor de llamadasdelete
. - Agrupación de memoria (para transferir la memoria al remitente)
- Recolección de basura (p. Ej., Boehm GC)
- (si los objetos son lo suficientemente pequeños) copie por valor para evitar la asignación del montón por completo
1) es la solución más obvia, así que la usaré para un prototipo. Lo más probable es que ya sea lo suficientemente bueno. Pero independientemente de mi problema específico, me pregunto qué técnica es más prometedora si está optimizando el rendimiento.
Esperaría que la agrupación sea teóricamente la mejor, especialmente porque puede usar un conocimiento adicional sobre el flujo de información entre los hilos. Sin embargo, me temo que también es lo más difícil de hacer bien. Mucha afinación ... :-(
La recolección de basura debería ser bastante fácil de agregar después (después de la solución 1), y esperaría que funcione muy bien. Entonces, supongo que es la solución más práctica si 1) resulta ser demasiado ineficiente.
Si los objetos son pequeños y simples, la copia por valor podría ser la más rápida. Sin embargo, me temo que impone limitaciones innecesarias en la implementación de los mensajes compatibles, por lo que quiero evitarlo.
fuente
unique_ptr
, supongo que quieres decirshared_ptr
. Pero aunque no hay duda de que usar un puntero inteligente es bueno para la administración de recursos, no cambia el hecho de que esté utilizando algún tipo de asignación de memoria y desasignación. Creo que esta pregunta es más de bajo nivel.El mayor impacto en el rendimiento cuando se comunica un objeto de un hilo a otro es la sobrecarga de agarrar un candado. Esto es del orden de varios microsegundos, que es significativamente más que el tiempo promedio que toma un par de
new
/delete
(del orden de cien nanosegundos). Lasnew
implementaciones sanas intentan evitar el bloqueo a casi todos los costos para evitar su impacto en el rendimiento.Dicho esto, desea asegurarse de que no necesita agarrar cerraduras al comunicar los objetos de un hilo a otro. Conozco dos métodos generales para lograr esto. Ambos funcionan solo unidireccionalmente entre un remitente y un receptor:
Use un anillo de amortiguación. Ambos procesos controlan un puntero en este búfer, uno es el puntero de lectura, el otro es el puntero de escritura.
El remitente primero verifica si hay espacio para agregar un elemento comparando los punteros, luego agrega el elemento, luego incrementa el puntero de escritura.
El receptor comprueba si hay un elemento para leer comparando los punteros, luego lee el elemento y luego incrementa el puntero de lectura.
Los punteros deben ser atómicos, ya que se comparten entre los hilos. Sin embargo, cada puntero solo es modificado por un hilo, el otro solo necesita acceso de lectura al puntero. Los elementos en el búfer pueden ser punteros, lo que le permite dimensionar fácilmente el búfer de anillo a un tamaño que no bloquee al remitente.
Use una lista vinculada que siempre contenga al menos un elemento. El receptor tiene un puntero al primer elemento, el emisor tiene un puntero al último elemento. Estos punteros no son compartidos.
El remitente crea un nuevo nodo para la lista vinculada y establece su
next
puntero ennullptr
. Luego actualiza elnext
puntero del último elemento para que apunte al nuevo elemento. Finalmente, almacena el nuevo elemento en su propio puntero.El receptor observa el
next
puntero del primer elemento para ver si hay nuevos datos disponibles. Si es así, elimina el primer elemento anterior, avanza su propio puntero para apuntar al elemento actual y comienza a procesarlo.En esta configuración, los
next
punteros deben ser atómicos, y el remitente debe asegurarse de no desreferenciar el segundo último elemento después de haber establecido sunext
puntero. La ventaja es, por supuesto, que el remitente nunca tiene que bloquear.Ambos enfoques son mucho más rápidos que cualquier enfoque basado en bloqueo, pero requieren una implementación cuidadosa para hacerlo bien. Y, por supuesto, requieren la atomicidad del hardware nativo de las escrituras / cargas del puntero; si su
atomic<>
implementación usa un bloqueo internamente, está prácticamente condenado.Del mismo modo, si tiene varios lectores y / o escritores, está prácticamente condenado: puede intentar crear un esquema sin bloqueo, pero será difícil de implementar en el mejor de los casos. Estas situaciones son mucho más fáciles de manejar con un candado. Sin embargo, una vez que agarra una cerradura, puede dejar de preocuparse por
new
/delete
rendimiento.fuente