Lecture rapide de fichiers texte en c ++

Je suis en train d’écrire un programme en c ++ qui comprend la lecture de nombreux fichiers texte volumineux. Chacun a ~ 400.000 lignes avec, dans les cas extrêmes, 4000 caractères ou plus par ligne. Juste pour le test, je lis l’un des fichiers en utilisant ifstream et l’implémentation offerte par cplusplus.com. Cela a pris environ 60 secondes, ce qui est beaucoup trop long. Maintenant, je me demandais, existe-t-il un moyen simple d’améliorer la vitesse de lecture?

edit: Le code que j’utilise est plus ou moins ceci:

ssortingng tmpSsortingng; ifstream txtFile(path); if(txtFile.is_open()) { while(txtFile.good()) { m_numLines++; getline(txtFile, tmpSsortingng); } txtFile.close(); } 

edit 2: Le fichier que j’ai lu est seulement gros comme 82 Mo J’ai principalement dit qu’il pourrait atteindre 4000 car je pensais qu’il pourrait être nécessaire de savoir pour faire de la mise en mémoire tampon.

edit 3: Merci à tous pour vos réponses, mais il semble qu’il n’y ait pas beaucoup de place à améliorer compte tenu de mon problème. Je dois utiliser readline, car je veux compter le nombre de lignes. L’instanciation de l’ifstream en binary n’a pas non plus accéléré la lecture. Je vais essayer de le mettre en parallèle autant que possible, cela devrait fonctionner au moins.

edit 4: Apparemment, il y a des choses que je peux faire. Grand merci à vous d’avoir mis tant de temps dans cela, je l’apprécie beaucoup! =)

Mises à jour: Assurez-vous de vérifier les mises à jour (surprenantes) sous la réponse initiale


Les fichiers mappés en mémoire m’ont bien servi 1 :

 #include  // for mmap #include  // for std::find #include  // for std::cout #include  int main() { boost::iostreams::mapped_file mmap("input.txt", boost::iostreams::mapped_file::readonly); auto f = mmap.const_data(); auto l = f + mmap.size(); uintmax_t m_numLines = 0; while (f && f!=l) if ((f = static_cast(memchr(f, '\n', lf)))) m_numLines++, f++; std::cout << "m_numLines = " << m_numLines << "\n"; } 

Cela devrait être assez rapide.

Mettre à jour

Au cas où cela vous aiderait à tester cette approche, voici une version utilisant mmap directement au lieu d'utiliser Boost: voyez-la en direct sur Coliru

 #include  #include  #include  // for mmap: #include  #include  #include  const char* map_file(const char* fname, size_t& length); int main() { size_t length; auto f = map_file("test.cpp", length); auto l = f + length; uintmax_t m_numLines = 0; while (f && f!=l) if ((f = static_cast(memchr(f, '\n', lf)))) m_numLines++, f++; std::cout << "m_numLines = " << m_numLines << "\n"; } void handle_error(const char* msg) { perror(msg); exit(255); } const char* map_file(const char* fname, size_t& length) { int fd = open(fname, O_RDONLY); if (fd == -1) handle_error("open"); // obtain file size struct stat sb; if (fstat(fd, &sb) == -1) handle_error("fstat"); length = sb.st_size; const char* addr = static_cast(mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0u)); if (addr == MAP_FAILED) handle_error("mmap"); // TODO close fd at some point in time, call munmap(...) return addr; } 

Mettre à jour

