Разбор потока символов - PullRequest
0 голосов
/ 04 декабря 2018

Скажем, у меня есть файл, похожий на этот:

*SP "<something>"
*VER "<something>"

*NAME_MAP
*1 abc
*2 def
...
...

*D_NET *1 <some_value>
*CONN
<whatever>
<whatever>
*CAP
*1:1 *2:2 <whatever_value>
*1:3 *2:4 <whatever_value>
*RES
<whatever>
<whatever>

Позвольте мне описать файл один раз, прежде чем я начну описывать свою проблему.Файл начинается с некоторых заголовков.Раздел NAME_MAP содержит информацию о сопоставлении имени и идентификатора, присвоенного этому.Этот идентификатор будет использоваться везде позже, когда я захочу указать соответствующее имя.

Раздел D_NET имеет 3 подраздела, CONN, CAP, RES.

Мне нужно собрать некоторые данные из этого файла,Данные, которые мне нужны, связаны с D_NET.Мне нужно

*D_NET *1 <some_value>

отображение * 1 из этой строки, которое в этом случае будет abc.

Второе, что мне нужно, из раздела CAP раздела D_NET.Что бы там ни было в разделе CAP, оно мне понадобится.

Наконец, мои данные будут выглядеть как хеш-код:

* 1 -> * 1, * 2 (В данном случае просточтобы вы поняли) abc -> abc, def (это то, что я хочу)

Надеюсь, я до сих пор ясен.

Поскольку размер файла огромен, в нескольких Гб, у меня естьвыяснил, что лучший способ прочитать файл - это отобразить его в памяти.Сделал это с помощью mmap.Вот так:

char* data = (char*)mmap(0, file.st_size, PROT_READ, MAP_PRIVATE, fileno(file), 0);

Итак, данные, указанные в mmap, - это просто поток символов.Теперь мне нужно получить из него вышеупомянутые данные.

Чтобы решить эту проблему, я думаю, что я мог бы использовать здесь сначала некоторый токенизатор (boost / tokenizer?), Чтобы разделить символ новой строки, а затем проанализировать их.линии, чтобы получить нужные данные.Кто все согласится со мной на это?Что еще вы мне предложите, если не согласитесь с этим?Пожалуйста, предложите.

Как бы вы предложили это сделать?Я открыт для любого быстрого алгоритма.

1 Ответ

0 голосов
/ 05 декабря 2018

Мне стало любопытно, на какой прирост производительности вы надеетесь, используя mmap, поэтому я собрал два теста, читая файлы из моей медиатеки (рассматривая их как текстовые файлы).Один использует getline подход, а другой mmap.Входные данные были:

files: 2012
lines: 135371784
bytes: 33501265769 (31 GiB)

Сначала вспомогательный класс, используемый в обоих тестах для чтения списка файлов:

filelist.hpp

#pragma once

#include <fstream>
#include <iterator>
#include <string>
#include <vector>

class Filelist {
    std::vector<std::string> m_strings;
public:
    Filelist(const std::string& file) :
        m_strings()
    {
        std::ifstream is(file);
        for(std::string line; std::getline(is, line);) {
            m_strings.emplace_back(std::move(line));
        }
        /*
        std::copy(
            std::istream_iterator<std::string>(is),
            std::istream_iterator<std::string>(),
            std::back_inserter(m_strings)
        );
        */
    }

    operator std::vector<std::string> () { return m_strings; }
};

getline.cpp

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <iomanip>
#include "filelist.hpp"

int main(int argc, char* argv[]) {
    std::vector<std::string> args(argv+1, argv+argc);
    if(args.size()==0) {
        Filelist tmp("all_files");
        args = tmp;
    }

    unsigned long long total_lines=0;
    unsigned long long total_bytes=0;

    for(const auto& file : args) {
        std::ifstream is(file);
        if(is) {
            unsigned long long lco=0;
            unsigned long long bco=0;
            bool is_good=false;
            for(std::string line; std::getline(is, line); lco+=is_good) {
                is_good = is.good();
                bco += line.size() + is_good;
                // parse here
            }
            std::cout << std::setw(15) << lco << " " << file << "\n";
            total_lines += lco;
            total_bytes += bco;
        }
    }
    std::cout << "files processed: " << args.size() << "\n";
    std::cout << "lines processed: " << total_lines << "\n";
    std::cout << "bytes processed: " << total_bytes << "\n";
}

getline результат:

files processed: 2012
lines processed: 135371784
bytes processed: 33501265769

real    2m6.096s
user    0m23.586s
sys     0m20.560s

