Подтвердить что ты не робот

Быстрое чтение текстового файла в С++

В настоящее время я пишу программу на С++, которая включает в себя чтение больших текстовых файлов. Каждый из них имеет ~ 400 000 строк, в крайних случаях 4000 или более символов в строке. Просто для тестирования я прочитал один из файлов, используя ifstream и реализацию, предлагаемую cplusplus.com. Это заняло около 60 секунд, что слишком долго. Теперь мне было интересно, есть ли простой способ улучшить скорость чтения?

изменить: Код, который я использую, более или менее таков:

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

edit 2: Файл, который я читаю, имеет только 82 МБ. Я в основном сказал, что он может достичь 4000, потому что я думал, что, возможно, необходимо знать, чтобы сделать буферизацию.

edit 3: Спасибо всем за ваши ответы, но кажется, что у меня не так много возможностей для улучшения, учитывая мои проблемы. Мне нужно использовать readline, так как я хочу подсчитать количество строк. Игнорирование ifstream как двоичного файла также не делало чтение быстрее. Я постараюсь распараллелить его как можно больше, что должно работать как минимум.

edit 4: Так что, видимо, есть некоторые вещи, которые я могу. Большое вам спасибо за то, что вы так много времени уделяете этому, я его очень ценю! =)

4b9b3361

Ответ 1

Обновления: Обязательно проверьте (удивительные) обновления ниже первоначального ответа


Файлы с отображением памяти хорошо меня поддержали 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";
}

Это должно быть довольно быстро.

Update

В случае, если это поможет вам протестировать этот подход, используйте используя mmap напрямую, вместо использования Boost: видеть его вживую на 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;
}

Update

Последний бит производительности, который я мог выжать из этого, я нашел, посмотрев на источник GNU coreutils wc. К моему удивлению, используя следующий (значительно упрощенный) код, адаптированный из wc , выполняется примерно в 84% времени, взятый с указанным выше файлом памяти:

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 см., например, эталонный показатель здесь: Как быстро разобрать поплавки с пространственным разделением на С++?

Ответ 2

4000 * 400 000 = 1,6 ГБ, если вы жесткий диск не SSD, вы, вероятно, получаете ~ 100 МБ/сек последовательное чтение. Это 16 секунд только в I/O.

Поскольку вы не уточняете конкретный код, который вы используете, или как вам нужно разбирать эти файлы (нужно ли это читать по очереди, система имеет много ОЗУ, вы можете прочитать весь файл в большой буфер памяти и затем проанализировать его?) Там мало что можно сделать, чтобы ускорить процесс.

Файлы с отображением памяти не будут улучшать производительность при чтении файла последовательно. Возможно, ручной анализ сильных фрагментов для новых строк, а не использование "getline", обеспечит улучшение.

EDIT После обучения (спасибо @sehe). Здесь, вероятно, будет использоваться решение с отображением памяти.

#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;
}

Ответ 3

Нейл Кирк, к сожалению, я не могу ответить на ваш комментарий (недостаточно репутации), но я выполнил тест производительности на ifstream stringstream и производительность, прочитав текстовый файл по строкам, точно так же.

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

Это занимает 1426 мс в файле размером 106 МБ.

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

Это занимает 1433 мс в одном файле.

Следующий код быстрее:

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

Это занимает 884 мс в одном файле. Это немного сложно, поскольку вам нужно установить максимальный размер вашего буфера (т.е. Максимальная длина для каждой строки во входном файле).

Ответ 4

Вам нужно одновременно прочитать все файлы? (например, в начале вашего приложения)

Если вы это сделаете, рассмотрите возможность распараллеливания операции.

В любом случае, рассмотрите возможность использования двоичных потоков или unbffered read для блоков данных.

Ответ 5

Используйте Random file access или используйте binary mode. для последовательного, это большое, но все же это зависит от того, что вы читаете.

Ответ 6

Как кто-то с небольшим опытом в конкурентном программировании, я могу вам сказать: по крайней мере, для простых вещей, таких как целочисленный синтаксический анализ, основная стоимость в C - это блокировка потоков файлов (что по умолчанию выполняется для многопоточности). Вместо этого используйте версии unlocked_stdio (fgetc_unlocked(), fread_unlocked()). Для С++ общепринятым является использование std::ios::sync_with_stdio(false), но я не знаю, было ли это так быстро, как unlocked_stdio.

Для справки здесь мой стандартный целочисленный код синтаксического анализа. Это намного быстрее, чем scanf, как я сказал в основном из-за того, что не блокировал поток. Для меня это было так же быстро, как лучшие ручные кодировки mmap или пользовательские буферизованные версии, которые я использовал ранее, без долгов безумного обслуживания.

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

(Примечание. Этот режим работает только в том случае, если между любыми двумя целыми числами существует ровно один символ без цифр).

И, конечно, избегайте распределения памяти, если это возможно...