Escenario: más de 1,5 GB de texto y archivos csv que necesito procesar matemáticamente. Intenté usar SQL Server Express, pero cargar la información, incluso con la importación BULK, lleva mucho tiempo, e idealmente necesito tener todo el conjunto de datos en memoria, para reducir el IO del disco duro.¿Por qué no puedo aprovechar 4 GB de RAM en mi computadora para procesar menos de 2 GB de información en C#?
Hay más de 120,000,000 de registros, pero incluso cuando intento filtrar la información a una sola columna (en memoria), mi aplicación de consola C# está consumiendo ~ 3.5GB de memoria para procesar solo 125MB (700MB actualmente read-in) de texto.
Parece que las referencias a las cadenas y matrices de cadenas no están siendo recopiladas por el GC, incluso después de establecer todas las referencias a los ID identificables nulos y encapsulantes con la palabra clave using.
Creo que el culpable es el método String.Split() que está creando una nueva cadena para cada valor separado por comas.
Puede sugerir que ni siquiera debería leer las columnas innecesarias * en una matriz de cadenas, pero eso pasa por alto: ¿Cómo puedo colocar este conjunto completo de datos en la memoria, por lo que puedo procesarlo en paralelo en DO#?
Pude optimizar los algoritmos estadísticos y coordinar las tareas con un sofisticado algoritmo de programación, pero esto es algo que esperaba hacer antes de tener problemas de memoria, y no por eso.
He incluido una aplicación de consola completa que simula mi entorno y debería ayudar a replicar el problema.
Cualquier ayuda es apreciada. Gracias por adelantado.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace InMemProcessingLeak
{
class Program
{
static void Main(string[] args)
{
//Setup Test Environment. Uncomment Once
//15000-20000 files would be more realistic
//InMemoryProcessingLeak.GenerateTestDirectoryFilesAndColumns(3000, 3);
//GC
GC.Collect();
//Demostrate Large Object Memory Allocation Problem (LOMAP)
InMemoryProcessingLeak.SelectColumnFromAllFiles(3000, 2);
}
}
class InMemoryProcessingLeak
{
public static List<string> SelectColumnFromAllFiles(int filesToSelect, int column)
{
List<string> allItems = new List<string>();
int fileCount = filesToSelect;
long fileSize, totalReadSize = 0;
for (int i = 1; i <= fileCount; i++)
{
allItems.AddRange(SelectColumn(i, column, out fileSize));
totalReadSize += fileSize;
Console.Clear();
Console.Out.WriteLine("Reading file {0:00000} of {1}", i, fileCount);
Console.Out.WriteLine("Memory = {0}MB", GC.GetTotalMemory(false)/1048576);
Console.Out.WriteLine("Total Read = {0}MB", totalReadSize/1048576);
}
Console.ReadLine();
return allItems;
}
//reads a csv file and returns the values for a selected column
private static List<string> SelectColumn(int fileNumber, int column, out long fileSize)
{
string fileIn;
FileInfo file = new FileInfo(string.Format(@"MemLeakTestFiles/File{0:00000}.txt", fileNumber));
fileSize = file.Length;
using (System.IO.FileStream fs = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (System.IO.StreamReader sr = new System.IO.StreamReader(fs))
{
fileIn = sr.ReadToEnd();
}
}
string[] lineDelimiter = { "\n" };
string[] allLines = fileIn.Split(lineDelimiter, StringSplitOptions.None);
List<string> processedColumn = new List<string>();
string current;
for (int i = 0; i < allLines.Length - 1; i++)
{
current = GetColumnFromProcessedRow(allLines[i], column);
processedColumn.Add(current);
}
for (int i = 0; i < lineDelimiter.Length; i++) //GC
{
lineDelimiter[i] = null;
}
lineDelimiter = null;
for (int i = 0; i < allLines.Length; i++) //GC
{
allLines[i] = null;
}
allLines = null;
current = null;
return processedColumn;
}
//returns a row value from the selected comma separated string and column position
private static string GetColumnFromProcessedRow(string line, int columnPosition)
{
string[] entireRow = line.Split(",".ToCharArray());
string currentColumn = entireRow[columnPosition];
//GC
for (int i = 0; i < entireRow.Length; i++)
{
entireRow[i] = null;
}
entireRow = null;
return currentColumn;
}
#region Generators
public static void GenerateTestDirectoryFilesAndColumns(int filesToGenerate, int columnsToGenerate)
{
DirectoryInfo dirInfo = new DirectoryInfo("MemLeakTestFiles");
if (!dirInfo.Exists)
{
dirInfo.Create();
}
Random seed = new Random();
string[] columns = new string[columnsToGenerate];
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= filesToGenerate; i++)
{
int rows = seed.Next(10, 8000);
for (int j = 0; j < rows; j++)
{
sb.Append(GenerateRow(seed, columnsToGenerate));
}
using (TextWriter tw = new StreamWriter(String.Format(@"{0}/File{1:00000}.txt", dirInfo, i)))
{
tw.Write(sb.ToString());
tw.Flush();
}
sb.Remove(0, sb.Length);
Console.Clear();
Console.Out.WriteLine("Generating file {0:00000} of {1}", i, filesToGenerate);
}
}
private static string GenerateString(Random seed)
{
StringBuilder sb = new StringBuilder();
int characters = seed.Next(4, 12);
for (int i = 0; i < characters; i++)
{
sb.Append(Convert.ToChar(Convert.ToInt32(Math.Floor(26 * seed.NextDouble() + 65))));
}
return sb.ToString();
}
private static string GenerateRow(Random seed, int columnsToGenerate)
{
StringBuilder sb = new StringBuilder();
sb.Append(seed.Next());
for (int i = 0; i < columnsToGenerate - 1; i++)
{
sb.Append(",");
sb.Append(GenerateString(seed));
}
sb.Append("\n");
return sb.ToString();
}
#endregion
}
}
* se necesitarán Estas otras columnas y se accede tanto en forma secuencial y al azar a través de la vida del programa, así que leer desde el disco cada vez que hay una sobrecarga tremendamente exigente.
** Medio Ambiente Notas: 4 GB de memoria SDRAM DDR2 800, Core 2 Duo 2.5Ghz, .NET Runtime 3.5 SP1, Vista 64.
Además de las respuestas a continuación, me he dado cuenta de que utiliza la lista, que se basa en una matriz. Hasta donde yo sé, el tamaño de la matriz se duplica cada vez que alcanzas su capacidad actual. Así que esto podría ser una verdadera amenaza una vez que se alcanza un límite específico. –