delphi memory-management delphi-2009 utf-16 fastmm

¿Por qué el exceso de memoria para cuerdas en Delphi?



memory-management delphi-2009 (8)

Estoy leyendo en un archivo de texto grande con 1,4 millones de líneas de 24 MB de tamaño (promedio de 17 caracteres por línea).

Estoy usando Delphi 2009 y el archivo es ANSI pero se convierte a Unicode luego de leerlo, por lo que puede decir que el texto una vez convertido tiene un tamaño de 48 MB.

(Editar: encontré un ejemplo mucho más simple ...)

Estoy cargando este texto en una simple StringList:

AllLines := TStringList.Create; AllLines.LoadFromFile(Filename);

Descubrí que las líneas de datos parecen llevar mucha más memoria que sus 48 MB.

De hecho, usan 155 MB de memoria.

No me importa que Delphi use 48 MB o incluso 60 MB, lo que permite un poco de sobrecarga de administración de memoria. Pero 155 MB parece excesivo.

Esto no es un error de StringList. Intenté anteriormente cargar las líneas en una estructura de registro, y obtuve el mismo resultado (160 MB).

No veo ni entiendo qué podría estar causando que Delphi o el administrador de memoria FastMM usen 3 veces la cantidad de memoria necesaria para almacenar las cadenas. La asignación de un montón no puede ser tan ineficiente, ¿verdad?

Lo he depurado y lo he investigado todo lo que puedo. Cualquier idea sobre por qué esto podría estar pasando, o ideas que podrían ayudarme a reducir el uso excesivo serían muy apreciadas.

Nota: Estoy usando este archivo "más pequeño" como ejemplo. Estoy tratando de cargar un archivo de 320 MB, pero Delphi está pidiendo más de 2 GB de RAM y se está quedando sin memoria debido a este exceso de cadenas.

Addenum: Marco Cantu acaba de publicar un Libro Blanco sobre Delphi y Unicode . Delphi 2009 ha aumentado la sobrecarga por cadena de 8 bytes a 12 bytes (más tal vez 4 más para el puntero real a la cadena). Un extra de 16 bytes por 17x2 = línea de 34 bytes agrega casi el 50%. Pero veo más del 200% de gastos generales. ¿Cuál podría ser el 150% extra?

¡¡Éxito!! Gracias a todos por sus sugerencias. Todos ustedes me hicieron pensar. Pero tendré que darle crédito a Jan Goyvaerts por la respuesta, ya que me preguntó:

... ¿por qué estás usando TStringList? ¿Debe el archivo realmente almacenarse en la memoria como líneas separadas?

Eso me llevó a la solución de que, en lugar de cargar el archivo de 24 MB como una StringList de 1,4 millones de líneas, puedo agrupar mis líneas en grupos naturales que mi programa conoce. Así que esto dio como resultado 127,000 líneas cargadas en la lista de cadenas.

Ahora, cada línea promedia 190 caracteres en lugar de 17. La sobrecarga por línea StringList es la misma, pero ahora hay muchas menos líneas.

Cuando aplico esto al archivo de 320 MB, ya no se queda sin memoria y ahora se carga en menos de 1 GB de RAM. (Y solo toma unos 10 segundos cargar, ¡lo que es bastante bueno!)

Habrá un poco de procesamiento adicional para analizar las líneas agrupadas, pero no debería notarse en el procesamiento en tiempo real de cada grupo.

