2016-12-21 64 views
7

Stworzyłem framework do parsowania plików tekstowych o rozsądnych rozmiarach, które zmieszczą się w pamięci RAM, a na razie wszystko idzie dobrze. Nie mam żadnych skarg, ale co by było, gdybym spotkała się z sytuacją, w której mam do czynienia z dużymi plikami, powiedzmy, większymi niż 8 GB (co jest wielkością mojej)? Jaki byłby skuteczny sposób radzenia sobie z tak dużymi plikami?Jak analizować pliki, które nie mieszczą się całkowicie w pamięci RAM

Moje ramy:

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

int Parse(const char *filename, 
    const char *outputfile); 

int main(void) 
{ 
    clock_t t1 = clock(); 
    /* ............................................................................................................................. */ 
    Parse("file.txt", NULL); 
    /* ............................................................................................................................. */ 
    clock_t t2 = clock(); 
    fprintf(stderr, "time elapsed: %.4f\n", (double)(t2 - t1)/CLOCKS_PER_SEC); 
    fprintf(stderr, "Press any key to continue . . . "); 
    getchar(); 
    return 0; 
} 

long GetFileSize(FILE * fp) 
{ 
    long f_size; 
    fseek(fp, 0L, SEEK_END); 
    f_size = ftell(fp); 
    fseek(fp, 0L, SEEK_SET); 
    return f_size; 
} 

char *dump_file_to_array(FILE *fp, 
    size_t f_size) 
{ 
    char *buf = (char *)calloc(f_size + 1, 1); 
    if (buf) { 
     size_t n = 0; 
     while (fgets(buf + n, INT_MAX, fp)) { 
      n += strlen(buf + n); 
     } 
    } 
    return buf; 
} 

int Parse(const char *filename, 
    const char *outputfile) 
{ 
    /* open file for reading in text mode */ 
    FILE *fp = fopen(filename, "r"); 
    if (!fp) { 
     perror(filename); 
     return 1; 
    } 
    /* store file in dynamic memory and close file */ 
    size_t f_size = GetFileSize(fp); 
    char *buf = dump_file_to_array(fp, f_size); 
    fclose(fp); 
    if (!buf) { 
     fputs("error: memory allocation failed.\n", stderr); 
     return 2; 
    } 
    /* state machine variables */ 
    // ........ 

    /* array index variables */ 
    size_t x = 0; 
    size_t y = 0; 
    /* main loop */ 
    while (buf[x]) { 
     switch (buf[x]) { 
      /* ... */ 
     } 
     x++; 
    } 
    /* NUL-terminate array at y */ 
    buf[y] = '\0'; 
    /* write buffer to file and clean up */ 
    outputfile ? fp = fopen(outputfile, "w") : 
       fp = fopen(filename, "w"); 
    if (!fp) { 
     outputfile ? perror(outputfile) : 
        perror(filename); 
    } 
    else { 
     fputs(buf, fp); 
     fclose(fp); 
    } 
    free(buf); 
    return 0; 
} 

funkcja usunięcie Wzór na podstawie ram:

int delete_pattern_in_file(const char *filename, 
    const char *pattern, const char *outputfile) 
{ 
    /* open file for reading in text mode */ 
    FILE *fp = fopen(filename, "r"); 
    if (!fp) { 
     perror(filename); 
     return 1; 
    } 
    /* copy file contents to buffer and close file */ 
    size_t f_size = GetFileSize(fp); 
    char *buf = dump_file_to_array(fp, f_size); 
    fclose(fp); 
    if (!buf) { 
     fputs("error - memory allocation failed", stderr); 
     return 2; 
    } 
    /* delete first match */ 
    size_t n = 0, pattern_len = strlen(pattern); 
    char *tmp, *ptr = strstr(buf, pattern); 
    if (!ptr) { 
     fputs("No match found.\n", stderr); 
     free(buf); 
     return -1; 
    } 
    else { 
     n = ptr - buf; 
     ptr += pattern_len; 
     tmp = ptr; 
    } 
    /* delete the rest */ 
    while (ptr = strstr(ptr, pattern)) { 
     while (tmp < ptr) { 
      buf[n++] = *tmp++; 
     } 
     ptr += pattern_len; 
     tmp = ptr; 
    } 
    /* copy the rest of the buffer */ 
    strcpy(buf + n, tmp); 
    /* open file for writing and print the processed buffer to it */ 
    outputfile ? fp = fopen(outputfile, "w") : 
       fp = fopen(filename, "w"); 
    if (!fp) { 
     outputfile ? perror(outputfile) : 
        perror(filename); 
    } 
    else { 
     fputs(buf, fp); 
     fclose(fp); 
    } 
    free(buf); 
    return 0; 
} 
+2

