Lecture rapide du fichier texte en C++

j'écris actuellement un programme en c++ qui inclut la lecture de beaucoup de gros fichiers texte. Chacune a ~400.000 lignes avec dans les cas extrêmes 4000 caractères ou plus par ligne. Juste pour tester, j'ai lu un des fichiers en utilisant ifstream et l'implémentation offerte par cplusplus.com. Cela a pris environ 60 secondes, ce qui est bien trop long. Maintenant, je me demandais s'il y avait un moyen simple d'améliorer la vitesse de lecture?

modifier: Le code que j'utilise est plus ou moins ce:

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

edit 2: Le fichier que j'ai lu est seulement 82 MO. J'ai surtout dit qu'elle pourrait atteindre 4000 parce que j'ai pensé qu'il pourrait être nécessaire de savoir pour faire tampon.

edit 3: je vous Remercie tous pour vos réponses, mais il semble qu'il n'y a pas beaucoup de place pour améliorer compte tenu de mon problème. J'ai utiliser readline, puisque je veux compter le nombre de lignes. L'instanciation de l'ifstream en tant que binaire n'a pas rendu la lecture plus rapide non plus. Je vais essayer de paralléliser autant que je peux, qui doit travailler au moins.

edit 4: Il y a donc des choses que je peux faire. Grand merci à sehe de mettre autant de temps à cela, je l'apprécie beaucoup! = )

47
demandé sur Arne 2013-07-29 17:12:45

6 réponses

Mises à jour: assurez-vous de vérifier l' (surprenant) les mises à jour ci-dessous, la réponse initiale


les fichiers cartographiés en mémoire m'ont bien servi 1 :

#include <boost/iostreams/device/mapped_file.hpp> // for mmap
#include <algorithm>  // for std::find
#include <iostream>   // for std::cout
#include <cstring>

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<const char*>(memchr(f, '\n', l-f))))
            m_numLines++, f++;

    std::cout << "m_numLines = " << m_numLines << "\n";
}

cela devrait être assez rapide.

mise à Jour

dans le cas où il vous aide à tester cette approche, voici une version en utilisant mmap directement au lieu d'utiliser Boost: voir en direct sur Coliru

#include <algorithm>
#include <iostream>
#include <cstring>

// for mmap:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

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<const char*>(memchr(f, '\n', l-f))))
            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<const char*>(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;
}

mise à Jour

le dernier morceau de performance que j'ai pu extraire de ceci, je l'ai trouvé en regardant la source de GNU coreutils wc . À mon grand étonnement en utilisant le code suivant (grandement simplifié) adapté de wc fonctionne dans environ 84% du temps prises 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 ici: comment analyser les flotteurs séparés par l'espace en C++ rapidement?

58
répondu sehe 2017-05-23 12:03:05

4000 * 400,000 = 1,6 Go si vous êtes disque dur n'est pas un SSD, vous êtes susceptible d'obtenir ~100 Mo/s lecture séquentielle. C'est 16 secondes juste en I/O.

puisque vous ne donnez pas de détails sur le code spécifique que vous utilisez ou comment vous devez analyser ces fichiers (avez-vous besoin de le lire ligne par ligne, Le système a-t-il beaucoup de mémoire vive?pourriez-vous lire l'ensemble du fichier dans un grand tampon de mémoire vive, puis l'analyser? Il y a peu que vous puissiez faire pour accélérer le processus.

Les fichiers cartographiés en mémoire n'offrent aucune amélioration de performance lors de la lecture séquentielle d'un fichier. Il serait peut-être préférable d'analyser manuellement de grands segments pour trouver de nouvelles lignes plutôt que d'utiliser "getline".

EDIT après avoir fait un peu d'apprentissage (merci @sehe). Voici la solution de mappage de mémoire que j'utiliserais probablement.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>

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;
}
8
répondu Louis Ricci 2013-07-29 14:47:07

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 stringstream et la performance, la lecture d'un fichier texte ligne par ligne, est exactement le même.

std::stringstream stream;
std::string line;
while(std::getline(stream, line)) {
}

cela prend 1426ms sur un fichier de 106MO.

std::ifstream stream;
std::string 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) {
}

This 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 (i.e. longueur maximale pour chaque ligne dans le fichier d'entrée).

3
répondu user2434119 2017-12-15 09:51:41

avez-vous de 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 flux binaires, ou pour les blocs de données.

2
répondu utnapistim 2013-07-29 13:31:52

Utiliser Random file access ou utiliser binary mode . pour séquentielle, c'est grand, mais encore cela dépend de ce que vous lisez.

1
répondu Shumail Mohyuddin 2013-07-29 13:22:26

comme quelqu'un avec un peu d'arrière-plan dans la programmation compétitive, je peux vous dire: au moins pour des choses simples comme le parsing entier le coût principal en C est le verrouillage des flux de fichiers (qui est par défaut fait pour le multi-threading). Utilisez plutôt les versions unlocked_stdio ( fgetc_unlocked() , fread_unlocked() ). Pour C++, la règle courante 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 voici mon code d'analyse standard. C'est un lot plus rapide que scanf, comme je l'ai dit principalement en raison de ne pas verrouiller le flux. Pour moi, il était aussi rapide que le meilleur mmap codé à la main ou les versions tamponnées personnalisées que j'avais utilisées auparavant, 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 numérique entre deux entiers).

et bien sûr éviter l'allocation de mémoire si possible...

1
répondu Jo So 2017-05-12 01:35:05