2011-04-15 17 views
5

Tengo un problema con la gestión de memoria con std :: string.std :: cadena de gestión de memoria

Tengo una aplicación - servidor de subprocesos múltiples con subprocesos separados (he necesitado unirme a ellos, ellos harán el trabajo y saldrán) y descubrí que después de un tiempo el uso de la memoria es bastante alto. He comenzado a experimentar dónde está el problema y he creado el programa de prueba que demuestra el problema

#include <iostream> 

#include <string> 
#include <pthread.h> 

pthread_t   thread[100]; 

using namespace std; 

class tst { 
    public: 
     tst() { 
      //cout << "~ Create" << endl; 
     } 
     ~tst() { 
      //cout << "~ Delete" << endl; 
     } 
     void calc() { 
      string TTT; 
      for (int ii=0; ii<100000; ii++) { 
       TTT+="abcdenbsdmnbfsmdnfbmsndbfmsndb "; 
      } 
     } 
}; 

void *testThread (void *arg) { 
    int cnt=*(int *) arg; 
    cout << cnt << " "; 
    tst *TEST=new tst; 
    TEST->calc(); 
    delete TEST; 
    pthread_exit((void *)0); 
} 

int main (int argc, char * const argv[]) { 

cout << "---------------------------------------------------" << endl; 
sleep(5); 

    for (int oo=0; oo<100; oo++) { 
     pthread_create(&thread[oo], NULL, testThread, &oo); 
     pthread_detach(thread[oo]); 
    } 
    cout << endl; 
    cout << "---------------------------------------------------" << endl; 

    sleep(5); 

    for (int oo=0; oo<100; oo++) { 
     pthread_create(&thread[oo], NULL, testThread, &oo); 
     pthread_detach(thread[oo]); 
    } 
    cout << endl; 
    cout << "---------------------------------------------------" << endl; 

    sleep(5); 
    exit(0); 
} 

después de la primera "---" el uso de la memoria es 364KB, después de la segunda de su 19 MB, después tercero es 33,5 MEGABYTE. también hay 1 cosa extraña: cada ejecución muestra un uso diferente de la memoria pero, en general, el último uso de la memoria es aproximadamente un 50% más que después del segundo "---".

esperaba que si se eliminaba la clase TEST (tst), la cadena también liberaría su memoria - encontré que los hilos no harían eso - es por eso que estoy creando un nuevo tst, ejecutándolo y luego borrar.

