2015-10-17 19 views
6

Mam plik w następującym formacie:najskuteczniejszym sposobem analizować każdą czwartą linię z bardzo dużego pliku

1: some_basic_info_in_this_line 
2: LOTS_OF_INFO_IN_THIS_LINE_HUNDREDS_OF_CHARS 
3: some_basic_info_in_this_line 
4: LOTS_OF_INFO_IN_THIS_LINE_HUNDREDS_OF_CHARS 
... 

To Format powtarza się dziesiątki tysięcy razy, dzięki czemu pliki do 50 Gib +. Potrzebuję skutecznego sposobu przetwarzania tylko linii 2 tego formatu. Jestem otwarty na używanie C, C++ 11 STL lub zwiększenie. Przejrzałem różne inne pytania dotyczące przesyłania plików na SO, ale mam wrażenie, że moja sytuacja jest wyjątkowa ze względu na duży rozmiar pliku i wymaga tylko jednej na cztery linie.

Mapowanie pamięci pliku wydaje się być najbardziej wydajne z tego, co przeczytałem, ale odwzorowanie pliku o wielkości 50 GB pochłonie większość komputerów RAM (można założyć, że ta aplikacja będzie używana przez "przeciętnych" użytkowników - powiedz 4-8 GiB RAM). Muszę też przetworzyć tylko jedną linię na raz. Oto jak jestem obecnie robi to (tak jestem świadomy tego nie jest skuteczny, dlatego jestem przeprojektowanie go):

std::string GL::getRead(ifstream& input) 
{ 
    std::string str; 
    std::string toss; 
    if (input.good()) 
    { 
     getline(input, toss); 
     getline(input, str); 
     getline(input, toss); 
     getline(input, toss); 
    } 
    return str; 
} 

łamie mmap na bloki odpowiedź na moją sytuację? Czy jest tak, że mogę wykorzystać tylko 1 z 4 linii? Dzięki za pomoc.

+0

Jeszcze raz zastanawiam się, kto to zajął. Jest to wystarczająco interesujące pytanie i jest dobrze postawione. +1 – sehe

+1

Jeśli twój format jest ściśle określony przy użyciu rozmiarów linii, możesz użyć input.seekg, aby pominąć niepożądane linie. – Nir

+0

Głównym wąskim gardłem będzie sama operacja wprowadzania. Sugeruję, abyś eksperymentował z różnymi sposobami ładowania bloku danych po bloku na typowym komputerze użytkownika. Nie wiem, jak najlepiej radzić sobie z linią, która okrakiem ma dwa bloki (w przypadku większości bloków będzie taka linia). –

Odpowiedz

0

Jest to najbardziej wydajne rozwiązanie, które mógłbym wymyślić, niezależne od platformy. Myślałem o wszystkich użytkownikach i na razie działam przy założeniu, że wszyscy mają maszynę 64-bitową, jeśli używają 4+ wielkości plików GiB. Jeśli to się zmieni, będę musiał zmodyfikować klasę, aby obsługiwać "bloki" danych w oddzielnych regionach mmap.

#include <string> 
#include <boost/iostreams/device/mapped_file.hpp> 

////////////////////////////////////////////////////////////////////////////////////////// 
/// @class LineParser 
/// 
/// @brief Class to efficiently parse a file and take the second line out of every 4 lines 
/// 
/// This class uses memory-mapped io to efficiently extract and return sequences from a 
/// file 
////////////////////////////////////////////////////////////////////////////////////////// 
class LineParser 
{ 
private: 
    boost::iostreams::mapped_file mmap; ///< Object for memory mapped file 
    const char* curr;     ///< Current position of the file 
    const char* end;     ///< End position of the file 

public: 
    ////////////////////////////////////////////////////////////////////////////////////// 
    /// @fn valid 
    /// 
    /// Indicates whether the parser is in a valid state or not 
    /// 
    /// @return Boolean indicating if the parser is open and in a valid state 
    /// 
    /// @note Declared inline as it is acceptable in my situation because of being called 
    ///  many times in only a few spots  
    ////////////////////////////////////////////////////////////////////////////////////// 
    inline bool valid(void) 
    { 
     return (curr && end && (curr < end) && (mmap.is_open())); 
    } 

    ////////////////////////////////////////////////////////////////////////////////////// 
    /// @fn peek 
    /// 
    /// Obtains the next sequence string - if it exists - but maintains parsers state 
    /// 
    /// @return Next sequence available in the file. Emptry string returned if none 
    ///   exist 
    /// 
    /// @note Declared inline as it is acceptable in my situation because of being called 
    ///  many times in only a few spots 
    ////////////////////////////////////////////////////////////////////////////////////// 
    inline std::string peek(void) 
    { 
     const char* save = curr; 
     std::string ret; 

     getRead(ret); 

     curr = save; 
     return ret; 
    } 

