2016-07-19 9 views
-1

Ich muss 4.000.000.000 Zeilen aus einer Datei lesen und sie in einem Array speichern.Lesen von 4.000.000.000 Zeilen aus einer Datei und Speichern in ein Array in C

Aber der Linux-Kernel tötet den Prozess wegen des Speichers:

tail /var/log/kern.log 
... Out of memory: Kill process ... 

Der Code

#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 


int main() { 

    /* 
    * Read line by line from the file and write into the array 
    */ 

    int lines_allocated = 128; 
    int max_line_len = 15; 

    char **array = (char **)malloc(sizeof(char*)*lines_allocated); 
    if (array==NULL) { 
     fprintf(stderr,"Out of memory (1).\n"); 
     exit(1); 
    } 

    file = fopen("file", "r"); 
    if (file == NULL) { 
     fprintf(stderr,"Error opening file.\n"); 
     exit(2); 
    } 

    int il; 
    for (il=0;1;il++) { 
     int j; 

     /* Have we gone over our line allocation? */ 
     if (il >= lines_allocated) { 
      int new_size; 

      /* Double our allocation and re-allocate */ 
      new_size = lines_allocated*2; 
      array = (char **)realloc(array,sizeof(char*)*new_size); 
      if (array==NULL){ 
       fprintf(stderr,"Out of memory.\n"); 
       exit(3); 
      } 
      lines_allocated = new_size; 
     } 

     /* Allocate space for the next line */ 
     array[il] = malloc(max_line_len); 
     if (array[il]==NULL) 
      { 
      fprintf(stderr,"Out of memory (3).\n"); 
      exit(4); 
      } 
     if (fgets(array[il], max_line_len-1, file)==NULL) 
      break; 

     /* Get rid of CR or LF at end of line */ 
     for (j=strlen(array[il])-1;j>=0 && (array[il][j]=='\n' || array[il][j]=='\r');j--) 
      ; 

     array[il][j+1]='\0'; 
    } 

    /* Close login file */ 
    fclose(file); 

    /* Print the array of data from the file */ 
    for (i=0; i < il; i++) 
     printf("%s\n", array[i]); 

    return 0; 
} 

Was ist der am besten geeignete und effektivste Weg, dies zu tun? Vielleicht ersten Block lesen, fertig damit, dann nächsten Block lesen und so weiter?

Was sind die Lösungen für dieses Problem?

+0

lesen Sie es nach kleineren Zeilenblöcken. –

+3

Wie Sie bereits gesagt haben, können Sie jeweils eine Zeile lesen, sie verarbeiten und dann für die verbleibenden Datenzeilen dasselbe tun. – ameyCU

+3

Sie haben sich selbst geantwortet. Stück für Stück ist der richtige Weg –

Antwort

1

Angenommen, Sie würden eine byte von jeder Zeile lesen, würden Sie 29 GB Speicher benötigen.

Für so große Datenmengen ist es wichtig, so wenig Daten wie möglich zu laden und dann nach der Verarbeitung den Speicher freizugeben. Sonst wirst du die Erinnerung vermissen.

+0

Warum "so wenig Daten wie möglich"? Ich würde persönlich einen schönen, dicken Puffer fester Größe bekommen (aber nicht zu fett) und lese große Stücke (aber nicht Gigabyte). Dann würde ich Zeile für Zeile aus diesem Fettpuffer lesen. Kein malloc und frei jedes Mal. –

+1

Was ist mit Memory-Mapped-Datei? – inzanez

1

Die naheliegende Lösung besteht darin, die Eingabe in Blöcken zu verarbeiten. Wenn Sie dies tun können, hat dies den zusätzlichen Vorteil, dass Sie Berechnungen (auf dem aktuellen Block) während der I/O durchführen können (die meisten Betriebssysteme führen Lookahead-Pufferungen durch), was zu einer kürzeren realen Zeit führt als das erste Lesen aller Daten , dann verarbeitet es in einem Stück.

