2010-01-29 18 views
9

Mam narzędzie do porównywania 2 plików CSV, a następnie do każdej z 6 komórek. Zasadniczo, czyta się w plikach csv (używając szybkiego czytnika csv, kredyt: http://www.codeproject.com/KB/database/CsvReader.aspx), a następnie tworzy słownik dotyczący każdego pliku na podstawie kluczy dostarczonych przez użytkownika. Następnie przechodzę przez słowniki porównujące wartości i zapisuję wynikowy plik csv.Słownik C# i efektywne wykorzystanie pamięci

Podczas gdy płonie szybko, jest bardzo nieefektywny pod względem zużycia pamięci. Nie mogę porównać więcej niż 150 MB plików na moim pudełku z pamięcią fizyczną 3 GB.

Oto fragment kodu do odczytania oczekiwanego pliku. Na końcu tego fragmentu zużycie pamięci jest bliskie 500 MB od menedżera zadań.

// Read Expected 
long rowNumExp; 
System.IO.StreamReader readerStreamExp = new System.IO.StreamReader(@expFile); 
SortedDictionary<string, string[]> dictExp = new SortedDictionary<string, string[]>(); 
List<string[]> listDupExp = new List<string[]>(); 
using (CsvReader readerCSVExp = new CsvReader(readerStreamExp, hasHeaders, 4096)) 
{ 
    readerCSVExp.SkipEmptyLines = false; 
    readerCSVExp.DefaultParseErrorAction = ParseErrorAction.ThrowException; 
    readerCSVExp.MissingFieldAction = MissingFieldAction.ParseError; 
    fieldCountExp = readerCSVExp.FieldCount;     
    string keyExp; 
    string[] rowExp = null; 
    while (readerCSVExp.ReadNextRecord()) 
    { 
     if (hasHeaders == true) 
     { 
      rowNumExp = readerCSVExp.CurrentRecordIndex + 2; 
     } 
     else 
     { 
      rowNumExp = readerCSVExp.CurrentRecordIndex + 1; 
     } 
     try 
     { 
      rowExp = new string[fieldCount + 1];      
     } 
     catch (Exception exExpOutOfMemory) 
     { 
      MessageBox.Show(exExpOutOfMemory.Message); 
      Environment.Exit(1); 
     }     
     keyExp = readerCSVExp[keyColumns[0] - 1]; 
     for (int i = 1; i < keyColumns.Length; i++) 
     { 
      keyExp = keyExp + "|" + readerCSVExp[i - 1]; 
     } 
     try 
     { 
      readerCSVExp.CopyCurrentRecordTo(rowExp); 
     } 
     catch (Exception exExpCSVOutOfMemory) 
     { 
      MessageBox.Show(exExpCSVOutOfMemory.Message); 
      Environment.Exit(1); 
     } 
     try 
     { 
      rowExp[fieldCount] = rowNumExp.ToString(); 
     } 
     catch (Exception exExpRowNumOutOfMemory) 
     { 
      MessageBox.Show(exExpRowNumOutOfMemory.Message); 
      Environment.Exit(1); 
     } 
     // Dedup Expected       
     if (!(dictExp.ContainsKey(keyExp))) 
     { 
      dictExp.Add(keyExp, rowExp);       
     } 
     else 
     { 
      listDupExp.Add(rowExp); 
     }      
    }     
    logFile.WriteLine("Done Reading Expected File at " + DateTime.Now); 
    Console.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n"); 
    logFile.WriteLine("Done Creating Expected Dictionary at " + DateTime.Now); 
    logFile.WriteLine("Done Identifying Expected Duplicates at " + DateTime.Now + "\r\n");     
} 

Czy jest coś, co mogę zrobić, aby zwiększyć wydajność pamięci? Coś, co mogłem zrobić inaczej, aby konsumować mniej mermory?

Wszelkie pomysły są mile widziane.

Dzięki chłopaki za wszystkie opinie.

Wprowadziłem zmiany zgodnie z sugestią, aby zapisać indeks wiersza zamiast samego wiersza w słownikach.

Oto ten sam fragment kodu z nową implementacją.

// Read Expected 
     long rowNumExp; 
     SortedDictionary<string, long> dictExp = new SortedDictionary<string, long>(); 
     System.Text.StringBuilder keyExp = new System.Text.StringBuilder(); 
     while (readerCSVExp.ReadNextRecord()) 
     { 
      if (hasHeaders == true) 
      { 
       rowNumExp = readerCSVExp.CurrentRecordIndex + 2; 
      } 
      else 
      { 
       rowNumExp = readerCSVExp.CurrentRecordIndex + 1; 
      } 
      for (int i = 0; i < keyColumns.Length - 1; i++) 
      { 
       keyExp.Append(readerCSVExp[keyColumns[i] - 1]); 
       keyExp.Append("|"); 
      } 
      keyExp.Append(readerCSVExp[keyColumns[keyColumns.Length - 1] - 1]); 
      // Dedup Expected      
      if (!(dictExp.ContainsKey(keyExp.ToString()))) 
      { 
       dictExp.Add(keyExp.ToString(), rowNumExp); 
      } 
      else 
      { 
       // Process Expected Duplicates   
       string dupExp; 
       for (int i = 0; i < fieldCount; i++) 
       { 
        if (i >= fieldCountExp) 
        { 
         dupExp = null; 
        } 
        else 
        { 
         dupExp = readerCSVExp[i]; 
        } 
        foreach (int keyColumn in keyColumns) 
        { 
         if (i == keyColumn - 1) 
         { 
          resultCell = "duplicateEXP: '" + dupExp + "'"; 
          resultCell = CreateCSVField(resultCell); 
          resultsFile.Write(resultCell); 
          comSumCol = comSumCol + 1; 
          countDuplicateExp = countDuplicateExp + 1; 
         } 
         else 
         { 
          if (checkPTColumns(i + 1, passthroughColumns) == false) 
          { 
           resultCell = "'" + dupExp + "'"; 
           resultCell = CreateCSVField(resultCell); 
           resultsFile.Write(resultCell); 
           countDuplicateExp = countDuplicateExp + 1; 
          } 
          else 
          { 
           resultCell = "PASSTHROUGH duplicateEXP: '" + dupExp + "'"; 
           resultCell = CreateCSVField(resultCell); 
           resultsFile.Write(resultCell); 
          } 
          comSumCol = comSumCol + 1; 
         } 
        } 
        if (comSumCol <= fieldCount) 
        { 
         resultsFile.Write(csComma); 
        } 
       } 
       if (comSumCol == fieldCount + 1) 
       { 
        resultsFile.Write(csComma + rowNumExp); 
        comSumCol = comSumCol + 1; 
       } 
       if (comSumCol == fieldCount + 2) 
       { 
        resultsFile.Write(csComma); 
        comSumCol = comSumCol + 1; 
       } 
       if (comSumCol > fieldCount + 2) 
       { 
        comSumRow = comSumRow + 1; 
        resultsFile.Write(csCrLf); 
        comSumCol = 1; 
       } 
      } 
      keyExp.Clear(); 
     } 
     logFile.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n"); 
     Console.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n"); 
     logFile.WriteLine("Done Analyzing Expected Duplicates at " + DateTime.Now + "\r\n"); 
     Console.WriteLine("Done Analyzing Expected Duplicates at " + DateTime.Now + "\r\n"); 
     logFile.Flush(); 

Jednak problem polega na tym, że potrzebuję obu zestawów danych w pamięci. Rzeczywiście, przeglądam zarówno słowniki, które szukają dopasowań, niedopasowań, duplikatów i rezygnacji w oparciu o klucz.

Stosując to podejście do przechowywania indeksu wiersza, nadal używam dużej ilości pamięci, ponieważ do uzyskania dostępu dynamicznego muszę teraz używać buforowanej wersji czytnika csv. Więc chociaż słownik jest teraz znacznie mniejszy, buforowanie danych nadrabia oszczędności, a ja wciąż miałem do czynienia z podobnym wykorzystaniem pamięci.

Hope, mam sensu ... :)

