Анализировать поток символов

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

*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?) Здесь сначала, чтобы разделить символ новой строки, а затем проанализировать эти строки, чтобы получить желаемые данные. Кто все со мной согласится? Что еще вы мне предложите, если не согласны с этим? Пожалуйста, предложите.

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

Это какой-то официальный формат файла, для которого можно прочитать спецификацию? Если вам нужно просмотреть файл только один раз, чтобы собрать данные, интересно, действительно ли нужен mmap? Разве создание этого mmap не займет много времени, прежде чем вы сможете приступить к синтаксическому анализу?

Ted Lyngmo 04.12.2018 10:14

От отображения в памяти строкового файла пользы очень мало. Придерживайтесь обычных парсеров, таких как bison / flex или lemon, и напишите грамматику, которая просто извлекает то, что вам нужно. Или даже просто читайте строку за строкой и выполняйте базовое сопоставление с образцом с помощью std::regex или около того ..

Botje 04.12.2018 10:20

@TedLyngmo, Нет, это не займет много времени при выполнении mmap .. Мне Google сказал, что для обработки больших файлов это наиболее эффективный способ чтения файлов.

Rahul Bhargava 05.12.2018 06:05

@Botje, std :: regex очень медленные .. Неправильно использовать.

Rahul Bhargava 05.12.2018 06:05

Итак, вы в основном будете читать файл один time, искать записи *D_NET и разделы *CAP и как-то их подключать? Я не понимаю, как из вашего примера - а как насчет каких-то реальных данных? Что тогда? Вы хотите, чтобы это было в памяти или его нужно поместить в другой файл?

Ted Lyngmo 05.12.2018 07:50

@TedLyngmo, да. Я мог бы прочитать файл, используя стандартные api cpp, и проанализировать их, просто чтобы получить нужную мне информацию, но, поскольку в моем случае файлы огромны, это было бы медленно. И это причина использования mmap. Надеюсь, что это ясно? Мои данные файла в настоящее время находятся в памяти, отмечены символом *. Мы могли бы анализировать его из самой памяти, чтобы получить информацию, вместо того, чтобы записывать ее в другие файлы. Для вас это тоже имеет смысл?

Rahul Bhargava 05.12.2018 08:38

@RahulBhargava Пожалуйста, покажите эксперименты, которые вы провели, чтобы доказать, что "std :: regex работает медленно" при поиске, например, \n\*D_NET.

Botje 05.12.2018 09:00

Как вы думаете, почему это будет медленно? mmap не увеличит скорость ваших приводов волшебным образом. Попробуйте открыть файл с помощью std::ifstream и просто std::getline() через него и сравните это с настройкой mmap и просканируйте его. Скорость сильно улучшилась?

Ted Lyngmo 05.12.2018 10:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
118
1

Ответы 1

Мне стало любопытно, на какой прирост производительности вы надеетесь при использовании 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

Другие вопросы по теме