2015-08-23 20 views
8

Molti altri post, come "Read whole ASCII file into C++ std::string" spiegano quali sono alcune opzioni ma non descrivono pro e contro di vari metodi in nessuna profondità. Voglio sapere perché un metodo è preferibile rispetto a un altro?Modo ottimale di leggere un file completo in una stringa usando fstream?

Tutti questi utilizzano std::fstream per leggere il file in un std::string. Non sono sicuro di quali siano i costi e i benefici di ciascun metodo. Supponiamo che questo sia per il caso comune in cui i file letti sono noti per essere di dimensioni ridotte e che la memoria possa facilmente adattarsi, la lettura chiara di un file multi-terrabyte in una memoria è una cattiva idea, indipendentemente da come lo si fa.

Il modo più comune dopo un paio di googles ricerche per leggere un intero file in uno std :: string comporta l'uso di std::getline e aggiungendo un carattere di nuova riga ad esso dopo ogni riga. Questo mi sembra inutile, ma c'è qualche motivo di prestazioni o compatibilità che questo sia l'ideale?

std::string Results; 
std::ifstream ResultReader("file.txt");  
while(ResultReader) 
{ 
    std::getline(ResultReader, Results); 
    Results.push_back('\n'); 
} 

Un altro modo in cui ho messo insieme è quello di cambiare il delimitatore getline quindi non è qualcosa nel file. Il char EOF non sembra essere nel mezzo del file, quindi sembra un candidato probabile. Questo include un cast quindi c'è almeno un motivo per non farlo, ma questo legge un file in una volta senza concatenazione di stringhe. Presumibilmente vi è ancora un costo per i controlli delimitatori. Ci sono altri buoni motivi per non farlo?

std::string Results; 
std::ifstream ResultReader("file.txt"); 
std::getline(ResultReader, Results, (char)std::char_traits<char>::eof()); 

Il cast significa che su sistemi che definiscono std :: :: char_traits eof() come qualcosa di diverso da -1 potrebbe avere problemi. È un motivo pratico per non scegliere questo rispetto ad altri metodi che utilizzano std::getline e string::push_pack('\n').

Come fa queste confronta con gli altri modi di leggere il file in una sola volta come in questa domanda: Read whole ASCII file into C++ std::string

std::ifstream ResultReader("file.txt"); 
std::string Results((std::istreambuf_iterator<char>(ResultReader)), 
        std::istreambuf_iterator<char>()); 

Sembrerebbe questo sarebbe meglio. Trasferisce quasi tutto il lavoro sulla libreria standard che dovrebbe essere fortemente ottimizzato per la piattaforma indicata. Non vedo alcun motivo per i controlli oltre alla validità del flusso e alla fine del file. È questo ideale o ci sono problemi con ciò che non si vede.

Lo standard o i dettagli di alcune implementazioni forniscono motivi per preferire un metodo piuttosto che un altro? Ho perso qualche metodo che potrebbe rivelarsi ideale in un'ampia varietà di circostanze?

Qual è il modo più semplice, più idiomatico, più efficiente e conforme allo standard per leggere un intero file in un std::string?

EDIT - 2 Questa domanda mi ha spinto a scrivere una piccola suite di benchmark. Sono licenza MIT ed è disponibile su GitHub a: https://github.com/Sqeaky/CppFileToStringExperiments

più veloce - TellSeekRead e CTellSeekRead- Questi hanno il sistema di fornire un facile per ottenere la dimensione e legge il file in una sola volta.

Più veloce - Getline Appending e Eof - Il controllo dei caratteri non sembra imporre alcun costo.

Fast - RdbufMove e Rdbuf - Lo std :: move sembra non fare alcuna differenza nel rilascio.

Lento - Iterator, BackInsertIterator e AssignIterator - Qualcosa non funziona con iteratori e flussi di input. Il lavoro è fantastico nella memoria, ma non qui. Detto questo, alcuni di questi sono più veloci di altri.

Ho aggiunto tutti i metodi suggeriti finora, compresi quelli nei collegamenti. Sarei grato se qualcuno potesse eseguirlo su Windows e con altri compilatori. Al momento non ho accesso a una macchina con NTFS e si è notato che questo e i dettagli del compilatore potrebbero essere importanti.

Per quanto riguarda la misurazione della semplicità e dell'idiomatica, come li misuriamo oggettivamente? La semplicità sembra fattibile, forse usare qualcosa di LOC e di complessità ciclomatica, ma come qualcosa di idiomatico sembra puramente soggettivo.

+4

