2012-10-02 20 views
7

Acabo de enterarme que el estándar std deque es realmente lento cuando se compara con mi versión "casera" de la pila que usa una matriz preasignada.
Este es el código de mi pila:std deque es sorprendentemente lento

template <class T> 
class FastStack 
{  
public: 
    T* st; 
    int allocationSize; 
    int lastIndex; 

public: 
    FastStack(int stackSize); 
    FastStack(); 
    ~FastStack(); 

    inline void resize(int newSize); 
    inline void push(T x); 
    inline void pop(); 
    inline T getAndRemove(); 
    inline T getLast(); 
    inline void clear(); 
}; 

template <class T> 
FastStack<T>::FastStack() 
{ 
    lastIndex = -1; 
    st = NULL; 
} 

template <class T> 
FastStack<T>::FastStack(int stackSize) 
{ 
    st = NULL; 
    this->allocationSize = stackSize; 
    st = new T[stackSize]; 
    lastIndex = -1; 
} 

template <class T> 
FastStack<T>::~FastStack() 
{ 
    delete [] st; 
} 

template <class T> 
void FastStack<T>::clear() 
{ 
    lastIndex = -1; 
} 

template <class T> 
T FastStack<T>::getLast() 
{ 
    return st[lastIndex]; 
} 

template <class T> 
T FastStack<T>::getAndRemove() 
{ 
    return st[lastIndex--]; 
} 

template <class T> 
void FastStack<T>::pop() 
{ 
    --lastIndex; 
} 

template <class T> 
void FastStack<T>::push(T x) 
{ 
    st[++lastIndex] = x; 
} 

template <class T> 
void FastStack<T>::resize(int newSize) 
{ 
    if (st != NULL) 
     delete [] st; 
    st = new T[newSize]; 
} 

.
corro este simple punto de referencia para deque:

std::deque<int> aStack; 
int x; 

HRTimer timer; 
timer.Start(); 

for (int i = 0; i < 2000000000; i++) 
{ 
    aStack.push_back(i); 
    x = aStack.back(); 
    if (i % 100 == 0 && i != 0) 
     for (int j = 0; j < 100; j++) 
      aStack.pop_back(); 
} 

double totalTime = timer.Stop(); 
stringstream ss; 
ss << "std::deque " << totalTime; 
log(ss.str()); 

.
utilizan la pila de std con vector como recipiente (como 'Michael Kohne' sugerido)

std::stack<int, std::vector<int>> bStack; 
int x; 

HRTimer timer; 
timer.Start(); 

for (int i = 0; i < 2000000000; i++) 
{ 
    bStack.push(i); 
    x = bStack.top(); 
    if (i % 100 == 0 && i != 0) 
     for (int j = 0; j < 100; j++) 
      bStack.pop(); 
} 

double totalTime = timer.Stop(); 
stringstream ss; 
ss << "std::stack " << totalTime; 
log(ss.str()); 

.
Y lo mismo para mi FastStack:

FastStack<int> fstack(200); 
int x; 

HRTimer timer; 
timer.Start(); 

for (int i = 0; i < 2000000000; i++) 
{ 
    fstack.push(i); 
    x = fstack.getLast(); 
    if (i % 100 == 0 && i != 0) 
     for (int j = 0; j < 100; j++) 
      fstack.pop(); 
} 

double totalTime = timer.Stop(); 
stringstream ss; 
ss << "FastStack " << totalTime; 
log(ss.str()); 

.
Los resultados después de 4 pistas son como sigue:
deque 15.529
deque 15,3756
deque 15.429
deque 15,4778

pila 6,19099
pila 6,1834
pila 6,19315
pila 6,19841

FastStack 3.01085
FastStack 2,9934
FastStack 3,02536
FastStack 3,00937

Los resultados se expresan en segundos y estaba ejecutando el código en 3570k i5 de Intel (en el reloj por defecto). Usé el compilador VS2010 con todas las optimizaciones que están disponibles. Sé que mi FastStack tiene muchas limitaciones, pero hay muchas situaciones en las que se puede usar y ¡cuando puede dar un buen impulso! (Lo usé en un proyecto donde obtengo una aceleración de 2x en comparación con std :: queue).
Así que ahora mi pregunta es:
¿Hay algún otro "inhibidor" en C++ que todos usen pero nadie sepa sobre ellos?
EDITAR: No quiero ser ofensivo, solo tengo curiosidad si conoces algunas "aceleraciones" desconocidas como esta.