La dernière partie de la performance que j'ai pu tirer de ce que j'ai trouvé en regardant la source de GNU coreutils wc . A ma grande surprise, en utilisant le code suivant (grandement simplifié) adapté de wc tourne dans environ 84% du temps pris avec le fichier mappé en mémoire ci-dessus:

 static uintmax_t wc(char const *fname) { static const auto BUFFER_SIZE = 16*1024; int fd = open(fname, O_RDONLY); if(fd == -1) handle_error("open"); /* Advise the kernel of our access pattern. */ posix_fadvise(fd, 0, 0, 1); // FDADVICE_SEQUENTIAL char buf[BUFFER_SIZE + 1]; uintmax_t lines = 0; while(size_t bytes_read = read(fd, buf, BUFFER_SIZE)) { if(bytes_read == (size_t)-1) handle_error("read failed"); if (!bytes_read) break; for(char *p = buf; (p = (char*) memchr(p, '\n', (buf + bytes_read) - p)); ++p) ++lines; } return lines; } 

1 voir par exemple le benchmark: Comment parsingr rapidement les flottants séparés en espace dans C ++?

4000 * 400 000 = 1,6 Go si votre disque dur n’est pas un disque SSD, vous obtiendrez probablement une lecture séquentielle d’environ 100 Mo / s. C’est 16 secondes juste en I / O.

Étant donné que vous n’élaborez pas le code spécifique que vous utilisez ou comment vous devez parsingr ces fichiers (avez-vous besoin de le lire ligne par ligne, le système a-t-il beaucoup de mémoire vive? et ensuite parsingr?) Il y a peu de choses que vous pouvez faire pour accélérer le processus.

Les fichiers mappés en mémoire n’offrent aucune amélioration des performances lors de la lecture séquentielle d’un fichier. Peut-être que l’parsing manuelle de gros blocs pour de nouvelles lignes plutôt que d’utiliser “getline” offrirait une amélioration.

EDIT Après quelques apprentissages (merci @sehe). Voici la solution mappée en mémoire que j’utiliserais probablement.

 #include  #include  #include  #include  #include  #include  #include  #include  int main() { char* fName = "big.txt"; // struct stat sb; long cntr = 0; int fd, lineLen; char *data; char *line; // map the file fd = open(fName, O_RDONLY); fstat(fd, &sb); //// int pageSize; //// pageSize = getpagesize(); //// data = mmap((caddr_t)0, pageSize, PROT_READ, MAP_PRIVATE, fd, pageSize); data = mmap((caddr_t)0, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); line = data; // get lines while(cntr < sb.st_size) { lineLen = 0; line = data; // find the next line while(*data != '\n' && cntr < sb.st_size) { data++; cntr++; lineLen++; } /***** PROCESS LINE *****/ // ... processLine(line, lineLen); } return 0; } 

Neil Kirk, malheureusement je ne peux pas répondre à votre commentaire (pas assez de réputation) mais j’ai fait un test de performance sur ifstream un ssortingngstream et la performance, en lisant un fichier texte ligne par ligne, est exactement la même.

 std::ssortingngstream stream; std::ssortingng line; while(std::getline(stream, line)) { } 

Cela prend 1426ms sur un fichier 106MB.

 std::ifstream stream; std::ssortingng line; while(ifstream.good()) { getline(stream, line); } 

Cela prend 1433ms sur le même fichier.

Le code suivant est plus rapide à la place:

 const int MAX_LENGTH = 524288; char* line = new char[MAX_LENGTH]; while (iStream.getline(line, MAX_LENGTH) && strlen(line) > 0) { } 

Cela prend 884ms sur le même fichier. C’est juste un peu délicat puisque vous devez définir la taille maximale de votre tampon (c’est-à-dire la longueur maximale de chaque ligne du fichier d’entrée).

Devez-vous lire tous les fichiers en même temps? (au début de votre application par exemple)

Si vous le faites, envisagez de paralléliser l’opération.

Dans les deux cas, envisagez d’utiliser des stream binarys, ou des lectures non différées pour des blocs de données.

Utilisez l’ Random file access ou utilisez binary mode . pour séquentiel, c’est gros mais cela dépend encore de ce que vous lisez.

En tant que personne avec un peu d’expérience en programmation compétitive, je peux vous dire: Au moins pour des choses simples comme l’parsing d’entiers, le principal coût en C est de verrouiller les stream de fichiers (par défaut pour le multi-threading). Utilisez plutôt les versions unlocked_stdio ( fgetc_unlocked() , fread_unlocked() ). Pour C ++, la tradition est d’utiliser std::ios::sync_with_stdio(false) mais je ne sais pas si c’est aussi rapide que unlocked_stdio .

Pour référence ici est mon code d’parsing standard d’entier. C’est beaucoup plus rapide que scanf, comme je l’ai dit principalement parce que je ne verrouille pas le stream. Pour moi, c’était aussi rapide que les meilleures versions mmap codées à la main ou tamponnées personnalisées que j’avais utilisées précédemment, sans la dette de maintenance insensée.

 int readint(void) { int n, c; n = getchar_unlocked() - '0'; while ((c = getchar_unlocked()) > ' ') n = 10*n + c-'0'; return n; } 

(Note: Celui-ci ne fonctionne que s’il y a précisément un caractère non-chiffre entre deux nombres entiers).

Et bien sûr éviter l’allocation de mémoire si possible …