(En caso de que se lo pregunte, este es un programa de genealogía, y este puede ser el último paso que necesité para permitir que cargue todos los datos sobre un millón de personas en un espacio de direcciones de 32 bits en menos de 30 segundos. Todavía tengo un buffer de 20 segundos para jugar y agregar los índices a los datos que se requerirán para permitir la visualización y edición de los datos.


Utilizo Delphi 2009 y el archivo es ANSI pero se convierte a Unicode luego de leerlo, por lo que puede decir que el texto una vez convertido tiene un tamaño de 48 MB.

Lo siento, pero no entiendo esto en absoluto. Si necesita que su programa sea Unicode, seguramente el archivo que es "ANSI" (debe tener algún juego de caracteres, como WIN1252 o ISO8859_1) no es lo correcto. Primero lo convertiría en UTF8. Si el archivo no contiene ningún carácter> = 128, no cambiará nada (incluso tendrá el mismo tamaño), pero está preparado para el futuro.

Ahora puede cargarlo en cadenas UTF8, lo que no duplicará su consumo de memoria. Al instante, la conversión de las pocas cadenas que pueden verse en la pantalla al mismo tiempo en la cadena Delphi Unicode será más lenta, pero dado el espacio de memoria más pequeño, su programa funcionará mucho mejor en sistemas con poco (libre) memoria.

Ahora, si su programa aún consume demasiada memoria con TStringList, siempre puede usar TStrings o incluso IStrings en su programa, y ​​escribir una clase que implemente IStrings o herede TStrings y no mantenga todas las líneas en la memoria. Algunas ideas que te vienen a la mente:

  1. Lea el archivo en un TMemoryStream y mantenga una serie de punteros a los primeros caracteres de las líneas. Devolver una cadena es fácil entonces, solo necesitas devolver una cadena adecuada entre el inicio de la línea y el inicio de la siguiente, con el CR y NL despojados.

  2. Si esto todavía consume demasiada memoria, reemplace el TMemoryStream con un TFileStream, y no mantenga una matriz de punteros de caracteres, pero se iniciará una matriz de desplazamientos de archivos para la línea.

  3. También podría usar las funciones de la API de Windows para archivos mapeados en memoria. Eso le permite trabajar con direcciones de memoria en lugar de desplazamientos de archivos, pero no consume tanta memoria como la primera idea.


¿Confía en Windows para decirle cuánta memoria está usando el programa? Es notorio por exagerar la memoria utilizada por una aplicación Delphi.

Aunque veo bastante uso de memoria extra en tu código.

Su estructura de registro es de 20 bytes; si hay uno de esos registros por línea, está buscando más datos para los registros que para el texto.

Además, una cadena tiene una sobrecarga de 4 bytes inherente, otro 25%.

Creo que hay una cierta cantidad de granularidad de asignación en el manejo del montón de Delphi, pero no recuerdo qué es en este momento. Incluso a 8 bytes (dos punteros para una lista vinculada de bloques libres) estás viendo otro 25%.

Tenga en cuenta que ya estamos a más de un 150% de aumento.


¿Estás compilando el programa con fuentes de FastMM de sourceforge y con FullDebugMode definido? En ese caso, FastMM realmente no está liberando bloques de memoria no utilizados, lo que explicaría el problema.


De forma predeterminada, TStringList de Delphi 2009 lee un archivo como ANSI, a menos que haya una Marca de orden de bytes para identificar el archivo como algo más, o si proporciona una codificación como el segundo parámetro opcional de LoadFromFile.

Entonces, si está viendo que TStringList está ocupando más memoria de la que cree, entonces algo más está sucediendo.


¿Por qué estás cargando esa cantidad de datos en una TStringList? La lista en sí tendrá algunos gastos generales. Tal vez TTextReader podría ayudarte.


¿Qué pasa si haces que tu registro original use AnsiString? ¿Eso lo corta a la mitad inmediatamente? El hecho de que Delphi adopte de manera predeterminada UnicodeString no significa que deba usarlo.

Además, si conoce exactamente la longitud de cada cadena (dentro de un carácter o dos), entonces podría ser mejor usar cuerdas cortas incluso y reducir algunos bytes más.

Tengo curiosidad de saber si podría haber una mejor manera de lograr lo que estás tratando de hacer. Cargar 320 MB de texto en la memoria podría no ser la mejor solución, incluso si puede bajarlo solo requerirá 320 MB


Me pediste personalmente que respondiera tu pregunta aquí. No sé la razón exacta por la que está viendo un uso de memoria tan alto, pero debe recordar que TStringList hace mucho más que solo cargar su archivo. Cada uno de estos pasos requiere memoria que puede provocar la fragmentación de la memoria. TStringList necesita cargar su archivo en la memoria, convertirlo de Ansi a Unicode, dividirlo en una cadena para cada línea y rellenar esas líneas en una matriz que se reasignará muchas veces.

Mi pregunta para ti es por qué estás usando TStringList? ¿Debe el archivo realmente almacenarse en la memoria como líneas separadas? ¿Vas a modificar el archivo en la memoria, o solo mostrar partes de él? Mantener el archivo en la memoria como una gran porción y escanear todo con expresiones regulares que coincidan con las partes que desea será más eficiente en cuanto a la memoria que el almacenamiento de líneas separadas.

Además, ¿el archivo completo debe convertirse a Unicode? Si bien su aplicación es Unicode, su archivo es Ansi. Mi recomendación general es convertir la entrada de Ansi a Unicode lo antes posible, ya que al hacerlo se ahorran ciclos de CPU. Pero cuando tienes 320 MB de datos Ansi que permanecerán como datos Ansi, el consumo de memoria será el cuello de botella. Intente mantener el archivo como Ansi en la memoria, y solo convierta las partes que se mostrarán al usuario como Ansi.

Si el archivo de 320 MB no es un archivo de datos del que está extrayendo determinada información, sino un conjunto de datos que desea modificar, considere convertirlo en una base de datos relacional y deje que el motor de la base de datos se preocupe por cómo administrar el enorme conjunto de datos con RAM limitada


Parte de esto podría ser el algoritmo de asignación de bloques. A medida que su lista crece, comienza a aumentar la cantidad de memoria asignada en cada fragmento. No lo he visto en mucho tiempo, pero creo que va a duplicar la cantidad asignada por última vez cada vez que se queda sin memoria. Cuando comienza a tratar con listas tan grandes, sus asignaciones también son mucho más grandes de lo que finalmente necesita.

EDITAR- Como señaló lkessler, este aumento es en realidad solo del 25%, pero aún debe considerarse como parte del problema. si está un poco más allá del punto de inflexión, podría haber un enorme bloque de memoria asignado a la lista que no se está utilizando.