mmap.cpp

#include <iostream>
#include <fstream>
#include <vector>
#include <iomanip>
#include "filelist.hpp"

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

class File {
    int m_fileno;
public:
    File(const std::string& filename) :
        m_fileno(open(filename.c_str(), O_RDONLY, O_CLOEXEC))
    {
        if(m_fileno==-1)
            throw std::runtime_error("could not open file");
    }
    File(const File&) = delete;
    File(File&& other) :
        m_fileno(other.m_fileno)
    {
        other.m_fileno = -1;
    }
    File& operator=(const File&) = delete;
    File& operator=(File&& other) {
        if(m_fileno!=-1) close(m_fileno);
        m_fileno = other.m_fileno;
        other.m_fileno = -1;
        return *this;
    }
    ~File() {
        if(m_fileno!=-1) close(m_fileno);
    }
    operator int () { return m_fileno; }
};

class Mmap {
    File m_file;
    struct stat m_statbuf;
    char* m_data;
    const char* m_end;

    struct stat pstat(int fd) {
        struct stat rv;
        if(fstat(fd, &rv)==-1)
            throw std::runtime_error("stat failed");
        return rv;
    }
public:
    Mmap(const Mmap&) = delete;
    Mmap(Mmap&& other) :
        m_file(std::move(other.m_file)),
        m_statbuf(std::move(other.m_statbuf)),
        m_data(other.m_data),
        m_end(other.m_end)
    {
        other.m_data = nullptr;
    }
    Mmap& operator=(const Mmap&) = delete;
    Mmap& operator=(Mmap&& other) {
        m_file = std::move(other.m_file);
        m_statbuf = std::move(other.m_statbuf);
        m_data = other.m_data;
        m_end = other.m_end;
        other.m_data = nullptr;
        return *this;
    }

    Mmap(const std::string& filename) :
        m_file(filename),
        m_statbuf(pstat(m_file)),
        m_data(reinterpret_cast<char*>(mmap(0, m_statbuf.st_size, PROT_READ, MAP_PRIVATE, m_file, 0))),
        m_end(nullptr)
    {
        if(m_data==MAP_FAILED)
            throw std::runtime_error("mmap failed");
        m_end = m_data+size();
    }
    ~Mmap() {
        if(m_data!=nullptr)
            munmap(m_data, m_statbuf.st_size);
    }

    inline size_t size() const { return m_statbuf.st_size; }
    operator const char* () { return m_data; }

    inline const char* cbegin() const { return m_data; }
    inline const char* cend() const { return m_end; }
    inline const char* begin() const { return cbegin(); }
    inline const char* end() const { return cend(); }
};

int main(int argc, char* argv[]) {
    std::vector<std::string> args(argv+1, argv+argc);
    if(args.size()==0) {
        Filelist tmp("all_files");
        args = tmp;
    }

    unsigned long long total_lines=0;
    unsigned long long total_bytes=0;

    for(const auto& file : args) {
        try {
            unsigned long long lco=0;
            unsigned long long bco=0;
            Mmap im(file);
            for(auto ch : im) {
                if(ch=='\n') ++lco;
                ++bco;
            }
            std::cout << std::setw(15) << lco << " " << file << "\n";
            total_lines += lco;
            total_bytes += bco;
        } catch(const std::exception& ex) {
            std::clog << "Exception: " << file << " " << ex.what() << "\n";
        }
    }
    std::cout << "files processed: " << args.size() << "\n";
    std::cout << "lines processed: " << total_lines << "\n";
    std::cout << "bytes processed: " << total_bytes << "\n";
}

mmap результат:

files processed: 2012
lines processed: 135371784
bytes processed: 33501265769

real    2m8.289s
user    0m51.862s
sys     0m12.335s

Я выполнил тесты сразу после друг друга вот так:

% ./mmap
% time ./getline
% time ./mmap

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

Отказ от ответственности: У меня мало опыта с mmap так что, возможно, я использовал это неправильно, чтобы получить производительность, которую он может доставить при помощи текстовых файлов.

Обновление : я объединил все файлы в один файл 31 ГиБ и провел тестыснова.Результат был немного удивительным, и я чувствую, что что-то упустил.

getline результат:

files processed: 1
lines processed: 135371784
bytes processed: 33501265769

real    2m1.104s
user    0m22.274s
sys     0m19.860s

mmap результат:

files processed: 1
lines processed: 135371784
bytes processed: 33501265769

real    2m22.500s
user    0m50.183s
sys     0m13.124s
...