Jedną z opcji jest, aby pozbyć się całkowicie i po prostu słowniku pętla przez 2 pliki, ale nie jestem pewien, czy wydajność byłaby tak szybko jak porównywanie 2 słowniki.

Wszelkie dane wejściowe są mile widziane.

+0

zamiast buforować czytnik csv, nie możesz buforować lokalizacji rekordów w pliku, aby móc później odzyskać te rekordy? kiedy przeglądasz słowniki szukające upuszczenia itp. kluczem jest przeglądanie rzeczywistych danych lub tylko kluczy? –

+0

czy próbowałeś przetłumaczyć ciąg znaków przed przejściem do słownika? czy to miało jakiś wpływ? Czy to wszystko pomogło z wykorzystaniem pamięci? –

Odpowiedz

7

Można zastąpić keyExp przez StringBuilder. ponowne przydzielenie łańcucha w takiej pętli spowoduje przydzielenie większej ilości pamięci, ponieważ łańcuchy są niezmienne.

StringBuilder keyExp = new StringBuilder(); 
... 
    keyExp.Append("|" + readerCSVExp[i - 1]) ; 
... 

to wiele z tych samych ciągów? można spróbować interning them, potem jakieś identyczne ciągi będą dzielić tę samą pamięć Zamiast kopii ...

