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