Fuga de memoria en C++

maRc

Tengo un programa en C++ que se encarga de segmentar un texto sin espacios, calculando cual es la solución que da la máxima probabilidad, usando la frecuencia de aparición de las palabras.

Tengo dos versiones del mismo, una en el que la frecuencia se almacena en un map de las stl, y en la otra se emplea un trie (la implementación la he extraido de una web).

Map: http://pastebin.ca/489992
Trie: http://pastebin.ca/490006

El código es idéntico, lo único que cambia es la forma de hacer la consulta al diccionario.

Con el trie funciona perfectamente. En cambio, el map empieza a utilizar toda la memoria que puede (ayer lo paré cuando llevaba consumidos dos gigas).

El problema sucede tanto en Windows (Visual Studio) como en Linux (gcc).

¿Alguna idea?

r2d2rigo

Que formato tiene el tipo de dato Trie que utilizas en el segundo codigo? Lo digo mas que nada porque en el que falla utilizas

map <string,double>&dic

Y la verdad, que hacer un map de <string,double> no me suena bien. Es solo un presentimiento, por las caracteristicas "especiales" del string :P

maRc

Un trie es una estructura especial destinada principalmente a guardar diccionarios en los que la clave es una cadena de texto.

Se implementa en forma de árbol, y en cada nodo se guarda un caracter. En este caso, se guarda como un char, y para buscar una cadena, le pasas un char *.

Aquí tienes más info: http://en.wikipedia.org/wiki/Trie

El texto a segmentar se lo paso con t.

Lo curioso es que si le paso un texto que es un pequeño párrafo repetido muchas veces (por ejemplo, 500 caracteres, repetidos hasta que ocupan un mega), no consume tanta memoria como si fuera un texto más largo, aunque solo sean 17k.

Me parece que vamos a tener que cambiar el map y hacerlo con char, en lugar de strings.

PD: Acabo de ver que el texto lo pasamos por valor, no por referencia. Ya lo cambiaré mañana, pero esto seguro que no es.

L

Sring de STL como tal, es un objeto bastante completo... La solución esta en el char*.

map <const char*, float> polla;

y entonces lo de siempre:

polla[wrd]+=1;

donde tendriamos char *wrd;

Dos cosas:

  • malloc en wrd, aunque no sea necesario. Pruebalo si sigue dandote problemas, tiene la cosa de que forzarás el posicionado de memoria y quizas evites el segmentado o los memory-leaks.

wrd = (*char)malloc(sizeof(char));

  • ojo con el polla[wrd]+=1;. A veces hace ciertas gilipolleces. Si no te cuadrán las cuentas, olvida la vaguez y hechate al

polla[wrd] = polla[wrd] + 1;

;)

Ahm y ahora no estoy seguro si seria polla[*wrd]...
Hace como 4 meses ahora que no toco los map... :S
Donde esten los putos hastable que se quite lo bailao...

No se si me quedo con Java o con C/C++ xD

EDIT:

Por cierto!
Otra cosa que veo ahora:

esto es asi?
map <string,double>&dic

creo que es una xaladura poner &dic... No te da ni siquiera warning? :S me extraña muchisimo... No debería ni compilarte!

EDIT2:

xDD calla calla... que no habia mirado el codigo. Ok si lo pasas por referencia sta bien...
Mira, esto te ocupa una burrada porque realmente lo debes tener declarado todo enterito y "en local" arriba, y aki lo vas pasando por referencia a medida que se llena... No solo tiene que ir lento a gran cabal de datos sino que se te llena la memoria que da gusto! Todo dinámico nene, o no te comas la olla y ponto "global", otra solucion un poco barriobajera. xD
O cambia el sistema de pasado referenciado y hazlo con pointers.

typedef map <string,double> Palabras;
.
.
.

#
double segmentationLogOptimMap(string t, Palabras *dic, string &ret, int maxCadena){

...

... double aux = min(P[j], P + *dic[t.substr(i, j - i)]);

...

}

r2d2rigo

Ahora que releo, la verdad que mi pregunta sobre el Trie es BASTANTE estupida. No hay otra forma de implementarlo que no sea un char en cada nivel del diccionario :P (y pensar que lo di en clase hace apenas un mes... tendria que poner mas atencion a lo que explican!)

Yo apostaria por lo que dice Lightworker, pasar de las string y almacenarlo todo con punteros a char; guardas tambien un flag en cada nivel para indicar si es fin de palabra o no, y ya tienes el diccionario.

maRc

Esta mañana, mi compañero de prácticas (el programa es para una práctica de la universidad, que no lo había dicho :P), ha hablado con el profesor y lo han solucionado.

Todavía no me ha pasado el código, en cuento pueda os lo pego aquí, por si alguna vez os pasa lo mismo ;).

Parece ser que la consulta al map está mal hecha, y que a la vez que consulta inserta nuevos elementos en este, supongo que cuando consulta un elemento que no existe.

Lightworker, en teoría al pasar por referencia es como pasar con un puntero, pero luego no tienes que dereferenciar (o lo que sea, que me lío con estos nombrecitos xD), ¿no? De todas formas, aunque eso sea una mejora en el programa, que se agradece, el problema no estaba ahí.

Por otro lado, la estructura del programa es:
1- Leer el diccionario de frecuencias.
2- Leer el texto a segmentar.
3- Segmentar según el método que elijas. Aquí es donde si utilizabamos el map se comía toda la memoria. En cambio, con el trie segmenta, ocupa la memoria que le toca y al salir de la función la libera correctamente.

Edit:

Ya tengo la solución. El problema es que al hacer la comprobación
if (dic[t.substr(i, j - i)] != 0)
si no existe la clave en el diccionario, crea su entrada. En el bucle la mayor parte de las consultas al diccionario son de claves que no existen, por lo que se crean muchas (muchísimas) entradas nuevas y se desboca el consumo de memoria.
Por eso si el texto que le pasaba era uno pequeño repetido varias veces (de menos de un KB hasta ser un mega) , no consumía casi memoria, pero si le pasaba uno sin repetir, aunque solo fuera de unos pocos kilobytes.

Al final, el método correcto es primero buscar con un find la clave, y si existe ya consultar su valor. Así:
if (dic.find(s) != dic.end())
double aux = max(P[j], P * dic);

Usuarios habituales

  • maRc
  • r2d2rigo
  • Lightworker