rowExp[fieldCount] = String.Intern(rowNumExp.ToString()); 

// Dedup Expected    
string internedKey = (String.Intern(keyExp.ToString()));   
if (!(dictExp.ContainsKey(internedKey))) 
{ 
    dictExp.Add(internedKey, rowExp);       
} 
else 
{ 
    listDupExp.Add(rowExp); 
} 

nie jestem pewien dokładnie, jak działa kod, ale ... poza to powiedziałbym don W słowniku zachowaj rowExp, zachowaj coś innego, jak numer i napisz na dysk rowExp w innym pliku. To prawdopodobnie zaoszczędzi ci najwięcej pamięci, ponieważ wydaje się, że jest to ciąg znaków z pliku, więc prawdopodobnie jest duży. Jeśli zapiszesz go w pliku i zachowasz jego numer w pliku, możesz odzyskać go w przyszłości, jeśli będziesz musiał go przetworzyć.Jeśli zapisałeś offset w pliku jako wartość w słowniku, możesz go szybko znaleźć. Może :).

+0

Interesujące, myślałem, że kompilator/interpreter/jitter/coś internowane ciągi automatycznie, ale to prawdopodobnie tylko dla żądła, które są znane w identycznym momencie kompilacji. – Davy8

+0

@ Davy8, zgadza się. Interwencja String występuje domyślnie w łańcuchach tworzonych ze stałych kompilujących. –

3

Powiedz, jeśli coś złego mi się stanie.

Powyższy kod odczytuje jeden plik CSV i wyszukuje duplikaty kluczy. Każdy wiersz przechodzi w jeden z dwóch zestawów, jeden dla dwóch kluczy i jeden bez.

Co robisz z tymi zestawami wierszy?

Czy są one zapisywane w różnych plikach?

Jeśli tak, nie ma powodu, aby przechowywać niezainteresowane wiersze na liście, ponieważ można je zapisać w pliku.

Po znalezieniu duplikatów, nie ma potrzeby przechowywania całego wiersza, po prostu zapisz klucz i zapisz wiersz do pliku (oczywiście inny plik, jeśli chcesz zachować je oddzielnie).

Jeśli konieczne jest dalsze przetwarzanie w różnych zestawach, zamiast zapisywania całego wiersza, nie zapisuj numeru wiersza. Następnie, gdy robisz to, co robisz z wierszami, musisz ponownie użyć numeru wiersza, aby ponownie pobrać wiersz.

NB: zamiast zapisywać numer wiersza, można zapisać offset w pliku punktu początkowego wiersza. Następnie możesz uzyskać dostęp do pliku i losowo czytać wiersze, jeśli potrzebujesz.

Po prostu skomentuj tę odpowiedź, udzielając odpowiedzi na pytania (lub wyjaśnienia), zaktualizuję odpowiedź, i tak będę tutaj przez kolejne kilka godzin.

Edit
Można zmniejszyć wydruk pamięci stóp dalej nie przechowywania kluczy, ale przechowywanie skrótów klawiszy. Jeśli znajdziesz duplikat, wyszukaj tę pozycję w pliku, przeczytaj ponownie wiersz i porównaj rzeczywiste klucze.

+0

Proszę spojrzeć na moją odpowiedź w edytowanym poście powyżej. Niestety, nie wiesz, jak skutecznie wkleić próbkę kodu w komentarzach. – user262102

2

Jeśli nie masz jeszcze profilera na tym, jak DotTrace, aby zobaczyć, które obiekty używają pamięci, to da ci to, co trzeba zoptymalizować.

Niektóre pomysły patrząc na kod:

Czy trzeba przechowywać listDupExp? Wydaje mi się, że z powodzeniem ładujesz oba pliki do pamięci, więc 2 x 150 MB + trochę narzutów może z łatwością zbliżyć się do 500 MB w menedżerze zadań.

Po drugie, czy można rozpocząć zapisywanie wyników przed przeczytaniem wszystkich danych wejściowych? Zakładam, że jest to trudne, ponieważ wygląda na to, że potrzebujesz wszystkich elementów wyjściowych posortowanych przed ich napisaniem, ale może być czymś, na co możesz spojrzeć.

Powiązane problemy