+0

Preguntar acerca de una lista de "inhibidores" como este no es constructivo. – nhahtdh

+2

Después de compilar la deque, llame [resize] (http://www.cplusplus.com/reference/stl/deque/resize/) en ella. Vea si hace una gran diferencia de velocidad. Supongo que una gran parte de la velocidad ganada/perdida proviene de la clase deque que intenta administrar la memoria de forma "inteligente" cambiando el tamaño. –

+0

Te faltan frenos en tu declaración if. – Rapptz

Respuesta

14

Si conoce la cantidad máxima de elementos que estarán en la pila en un momento dado, debe usar un std::vector en el que reserve la capacidad por adelantado. Incluso si no conoce la capacidad, para una pila debe usar std::vector que crecerá varias veces (mayor costo que un vector preasignado al crecer) pero nunca se encogerá, por lo que después de un tiempo dejará de asignar memoria.

El problema con el rendimiento es que std::deque atribuirá bloques, según sea necesario, y desasignar ellos cuando ya no son necesarios (siguiendo alguna estrategia), por lo que si usted llena y despejar el std::deque A menudo habrá asignaciones y cancelaciones de asignación de forma continua.

+0

Tienes razón. El rendimiento mejora de 15 sa 6 s, pero sigue siendo muy lento cuando se compara con una clase simple de "pila" como la que hago. Sé que mi versión no tiene manejo de errores u otras características pero es más rápida y también permite el acceso aleatorio debido a miembros públicos ... – icl7126

+2

@klerik: ¿Qué compilador y banderas está usando? La única razón por la que un 'std :: vector' sería más lento que su código artesanal es si está utilizando iteradores marcados, o compilando en modo de depuración sin ninguna función en línea. Las pruebas de rendimiento no son triviales, tienes que entender realmente lo que estás haciendo y las implicaciones de la prueba, de lo contrario, es posible que no puedas interpretar los resultados de manera apropiada. –

+0

Estaba usando VS2010, todos los benchmarks se ejecutaban en Release build con los parámetros/O2,/Oi,/Ot,/Oy-,/GL y/Ob2, por lo que cualquier función adecuada debería estar en línea y todas las optimizaciones de código deberían funcionar. Estaba usando la depuración solo para descubrir la capacidad de la pila. Pero estaba usando iteradores marcados, así que lo apagué en este momento, pero parece que no importa en absoluto. – icl7126

21

Estás viendo manzanas y naranjas. La dequeue tiene DOBLE SENTIDO, lo que requiere que sea bastante diferente internamente que la simple pila que has implementado. Intente ejecutar su punto de referencia contra un std::stack<int, std::vector<int> > en su lugar y vea cómo lo hace. std :: stack es un adaptador de contenedor, y si usa un vector como contenedor subyacente, debería ser casi tan rápido como la implementación local.

El único inconveniente es que la implementación de std :: stack no tiene una forma de preestablecer el tamaño, por lo que en situaciones donde SEPA cuál debe ser el tamaño máximo, será una un poco más lento inicialmente, ya que tiene que reasignarse varias veces. Después de eso, será más o menos lo mismo.

+0

Mismo punto de referencia usando el vector como contenedor para la pila : 6.19099 6.1834 6.19315 6.19841. El índice de referencia coloca solo 100 números en la pila al mismo tiempo, por lo que la reasignación no debería ser un problema tan grande. – icl7126

+0

Haga una iteración con std :: stack (para establecer el tamaño) y _then_ haga el ciclo temporizado (usando la misma pila precremada). ¿Cómo se ve ahora? – Useless

+0

Ok, hice un ciclo y agregué 100 números, luego otro ciclo y los eliminé (justo antes de la línea 'Temporizador HRTimer'). Utilizando el depurador verifico la capacidad de la pila después de estos ciclos y era 141 y permanece 141 todo el tiempo (porque solo se agregan 100 elementos al mismo tiempo) y los resultados son peores: 6.65823. Hice algunos experimentos, así que cambio los bucles para llegar a 1000 y luego los resultados fueron 6.34 y luego lo cambié a 1000000 y los resultados fueron 6.35 (la capacidad fue 1049869). No sé por qué cambian los resultados al cambiar el espacio de capacidad no utilizada (me refiero a la diferencia entre 100 y 1000). – icl7126

Cuestiones relacionadas