Скажем, у меня есть примерно такой файл:
*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?) Здесь сначала, чтобы разделить символ новой строки, а затем проанализировать эти строки, чтобы получить желаемые данные. Кто все со мной согласится? Что еще вы мне предложите, если не согласны с этим? Пожалуйста, предложите.
Как бы вы посоветовали это сделать? Я открыт для любого быстрого алгоритма.
От отображения в памяти строкового файла пользы очень мало. Придерживайтесь обычных парсеров, таких как bison / flex или lemon, и напишите грамматику, которая просто извлекает то, что вам нужно. Или даже просто читайте строку за строкой и выполняйте базовое сопоставление с образцом с помощью std::regex или около того ..
@TedLyngmo, Нет, это не займет много времени при выполнении mmap .. Мне Google сказал, что для обработки больших файлов это наиболее эффективный способ чтения файлов.
@Botje, std :: regex очень медленные .. Неправильно использовать.
Итак, вы в основном будете читать файл один time, искать записи *D_NET и разделы *CAP и как-то их подключать? Я не понимаю, как из вашего примера - а как насчет каких-то реальных данных? Что тогда? Вы хотите, чтобы это было в памяти или его нужно поместить в другой файл?
@TedLyngmo, да. Я мог бы прочитать файл, используя стандартные api cpp, и проанализировать их, просто чтобы получить нужную мне информацию, но, поскольку в моем случае файлы огромны, это было бы медленно. И это причина использования mmap. Надеюсь, что это ясно? Мои данные файла в настоящее время находятся в памяти, отмечены символом *. Мы могли бы анализировать его из самой памяти, чтобы получить информацию, вместо того, чтобы записывать ее в другие файлы. Для вас это тоже имеет смысл?
@RahulBhargava Пожалуйста, покажите эксперименты, которые вы провели, чтобы доказать, что "std :: regex работает медленно" при поиске, например, \n\*D_NET.
Как вы думаете, почему это будет медленно? mmap не увеличит скорость ваших приводов волшебным образом. Попробуйте открыть файл с помощью std::ifstream и просто std::getline() через него и сравните это с настройкой mmap и просканируйте его. Скорость сильно улучшилась?





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