en mi programa esta causando un gran problema porque estoy usando no pocas cadenas en cada hilo y después de un tiempo el uso de la memoria es más ;-(concierto

¿Hay alguna opción de la forma de 'vacío' el ?. la memoria después de cadena

he intentado TTT = "" o TTT.clear() sin ningún cambio

... i necesidad de utilizar hilos - que se ejecuta en la máquina donde multicpu hilos es el única opción para usar es 'potencia total' (como sé)

+0

¿Cómo mide uso de la memoria? –

+0

@Doug T .: Monitor de actividad - Mac OSX –

+0

@tominko, me pregunto si el sistema operativo no es muy agresivo para reclamar la memoria virtual que asigna. Es posible que se le asigne una cierta cantidad, pero que en realidad no la use. –

Respuesta

2

s tring debe ser desasignado tan pronto como se cierre la función calc porque es una variable local.

¿Qué estás usando para rastrear el uso de tu memoria? Es posible que esté viendo la fragmentación de la memoria en lugar de aumentar el uso de la memoria. Aquí hay una herramienta que puede demostrar si su memoria se está fragmentando: http://hashpling.org/asm/

No estoy familiarizado con pthreads, así que no puedo decir si podría haber un problema allí con los hilos u otra cosa relacionada con la API no ser desasignado

+0

he esperado que después de salir del ámbito se borrará pero no ;-(... el programa no puedo usar; no tengo máquina de Windows ;-( –

6

La memoria utilizada por la cadena en calc() se liberará después de que finalice la función calc. Eliminar cada objeto tst no tiene nada que ver con eso porque no hay miembros de clase.

Lo que creo que está viendo es que hay potencialmente 200 subprocesos en ejecución. Como separa todos los hilos y nunca se une a ellos, no tiene ninguna garantía de que los primeros 100 realmente hayan terminado antes de comenzar los siguientes 100.

Además, debido a que está extendiendo estas cuerdas una y otra vez añadiéndolas en el Al mismo tiempo, la asignación de memoria en otros hilos, me imagino que la fragmentación del montón es realmente realmenterealmente malo. Digamos que un hilo acaba de liberar memoria para una cadena de 256 bytes. Los otros subprocesos se han ejecutado y ninguno necesita una cadena de 256 bytes. Ese espacio ahora solo se desperdicia, pero todavía se asigna al programa.

Algunas opciones que podrían ayudar:

  • .reserve(your_largest_expected_string_size) Uso antes de colocar los datos en la cadena. Esto ayudará a evitar el problema de fragmentación porque casi todas las cadenas tendrán el mismo tamaño.
  • Una clase de asignador de cadena personalizada. Podrías escribir el tuyo.
  • Puede reemplazar su asignador de montón con algo como jemalloc o tcmalloc.
  • Para una aplicación de servidor que maneje clientes que van y vienen, puede obtener un gran rendimiento utilizando un grupo de memoria por cliente. Asigne un grupo de memoria grande en un bloque. Haga que todas las asignaciones utilicen el grupo (a través de los parámetros del asignador STL) y cuando el cliente salga libre del grupo completo.
+0

no hay ningún problema con los hilos - si elimino el código del hilo (déjalo vacío) la memoria está bien. La cadena larga que he creado es solo para prueba, porque si solo hay una cadena corta, la diferencia es demasiado pequeña. En el programa real hay (habrá) a veces 1000 solicitudes por segundo, y algunas veces ninguna durante unos minutos, pero después de un tiempo el programa asigna más gigas de memoria ... la gestión de subprocesos lo he ordenado allí por global bool array que se establece en falso si termina el hilo y luego la siguiente solicitud puede usar su variable ... el problema es que no puedo borrar la memoria después de la cadena –

+2

@tominko: los subprocesos hacen que la fragmentación del montón sea mucho peor, como traté de ilustrar en mi respuesta. Un grupo de asignadores de memoria personalizada por subproceso o un asignador global ayuda a tu problema –

+0

De manera divertida, la mayor parte del problema también se puede reducir al usar menos hilos y unirlos. –

2

Dependiendo de la implementación de new y delete (o libre() y malloc()) su memoria podría no devolverse al SO después de una eliminación/libre. No se requiere un asignador de memoria para hacerlo. La memoria aún puede ser reclamada por una subsiguiente nueva, o después de una desfragmentación de memoria interna o recolección de basura, el uso de la memoria puede disminuir nuevamente.

+0

malloc, realloc y libre funcionan bien y veo la diferencia directamente en el monitor de actividad, pero el 'método * char' - he hecho en otra cadena de clase de prueba no incluye las funciones que necesito que están en std :: string –

5

Sospecho que está viendo un problema con la fragmentación de la memoria, en lugar de una pérdida de memoria, per se. Como no está esperando la salida de los subprocesos, tiene hasta 200 subprocesos que intentan asignar memoria al mismo tiempo. A esto se agrega la estrategia de asignación de memoria predeterminada para std::string, que dobla la asignación actual cada vez que necesita crecer, y probablemente esté masticando su espacio de direcciones con bastante rapidez.

Una cosa muy simple que puede hacer para ayudar a mitigar el problema es preasignar la memoria para las cadenas usando reserve(). En este caso, las cuerdas son (31 * 100.000) + 1 bytes de longitud, por lo que podría modificar calc() de la siguiente manera:

string TTT; 

// We know how big the string will get, so pre-alloc memory for it to avoid the 
// inefficiencies of the default allocation strategy as the string grows. 

TTT.reserve(3100001); 

for (int ii=0; ii<100000; ii++) { 
    TTT+="abcdenbsdmnbfsmdnfbmsndbfmsndb "; 
} 

Esto sería asignar la memoria para las cadenas de una vez, en un solo bloque contiguo, y evitar gran parte de la fragmentación que estás sufriendo ahora. Una prueba rápida de este cambio en Valgrind muestra que el código original tiene asignaciones de aproximadamente 1,5 GB en total durante la vida útil del proceso; pero la versión modificada tiene asignaciones de aproximadamente 620 MB en total.

También vale la pena señalar que Valgrind hizo no revelar cualquier fuga de memoria. Siempre es una buena idea probarlo si cree que tiene un problema de memoria en su programa: Valgrind home.

+0

el problema no es la asignación, incluso si no sé el tamaño máximo de la cadena si la estoy creando, tengo otra versión en la que estoy usando la clase "mi cadena", que es char * con malloc y gratuita, que funciona perfectamente, pero necesito usar las funciones de std :: string como find_first_not_of ... (que llevará bastante tiempo implementar en mi clase de cadenas) - lo que necesito es limpiar después de la cadena de alguna manera –

+0

@tominko: no olvide que si tiene una cadena en un búfer char las viejas funciones de cadena C aún funcionan. Podría '#include ' y usar 'strcspn' para reemplazar' find_first_not_of'. –

+0

@Zan Lynx: creo que tengo que deshacerme de std :: string ;-( –

0

problema resuelto poniendo 'rutina enhebrada' en otro ámbito - if (1) {..} porque como he encontrado en algunos foros - hilos que no llaman destructores en la salida en esta ocasión (sin agregar bloque si) cada subproceso creado asignará memoria para 'int cnt' y nunca se desasigna

void *testThread (void *arg) { 
    if (1) { 
     int cnt=*(int *) arg; 
     cout << cnt << " "; 
     tst *TEST=new tst; 
     TEST->calc(); 
     delete TEST; 
    } 
    pthread_exit((void *)0); 
} 

... he encontrado algunos errores poco más en mi proyecto, pero este fue el tema principal

+2

Puede simplemente agregar un alcance con '{}'. No necesita la parte 'if (1)'. – jalf

+0

se olvida de aceptar su respuesta. hágalo, porque se moverá a la parte superior y ayudará a los demás – Nick