2011-11-16 12 views
8

He encontrado un comportamiento extraño de std :: set.std :: establecido rápido y lento, ¿qué está pasando?

Aquí está el código:

#include <cstdio> 
#include <windows.h> 
#include <stdlib.h> 

#include <vector> 
#include <set> 

using namespace std; 

int main(int argc, char *argv[]) 
{ 
    set<int> b[100]; 

    for (int o=0; o<10; o++) 
    { 
     int tt = GetTickCount(); 

     for (int i=0; i<5000000; i++) 
     { 
      b[o].insert(i); 
     } 

     tt = GetTickCount() - tt; 

     b[o].clear(); 

     printf("%d\n", tt); 
    } 

    return 0; 
} 

estoy corriendo en Windows XP.

Aquí está la parte interesante: este primer tiempo impreso es de aproximadamente 3500 ms, ¡mientras que todos los siguientes son más de 9000 ms! ¿Por qué está sucediendo eso?

Ah, y esto solo ocurre en la versión de lanzamiento (optimización de -O2).

No ocurre en Linux (después de cambiar el código para compilar allí).

Una cosa más: cuando lo ejecuto durante el perfilado con Intel VTune, siempre lleva unos 3000 ms, así es como debería ser.

ACTUALIZACIÓN: Aquí hay un código nuevo:

#include <cstdio> 
#include <windows.h> 
#include <stdlib.h> 

int main(int argc, char *argv[]) 
{ 
const int count = 10000000; 
int **a = new int*[count]; 

for (int o=0; o<10; o++) 
{ 
    int ttt = GetTickCount(); 

    for (int i=0; i<count; i++) 
    { 
     a[i] = new int; 

     *a[i] = i; 
    } 

    int ttt2 = GetTickCount(); 

    for (int i=0; i<count; i++) 
    { 
     int r1 = rand() * 10000 + rand(); 
     int r2 = rand() * 10000 + rand(); 
     r1 = r1%count; 
     r2 = r2%count; 

     int *e = a[r1]; 
     a[r1] = a[r2]; 
     a[r2] = e; 
    } 

    int ttt3 = GetTickCount(); 

    for (int i=0; i<count; i++) 
    { 
     delete a[i]; 
    } 

    int ttt4 = GetTickCount(); 

    printf("%d %d\n", ttt2-ttt, ttt4-ttt3); 
} 

return 0; 
} 

Este es el mismo problema. Lo que sucede es que asigno muchos objetos pequeños y luego los elimino en orden aleatorio, por lo que es similar a como se ve en std :: set. Este es un problema de administración de memoria de Windows. Realmente no puede manejar bien muchos pequeños allocs y elimina.

+0

Esto probablemente esté relacionado con algunos detalles de implementación de su biblioteca/plataforma estándar.Podría tratar de ejecutarlo en un generador de perfiles y verificar dónde se está gastando el tiempo. Puede haber muchas cosas pasando, desde las diferencias en el esquema de asignación de los pases iniciales y consecutivos (que lanzó la memoria) a casi cualquier otra cosa. También tenga en cuenta que debe usar -O3 para el rendimiento. –

+1

Quizás esté relacionado con la programación del hilo del sistema. Su hilo no es el único que necesita tiempo de procesador. Puede usar 'GetThreadTimes' para ver cuánto tiempo de procesador realmente consumió su hilo – valdo

+0

Su mejor opción podría ser mirar el código ensamblado generado con' -O2' para ver si hay algo allí que pueda explicar este comportamiento. – NPE

Respuesta

6

No puedo explicar exactamente por qué sucede esto, pero podría proponer una solución. Pude reproducir esto en mi PC cuando ejecuto la compilación de lanzamiento en el depurador (con F5). Cuando ejecuto la compilación desde la línea de comandos o con Ctrl-F5 no obtengo ese tipo de comportamiento.

Esto tiene algo que ver con el montón de depuración que está activado de forma predeterminada cuando se inicia bajo el depurador. Se describe en gran detalle here. Para evitar que esto ocurra,

  1. Ejecutar desde la línea de comandos o con Ctrl-F5 (Depurar -> Iniciar sin depurar).
  2. Vaya a Proyecto -> Propiedades -> Depuración -> Entorno y agregue _NO_DEBUG_HEAP=1.

Si tuviera que adivinar, diría que tiene algo que ver con la implementación del seguimiento de asignación de memoria en el tiempo de ejecución de Windows/VS. Probablemente algunas listas internas se llenan y reasignan o algo más en esta línea.

+0

Esta opción _NO_DEBUG_HEAP es nueva para mí. Buena respuesta. – Dialecticus

+0

Siempre lo ejecuto sin el depurador, por lo que no es el problema. Lo probé con VS2010 y funciona bien (sin anomalías), pero en VS2008 funciona mal. PERO, el segundo código que proporcioné funciona mal en VS2008 y en VS2010. Nota para todos: La implementación VS2010 de la biblioteca STL (especialmente del conjunto) es MUCHA más que VS2008, que no cumple con la especificación STL oficial en términos de complejidad (por ejemplo: inserción del vector de elementos en el conjunto, olvidaron para usar insert (end(), element) (que puede ser O (1) y usan insert (elemento) (siempre O (logN)) – tweety3d

0

Creo que std::set se implementa como un árbol de búsqueda binaria. Como está aumentando i por 1 cada vez, básicamente está creando un escenario adversarial (peor caso) para este tipo de estructura de datos (se requiere reequilibrar el árbol en casi todos los insertos).

Además, tiene 50 millones de insertos, por lo que se espera un tiempo, aunque no creo que sea de 5 ms.

Además, me gustaría hacer su "claro" después de imprimir su tiempo, ya que no veo la razón por la que compararía tanto la inserción como la eliminación de elementos.

+0

En realidad, borrar todo el conjunto es de 3 a 10 veces más lento que agregar todo. Esto sucede porque el administrador de memoria de Windows lo hace tan lento. – tweety3d

+0

@ tweety3d ¿Alguna idea de por qué es tan lento? Imagino que la eliminación sería un tiempo lineal independientemente, ¿Windows abstrae la estructura de datos subyacente? – Alex

+0

Probablemente porque el administrador de memoria está optimizado para hacer allocs rápidos, por lo que todo el proceso de reorganización de sus punteros/listas de iteración/unión de espacios libres se realiza en el separador de direcciones. – tweety3d

Cuestiones relacionadas