front - stack c++ stl
¿Qué contenedor de STL debería usar para un FIFO? (6)
¿Qué contenedor de STL se ajustaría mejor a mis necesidades? Básicamente, tengo un contenedor de 10 elementos en el que push_back
continuamente hacia push_back
elementos nuevos mientras pop_front
en el elemento más antiguo (alrededor de un millón de veces).
Actualmente estoy usando un std::deque
para la tarea, pero me preguntaba si una std::list
sería más eficiente ya que no necesitaría reasignarme (o tal vez estoy confundiendo una std::deque
por una std::vector
?). ¿O hay un contenedor aún más eficiente para mi necesidad?
PD: no necesito acceso aleatorio
Continuamente
push_back
elementos nuevos mientraspop_front
ing el elemento más antiguo (alrededor de un millón de veces).
Un millón realmente no es un gran número en informática. Como otros han sugerido, use una std::queue
como su primera solución. En el caso poco probable de que sea demasiado lento, identifique el cuello de botella usando un generador de perfiles (¡no lo adivine!) Y vuelva a implementarlo utilizando un contenedor diferente con la misma interfaz.
¿Por qué no std::queue
? Todo lo que tiene es push_back
y pop_front
.
Dado que hay una gran cantidad de respuestas, puede estar confundido, pero para resumir:
Use una std::queue
. La razón de esto es simple: es una estructura FIFO. Quieres FIFO, usas una std::queue
.
Hace que tu intención sea clara para cualquier otra persona, e incluso para ti mismo. Una std::list
o std::deque
no lo hace. Una lista puede insertarse y eliminarse en cualquier lugar, que no es lo que se supone que debe hacer una estructura FIFO, y una deque
puede agregarse y eliminarse desde cualquier extremo, lo cual también es algo que una estructura FIFO no puede hacer.
Es por eso que deberías usar una queue
.
Ahora, preguntaste sobre el rendimiento. En primer lugar, recuerde siempre esta importante regla de oro: primero, código bueno, rendimiento último.
La razón de esto es simple: las personas que luchan por el rendimiento antes de la limpieza y la elegancia casi siempre terminan en último lugar. Su código se convierte en un desastre, porque han abandonado todo lo que es bueno para realmente no sacar nada de él.
Al escribir primero un código bueno y legible, la mayoría de sus problemas de rendimiento se resolverán solos. Y si más tarde descubre que su rendimiento es deficiente, ahora es fácil agregar un generador de perfiles a su código limpio y agradable, y descubrir dónde está el problema.
Dicho todo esto, std::queue
es solo un adaptador. Proporciona la interfaz segura, pero utiliza un contenedor diferente en el interior. Puede elegir este contenedor subyacente, y esto permite una gran flexibilidad.
Entonces, ¿qué contenedor subyacente debería usar? Sabemos que std::list
y std::deque
proporcionan las funciones necesarias ( push_back()
, pop_front()
y front()
), entonces, ¿cómo decidimos?
Primero, comprenda que asignar (y desasignar) la memoria no es una tarea rápida, generalmente, porque implica salir al sistema operativo y pedirle que haga algo. Una list
tiene que asignar memoria cada vez que se agrega algo, y desasignarlo cuando desaparece.
Un deque
, por otro lado, asigna en trozos. Se asignará con menos frecuencia que una list
. Piense en ello como una lista, pero cada fragmento de memoria puede albergar múltiples nodos. (Por supuesto, sugeriría que realmente aprendas cómo funciona ).
Por lo tanto, solo con eso un deque
debería tener un mejor rendimiento, ya que no se ocupa de la memoria con tanta frecuencia. Mezclado con el hecho de que está manejando datos de tamaño constante, probablemente no tendrá que asignarlos después de la primera pasada a través de los datos, mientras que una lista será asignada y desasignada constantemente.
Una segunda cosa para entender es el rendimiento de la memoria caché . Salir a la memoria RAM es lento, por lo que cuando la CPU realmente lo necesita, saca lo mejor de esta vez al llevar una porción de memoria a la caché. Debido a que una deque
asigna trozos de memoria, es probable que al acceder a un elemento en este contenedor la CPU vuelva a traer el resto del contenedor. Ahora cualquier acceso adicional al deque
será rápido, porque los datos están en caché.
Esto es diferente a una lista, donde los datos se asignan de uno en uno. Esto significa que los datos se pueden distribuir por todas partes en la memoria, y el rendimiento de la memoria caché será malo.
Entonces, considerando eso, un deque
debería ser una mejor opción. Es por eso que es el contenedor predeterminado cuando se utiliza una queue
. Dicho todo esto, esta es solo una conjetura (muy) educada: tendrás que perfilar este código, usando un deque
en una prueba y una list
en la otra para saberlo con certeza.
Pero recuerde: haga que el código funcione con una interfaz limpia, luego preocúpese por el rendimiento.
John plantea la preocupación de que envolver una list
o deque
cause una disminución en el rendimiento. Una vez más, ni él ni yo podemos decirlo con certeza sin elaborar un perfil nosotros mismos, pero lo más probable es que el compilador alinee las llamadas que hace la queue
. Es decir, cuando dices queue.push()
, simplemente dirá queue.container.push_back()
omitiendo la llamada a la función por completo.
Una vez más, esto es solo una suposición educada, pero usar una queue
no degradará el rendimiento, en comparación con el uso del contenedor subyacente en bruto. Como he dicho antes, use la queue
, porque es limpia, fácil de usar y segura, y si realmente se convierte en un perfil problemático y una prueba.
Donde el rendimiento realmente importa, consulte la biblioteca de buffer circular Boost .
Echa un vistazo a std :: queue. Envuelve un tipo de contenedor subyacente, y el contenedor predeterminado es std :: deque.