    ////////////////////////////////////////////////////////////////////////////////////// 
    /// @fn getRead 
    /// 
    /// Sets container to the current read being processed 
    /// 
    /// @param container String container to place current sequence into 
    /// 
    /// @return Boolean indicating if getting a new read was successful 
    /// 
    /// @note Declared inline as it is acceptable in my situation because of being called 
    ///  many times in only a few spots 
    ////////////////////////////////////////////////////////////////////////////////////// 
    inline bool getRead(std::string& container) 
    { 
     if (valid() == false) 
     { 
      return false; 
     } 

     curr = static_cast<const char*>(memchr(curr, '\n', end-curr)) + 1; 
     const char* index = static_cast<const char*>(memchr(curr, '\n', end-curr)); 
     container = std::string(curr, index - curr); 
     curr = index + 1; 
     curr = static_cast<const char*>(memchr(curr, '\n', end-curr)) + 1; 
     curr = static_cast<const char*>(memchr(curr, '\n', end-curr)) + 1; 

     return true; 
    } 

    ////////////////////////////////////////////////////////////////////////////////////// 
    /// @fn LineParser 
    /// 
    /// Constructor to initialize memory mapped file and set index values 
    ////////////////////////////////////////////////////////////////////////////////////// 
    LineParser(const std::string& filepath) 
     : mmap(filepath, boost::iostreams::mapped_file::readonly) 
    { 
     if (mmap.is_open()) 
     { 
      curr = mmap.const_data(); 
      end = curr + mmap.size(); 
     } 
    } 

    ////////////////////////////////////////////////////////////////////////////////////// 
    /// @fn ~LineParser 
    /// 
    /// Default destructor 
    ////////////////////////////////////////////////////////////////////////////////////// 
    ~LineParser(void) = default; 
}; 

Należy pamiętać, że ta klasa nie jest doskonały i może nie bardzo dobrze obsłużyć odchylenia w formacie pliku, ale przy założeniu idealnej formacie działa ładnie.

5

Zastosowanie ignore zamiast getline:

std::string GL::getRead(ifstream& input) 
{ 
    std::string str; 
    if (!input.fail()) 
    { 
     input.ignore(LARGE_NUMBER, '\n'); 
     getline(input, str); 
     input.ignore(LARGE_NUMBER, '\n'); 
     input.ignore(LARGE_NUMBER, '\n'); 
    } 
    return str; 
} 

LARGE_NUMBER może być std::numeric_limits<std::streamsize>::max() jeśli nie masz dobry powód, aby mieć mniejszą liczbę (myślę ataków DOS)

TIP Rozważ przekazanie str przez odniesienie. Odczytując ten sam ciąg za każdym razem, można uniknąć wielu alokacji, które zazwyczaj są przyczyną numer 1 powolnego działania programu.

TIP Rozważ użycie pliku memoery odwzorowane (Zwiększ iostreams, Zwiększ Interpocess lub mmap(1))

+1

Sprawdzałbym powodzenie wywołania 'getline'. Nie jestem pewien, czy gwarantuje on pusty ciąg w razie awarii. –

+0

+1 za to. Głównie wskazywałam na 'ignore' oczywiście :) – sehe

+1

LARGE_NUMBER __should__ be' std :: numeric_limits :: max() ', jest to specjalny przypadek i mówi' ignore' aby nie policzyć znaków, tylko poszukaj ogranicznika. – Blastfurnace

2

Memory-mapping plik nie ładuje go do pamięci RAM. Zajmuje on wirtualną przestrzeń adresową dla procesu, ale nie fizyczną pamięć RAM. Wywołanie systemowe mmap po prostu zawiedzie w systemie 32-bitowym, ponieważ 4GiB wirtualnej przestrzeni adresowej jest niewystarczające dla pliku 50GiB. W systemie 64-bitowym zajmuje to mikrosekundy. (Nie odczytano dysku, ponieważ plik jest już otwarty, więc metadane pliku są już załadowane.)

Tylko strony, z których faktycznie czytasz, są ładowane z dysku, a strony można ponownie usunąć, gdy system operacyjny chce odzyskać część pamięci . (Ponieważ je ponownie przeczytasz, system operacyjny może ponownie prześ leć z dysku. Jest to podobne do zamiany ich na miejsce/plik strony, ale bez koniecznoś ci pisania, ponieważ jest już czysta kopia na dysku.)

Mapowanie pamię ci pozwala Twój proces odczytuje strony pamięci podręcznej stron OS, zamiast tworzyć ich kopie za pomocą wywołania systemowego read.

Have a look at wikipedia, aby uzyskać więcej informacji.

Powiązane problemy