Najczęściej stosowane jest tworzenie parsera opartego na zdarzeniach z flex/yacc. Zawierają one tylko niezbędne informacje w pamięci RAM (tokeny na stosie itp.). Ile dokładnie zależy głównie od gramatyki. – Ctx

+0

To może być specyficzny dla systemu operacyjnego. Zobacz także [tę odpowiedź] (http://stackoverflow.com/a/41237690/841108), wspominając o kilku przydatnych wersjach systemu Linux. Ale prawdopodobnie możesz odczytać linię pliku po linii, np. z [getline (3)] (http://man7.org/linux/man-pages/man3/getline.3.html). Zobacz także odnośniki w [tej odpowiedzi] (http://stackoverflow.com/a/41208995/841108). –

+1

Powinieneś zdefiniować składnię i leksery swojego parsowanego pliku tekstowego. –

Odpowiedz

11

Jeśli chcesz trzymać się swojej obecnej konstrukcji, rozwiązaniem mogłoby być mmap() plik zamiast czytania do bufora pamięci.

Można zmienić funkcję dump_file_to_array do następujących (specyficzne dla Linuksa):

char *dump_file_to_array(FILE *fp, size_t f_size) { 
    buf = mmap(NULL, f_size, PROT_READ, MAP_SHARED, fileno(fp), 0); 
    if (buf == MAP_FAILED) 
     return NULL; 
    return buf; 
} 

Teraz można przeczytać plik, menedżer pamięci weźmie automatycznie obchodzi tylko posiadać odpowiednie mikstury pliku w pamięci. W przypadku systemu Windows istnieją podobne mechanizmy.

+0

Pamiętaj jednak, że ten bufor nie będzie zakończone znakiem null, więc analizator składni musi porównać przesunięcie z rozmiarem pliku dla każdego bajtu, zamiast polegać na obecności linii lub łańcucha znaków. – chqrlie

+0

@chqrlie Rzeczywiście, zero jest kończone w _almost_ wszystkich przypadkach; gdy plik nie jest wielokrotnością rozmiaru strony. Jednak, jeśli tak, może nie być. – Ctx

+0

@chux: jeśli rozmiar pliku nie jest mnogością rozmiaru strony, nie jestem pewien, czy odczytanie bajtu poza końcem niewidocznego obszaru jest OK. W przypadku większości systemów może być OK, ale może to spowodować błąd segmentacji w systemach o drobniejszej ziarnistości. – chqrlie

2

Prawdopodobieństwo, że parsujesz plik linia po linii. Tak więc przeczytaj w dużym bloku (4k lub 16k) i przeanalizuj wszystkie wiersze w tym. Skopiuj małą pozostałość na początek bufora 4k lub 16k i odczytaj w pozostałej części bufora. Wypłukać i powtórzyć.

Dla JSON lub XML wymagany jest parser oparty na zdarzeniu, który może akceptować wiele bloków lub danych wejściowych.

1

Przede wszystkim nie sugerowałbym posiadania tak dużych plików w pamięci RAM, ale zamiast tego za pomocą strumieni. To dlatego, że buforowanie jest zwykle wykonywane przez bibliotekę, a także przez jądro.

Jeśli uzyskujesz dostęp do pliku sekwencyjnie, co wydaje się być prawdą, to prawdopodobnie wiesz, że wszystkie nowoczesne systemy wdrażają algorytmy odczytu z wyprzedzeniem, więc po prostu odczytanie całego pliku z wyprzedzeniem W RAM może w większości przypadków po prostu marnować czas .

Nie określić przypadków użycia masz na pokrycie tak mam zamiar założyć, że za pomocą strumieni jak

std::ifstream 

i robi analizowania na bieżąco będzie odpowiadał naszym potrzebom. Na marginesie, upewnij się, że operacje na plikach, które mają być duże, są wykonywane w osobnych wątkach.

+2

'std :: ifstream' to C++, nie? –

+0

@ machine_1 Tak, to C++, fgets jest w porządku i nie trzeba kopiować wszystkiego w pamięci RAM. Jeśli naprawdę musisz to zrobić, możesz spróbować mmap z MAP_HUGETLB, ale to również NIE DZIAŁA, jeśli system ma niewystarczającą ilość pamięci. –

2

Jest wiele problemów z twoim podejściem.

Koncepcja maksymalna i dostępny pamięci nie są tak oczywiste: technicznie, nie są ograniczone przez wielkość pamięci RAM, ale przez ilość pamięci środowisko pozwoli alokować i wykorzystać do swojego programu. To zależy od wielu czynników:

  • Co ABI kompilacji dla: maksymalna wielkość pamięci dostępnej dla programu jest ograniczona do mniej niż 4 GB, jeśli kompilacji kodu 32-bitowego, nawet jeśli system ma więcej pamięci RAM niż że.
  • Jaki przydział jest skonfigurowany, aby umożliwić korzystanie z programu. Może to być mniej niż dostępna pamięć.
  • Jaka strategia wykorzystuje system, gdy żądana jest większa ilość pamięci niż jest fizycznie dostępna: większość nowoczesnych systemów korzysta z pamięci wirtualnej i współużytkuje pamięć fizyczną między procesami i zadaniami systemowymi (takimi jak pamięć podręczna dysku) przy użyciu bardzo zaawansowanych algorytmów, których nie można opisać w kilka linii. W niektórych systemach twój program może przydzielić i zużyć więcej pamięci niż fizycznie zainstalowana na płycie głównej, zamieniając strony pamięci na dysk, gdy dostęp do większej ilości pamięci jest bardzo kosztowny w czasie opóźnienia.

Istnieją również inne problemy w kodzie:

  • Typ long może być zbyt mały, aby pomieścić rozmiar pliku: W systemach Windows long jest 32-bitowy nawet na 64- wersje bitowe, w których pamięć może być przydzielana w porcjach większych niż 2 GB. Aby zażądać rozmiaru pliku z systemu, musisz użyć innego interfejsu API.
  • Przeczytałeś plik z serią połączeń pod numer fgets(). Jest to niewydajne, wystarczyłoby jedno połączenie z numerem fread(). Co więcej, jeśli plik zawiera osadzone bajty zerowe (znaki "\ 0"), fragmenty z pliku nie będą w pamięci. Jednak nie można zajmować się osadzonymi pustymi bajtami, jeśli używasz funkcji łańcuchowych, takich jak strstr() i strcpy() do obsługi zadania usuwania łańcucha.
  • Stan w while (ptr = strstr(ptr, pattern)) to zadanie. Chociaż nie jest to całkowicie niepoprawne, jest to zły styl, ponieważ myli czytelników kodu i zapobiega zapisywaniu ostrzeżeń przez kompilator, gdy takie warunki przypisania są błędami kodowania. Możesz myśleć, że to się nigdy nie zdarzy, ale każdy może popełnić literówkę, a brakujący = w teście jest trudny do wykrycia i ma tragiczne konsekwencje.
  • Ci korzystanie krótkim ręka operatora potrójnego zamiast if stwierdzeń jest dość mylące zbyt: outputfile ? fp = fopen(outputfile, "w") : fp = fopen(filename, "w");
  • przepisanie pliku wejściowego na miejscu jest zbyt ryzykowne: jeśli coś pójdzie nie tak, plik wejściowy zostaną utracone.

pamiętać, że można realizować filtrowanie w locie, bez bufora, aczkolwiek nieskutecznie:

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

int main(int argc, char *argv[]) { 
    if (argc < 2) { 
     fprintf(stderr, "usage: delpat PATTERN <inputfile> outputfile\n"); 
     return 1; 
    } 
    unsigned char *pattern = (unsigned char*)argv[1]; 
    size_t i, j, n = strlen(argv[1]); 
    size_t skip[n + 1]; 
    int c; 

    skip[0] = 0; 
    for (i = j = 1; i < n; i++) { 
     while (memcmp(pattern, pattern + j, i - j)) { 
      j++; 
     } 
     skip[i] = j; 
    } 

    i = 0; 
    while ((c = getchar()) != EOF) { 
     for (;;) { 
      if (i < n && c == pattern[i]) { 
       if (++i == n) { 
        i = 0; /* match found, consumed */ 
       } 
       break; 
      } 
      if (i == 0) { 
       putchar(c); 
       break; 
      } 
      for (j = 0; j < skip[i]; j++) { 
       putchar(pattern[j]); 
      } 
      i -= skip[i]; 
     } 
    } 
    for (j = 0; j < i; j++) { 
     putchar(pattern[j]); 
    } 
    return 0; 
} 
0

alternatywne rozwiązanie: Jeśli jesteś na systemach Linux i masz przyzwoitą kwotę zamień przestrzeń, po prostu otwórz cały zły chłopiec. Spowoduje to pochłonięcie twojego RAMa, a także pochłonie miejsce na dysku twardym (zamiana). W ten sposób możesz mieć całą rzecz otwartą na raz, po prostu nie wszystko będzie na baranie.

Plusy

  • Jeśli nieoczekiwane shut down nastąpiło, pamięć na przestrzeni wymiany podlega zwrotowi.
  • RAM jest drogie, dyski twarde są tanie, więc aplikacja będzie umieścić mniejsze obciążenie na drogiego sprzętu
  • wirus nie mógł uszkodzić komputer, ponieważ nie byłoby pokój w pamięci RAM dla nich, aby uruchomić
  • Będziesz pełne wykorzystanie systemu operacyjnego Linux za pomocą przestrzeni wymiany. Zwykle moduł wymiany nie jest używany i wszystko, co robi, zapycha cenny baran.
  • Dodatkowa energia potrzebna do wykorzystania całego barana może ogrzać najbliższy obszar. Przydatne w okresie zimowym
  • Możesz dodać "Złożoną i specjalną inżynierię przydzielania pamięci" do swojego CV.

Wady

  • Brak
0

Rozważmy traktując go jako zewnętrzny tablicy linii.

Kod może korzystać z tablicy indeksów linii. Ta tablica indeksów może być przechowywana w pamięci w ułamku wielkości dużego pliku. Dostęp do dowolnej linii odbywa się szybko przez to wyszukiwanie, wyszukiwanie z fsetpos() i fread()/fgets(). Gdy linie są edytowane, nowe wiersze można zapisywać w dowolnej kolejności w tymczasowym pliku tekstowym. Zapisanie pliku odczytuje zarówno plik oryginalny, jak i tymczasowy, aby utworzyć i zapisać nowy plik.

typedef struct { 
    int attributes; // not_yet_read, line_offset/length_determined, 
        // line_changed/in_other_file, deleted, etc. 
    fpos_t line_offset; // use with fgetpos() fsetpos() 
    unsigned line_length; // optional field as code could re-compute as needed. 
} line_index; 

size_t line_count; 
// read some lines 
line_index *index = malloc(sizeof *index * line_count); 
// read more lines 
index = realloc(index, sizeof *index * line_count); 
// edit lines, save changes to appended temporary file. 
// ... 
// Save file -weave the contents of the source file and temp file to the new output file. 

Dodatkowo z ogromnych plików tablicy line_index[] sama może być realizowany w pamięci dyskowej też. Dostęp do jest łatwo obliczany. W skrajnym sensie tylko 1 linii pliku musi w pamięci w dowolnym momencie.

0

Wspomniał Pan o automatach państwowych. Każdy automat skończony-stan może być zoptymalizowany tak, aby miał minimalny (lub nie) wyprzedzający.

Czy można to zrobić w Lex? Wygeneruje plik wyjściowy c, który możesz skompilować.

Jeśli nie chcesz korzystać z Lex, można zawsze następuje: (? Pierścienia)

  1. Czytaj n znaków w buforze, gdzie n jest wielkość wzoru.
  2. Spróbuj dopasować bufor wzorkiem
  3. Jeśli mecz goto 1
  4. Bufor wydruku [0], przeczytaj char, goto 2

także dla bardzo długich wzorów i wejść zdegenerowanych strstr może być powolne. W takim przypadku warto przyjrzeć się bardziej zaawansowanym agronomiom dopasowującym się do żądła.

0

mmap() to całkiem niezły sposób pracy z plikami o dużych rozmiarach. Zapewnia dużą elastyczność, ale musisz zachować ostrożność przy rozmiarze strony. Here to dobry artykuł, który mówi o więcej szczegółów.