(Ich erwähne oft diese Tatsache für diejenigen, die ein sort-Typ-Dienstprogramm implementieren. Für echte, messbare Beschleunigungen, sollten Sie die Eingaben in einen Baum, anstatt in ein Array lesen und dann das Array sortieren, weil Der Speicher ist langsam, und wenn Sie jeden Eingangsdatensatz (Zeile) sofort in einen Baum einfügen, können Sie im Prinzip die Zeit nutzen, die sonst für das Sortieren der Daten auf E/A benötigt wird Implementierung) im Vergleich zu einem wirklich guten Array Sortieralgorithmus, aber die Menschen kümmern sich nicht oft viel darüber. Wir kümmern uns hauptsächlich um reale Welt Wartezeit verbracht haben. Wir möchten nicht warten.)


Wenn die Auf Eingabedatensätze (Zeilen) wird zufällig zugegriffen, und Sie arbeiten an einer 64-Bit-Architektur, Speichermapping-Techniken können es möglich machen. (Wenn Sie können tun die Arbeit in Chunks, tun Sie dies, wenn Sie nicht können, Speicherzuordnung können Sie die Verarbeitung auch wenn Sie nicht genügend RAM haben.)

Vor Jahren schrieb ich ein minimales Beispiel auf wie Sie eine Datei-abgesicherte Sparse-Speicherzuordnung in 64-Bit-Linux zu manipulate a terabyte data structure verwenden.

In diesem Fall können Sie eine schreibgeschützte Speicherzuordnung für den wahlfreien Zugriff auf die Textdatei verwenden. Beachten Sie, dass Sie in Linux mmap(NULL, aligned_length, PROT_READ, MAP_SHARED | MAP_NORESERVE, fd, 0) verwenden sollten, wobei aligned_length die Länge der Textdatei ist, die auf das nächste Vielfache von sysconf(SC_PAGESIZE) aufgerundet wird. Solche Zuordnungen verwenden keinen Swap (und daher begrenzt die Größe des Swap-Space nicht die Größe des Mappings), aber der Kernel kann und wird die Seiten löschen (und bei Bedarf aus der Datei selbst erneut lesen), wenn der Speicher zu knapp ist.

Eine zweite Speicherzuordnung kann zum Speichern der Offsets (bei 64-Bit-Architekturen size_t ist ausreichend groß) am Anfang jeder Zeile verwendet werden. Diese Zuordnung sollte mmap(NULL, offbufsize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd2, 0) verwenden, wobei offbufsize ein Vielfaches von sysconf(_SC_PAGESIZE) ist, aber mindestens so groß wie die Anzahl der Zeilen in der Eingabedatei, multipliziert mit sizeof (size_t).

Die Bestimmung des Anfangs jeder Zeile ist eine Operation, die am besten in Chunks (von 512 × 1024 = 524288 Bytes) durchgeführt wird. Das heißt, Sie sollten zuerst das zweite Mapping einrichten, indem Sie ftruncate() und mremap() verwenden, um es bei Bedarf zu vergrößern, indem Sie die erste Datei mit Low-Level-E/A (unistd.h read()) in den oben genannten Abschnitten lesen, um den Anfang jeder Zeile zu bestimmen.

Ich persönlich habe etwas Ähnliches in der Praxis gemacht. Selbst mit universeller Newline-Unterstützung (unterstützt \r\n oder CRLF, \n\r oder LFCR, \r oder CR, und \n oder LF, gleichzeitig, automatisch) benötigen Sie nur ein paar zusätzliche Bytes Speicherplatz im Array zum Speichern des Chunk gelesen aus der großen Textdatei. Da der Initial Pass vollständig linear ist, kann die Verwendung von posix_fadvise(fd,0,0,POSIX_FADV_NOREUSE) oder posix_fadvise(fd,0,0,POSIX_FADV_SEQUENTIAL) gerechtfertigt sein.

Angesichts mindestens ein paar Gigabyte RAM zur Pufferung zur Verfügung (was als eine typische Situation sogar auf kleineren 64-Bit-Linux/POSIX-Workstations gilt), obwohl das Linux-Kernel-Paging-Zeug nicht "magisch" ist überraschend gut.

Der häufigste Grund für unbefriedigende Leistung mit viel-größer-als-verfügbaren RAM-Mappings, die ich in Linux gesehen habe, ist das Vergessen der MAP_NORESERVE Flagge. Dies führt zu unnötigen Einschränkungen (verfügbarer Swap-Speicherplatz, der die maximal zulässige Mapping-Größe begrenzt) sowie zu schlechter Leistung bei knappem Speicher (da die Seiten zum Austauschen geschrieben sind und nicht einfach in den Boden fallen).