possibile duplicato del [Leggi l'intero file ASCII in C++ std :: string ] (http://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring) –

+1

La risposta collegata utilizza seek/tell per trovare la lunghezza del file. Se sai che è un file normale, è più semplice usare stat. – stark

+1

'stat' è conforme allo standard, ma lo standard è POSIX. – user4581301

risposta

1

Ci sono due grandi difficoltà con la tua domanda. Innanzitutto, lo standard non impone alcuna implementazione particolare (sì, quasi tutti hanno iniziato con la stessa implementazione, ma lo hanno modificato nel tempo e il codice I/O ottimale per NTFS, ad esempio, sarà diverso da quello ottimale I/O code per ext4), quindi è possibile (anche se in qualche modo improbabile) che un particolare approccio sia più veloce su una piattaforma, ma non un'altra. Secondo, c'è un po 'di difficoltà nel definire "ottimale"; Immagino tu intenda "il più veloce", ma non è necessariamente così.

Ci sono approcci che sono C++ idiomatici e perfettamente soddisfacenti, ma che difficilmente danno prestazioni meravigliose. Se il tuo obiettivo è finire con un singolo std::string, usare std::getline(std::ostream&, std::string&) molto probabilmente più lento del necessario. La chiamata std::getline() deve cercare '\n' e occasionalmente riallocare e copiare la destinazione std::string. Anche così, è incredibilmente semplice e facile da capire. Ciò potrebbe essere ottimale dal punto di vista della manutenzione, assumendo che non sia necessaria la prestazione più rapida possibile. Questo sarà anche un buon approccio se il tuo non ha il bisogno dell'intero file in un unico gigante std::string alla volta. Sarai molto parsimonioso con la memoria.

Un approccio che è probabile più efficiente è quello di manipolare il buffer di lettura:

std::string read_the_whole_file(std::ostream& ostr) 
{ 
    std::ostringstream sstr; 
    sstr << ostr.rdbuf(); 
    return sstr.str(); 
} 

Personalmente, sono le stesse probabilità di usare std::fopen() e std::fread() (e std::unique_ptr<FILE>) perché, su Windows, almeno, è Otterrà un messaggio di errore migliore quando std::fopen() fallisce rispetto a quando la costruzione di un oggetto del flusso di file non riesce. Considero il messaggio di errore migliore un fattore importante al momento di decidere quale approccio è ottimale.

+1

Ho scritto questo e i 3 metodi in cui ho scritto in un microbenchmark: https://github.com/Sqeaky/CppFileToStringExperiments. Hai accesso immediato a una macchina con NTFS? Io non. In qualche modo le due ingenue strategie getline erano le più veloci, quindi l'accesso diretto al buffer di lettura era marginalmente ma misurabilmente più lento, quindi finalmente il metodo iteratore era terribilmente lento. Sono d'accordo sul fatto che il messaggio di errore è importante, ma la sua qualità è difficile da misurare empiricamente. – Sqeaky

3

Che è un più semplice, più idiomatica, migliori prestazioni e standard di modo compatibile di leggere un intero file in uno std :: string?

queste sono richieste molto contraddittorie, una delle quali probabilmente riduce l'altra. il codice più semplice non sarà il più veloce o più idiomatico.

dopo aver esplorato questa zona per un po 'ho imparato ad alcune conclusioni:
1) la sanzione più prestazioni che causano è l'azione in sé IO - meno azioni intraprese IO - il più veloce il codice
2) allocazioni di memoria anche piuttosto costoso, ma non è così costoso come l'IO
3) la lettura in formato binario è più veloce rispetto alla lettura come testo
4) utilizzando l'API di sistema operativo sarà probabilmente più veloce di C++ flussi
5) std::ios_base::sync_with_stdio in realtà non effettuare il esibizione, è una leggenda metropolitana.

utilizzando std::getline non è probabilmente la scelta migliore se l'esecuzione è necessaria a causa di questi motivi: effettuerà azioni N IO e allocazioni N per N righe.

Un compromesso che è veloce, standard ed elegante è quello di ottenere la dimensione del file, allocare tutta la memoria in una sola volta, quindi la lettura del file in una sola volta:

std::ifstream fileReader(<your path here>,std::ios::binary|std::ios::ate); 
if (fileReader){ 
    auto fileSize = fileReader.tellg(); 
    fileReader.seekg(std::ios::beg); 
    std::string content(fileSize,0); 
    fileReader.read(&content[0],fileSize); 
} 

spostare il contenuto intorno per evitare un copie con meno necessità.

+0

L'ho aggiunto alla suite di riferimento I collegata nella domanda. Sono d'accordo che questo metodo è buono e il più veloce finora, ma non sono d'accordo con alcuni dei tuoi punti. Non credo che il binario sia più veloce del testo, non ho visto termini di differenza di millisecondi, su 1000 iterazioni. Penso che la risposta a tutta questa domanda possa essere semplice come il punto 1. – Sqeaky

+0

Il costruttore 'std :: string (size_t, char)' non solo alloca e imposta la dimensione, ma riempie anche la memoria allocata con il dato char.Vorrei usare 'std :: unique_ptr (nuovo char [fileSize]);' o forse 'make_unique' - in questo modo avrai un'eccezione di sicurezza ed eviterà anche di inizializzare il buffer potenzialmente grande con' '\ 0'' –

2

This website ha un buon confronto su diversi metodi per farlo. Quello che attualmente utilizzo è:

std::string read_sequence() { 
    std::ifstream f("sequence.fasta"); 
    std::ostringstream ss; 
    ss << f.rdbuf(); 
    return ss.str(); 
} 

Se i file di testo sono separati da newline, questo li manterrà. Se si desidera rimuovere che, per esempio (che è il mio caso, la maggior parte delle volte), si può semplicemente aggiungere una chiamata a qualcosa come

auto s = ss.str(); 
s.erase(std::remove_if(s.begin(), s.end(), 
     [](char c) { return c == '\n'; }), s.end()); 
+1

leggerò il sito web che hai, grazie per l'espressione lambda remove_if, è un compito semplice da realizzare. Il tuo buffer di lettura per il metodo stringstream non sembra materialmente diverso dal metodo di Max, lo std :: move non sembra fare nulla che un buon compilatore non faccia già. Ho aggiunto RdbufMove come test per la suite di benchmark questa domanda mi sta facendo scrivere: https://github.com/Sqeaky/CppFileToStringExperiments – Sqeaky

+0

Usa mmap e sottoclasse stringa per comportarsi correttamente. Windows [sembra avere una funzione simile] (https://msdn.microsoft.com/en-us/library/aa366542 (v = vs.85) .aspx). – msw

+0

@msw Non ho idea di cosa abbiate detto, né ho accesso a una macchina Windows. Potresti spiegare per favore? – Sqeaky