LINUX.ORG.RU

Буфферизированное чтение бинарного файла на C++

 ,


0

2

Всегда как то с неохотой пользовался и не желал разбираться в плюсовых *stream, а тут понадобилось. В общем, задача - максимально быстро читать бинарные данные (например, int) из файла, заняв при этом не более точно известного количества памяти для этого чтения (данных много, счет идет на десятки-сотни мегабайт). Куда они потом идут - не суть.

мой вариант (черновой пример, показывающий идею).

std::ifstream file("data", std::ios::binary);
char* buffer = new char[MAX_MEMORY];
file.rdbuf()->pubsetbuf(buffer, MAX_MEMORY);
//blablabla
//читаем int
int value;
file.read(reinterpret_cast<char*>(&value, sizeof(value));
Вопросы: я правильно понимаю, что данные из файла будут читаться в мой буффер, кусками размером MAX_MEMORY, и когда я делаю read, я читаю из этого буфера? Вообще правильно я все делаю? Есть ли способ лучше (если говорить о другом способе, то требования: кроссплатформенность, не использовать никаких либ кроме stl и boost).



Последнее исправление: CatsCantFly (всего исправлений: 1)

данных много, счет идет на десятки-сотни мегабайт

Обзмеился. Данных много — это когда они не умещаются на терабайтный диск.

mix_mix ★★★★★
()

и когда я делаю read, я читаю из этого буфера?

Да, если file.rdbuf() не положит на соответствующий вызов std::streambuf* setbuf(char_type* s, std::streamsize n) вот тут есть картина маслом как эти буферы устроены: http://en.cppreference.com/w/cpp/io/basic_streambuf

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

Возможно, но не факт, это file.rdbuf() решает, вернее, реализация метода int_type underflow().

asaw ★★★★★
()
Ответ на: комментарий от annulen

У меня нет под рукой Windows что бы проверять на ней, поэтому и в приоритете изначально кроссплатформенные библиотечные решения. И все же вопрос - правильно ли я понимаю значение буффера в своем примере? Почему его содержимое не меняется в процессе чтения файла?

CatsCantFly
() автор топика
Ответ на: комментарий от annulen

Для кроссплатформенного mmap есть boost, который прекрасно работает. Только вот mmap далеко не всегда и не везде быстрее. На макоси, например, он очень долго стартует.

asaw ★★★★★
()

Стоит ли вообще городить свою внешнюю буфферизацию вида: читаем из стрима в буфер размером MAX_MEMORY, если запросили что то за границей буффера, то читаем новый кусок в буфер, или я смогу как то обойтись этой возней с streambuf и прочим? Вообще суть в том, что бы создать что то типа вектора из stl, по которому мы будем ходить итераторами (достаточно только вперед), только данные у него в файле, а в память мы можем за раз читать не больше MAX_MEMORY.

CatsCantFly
() автор топика

максимально быстро - это точно не стримы. это fopen/fread (они в разы быстрее стримов). и установить им буфер через setbuf.

Iron_Bug ★★★★★
()
Ответ на: комментарий от SZT

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

Iron_Bug ★★★★★
()
Ответ на: комментарий от CatsCantFly

Потому что реализация того буфера, что у тебя используется, просто, скорее всего, забивает на соответствующий вызов установки внешнего буфера и всё. Как ты, надеюсь, понял из того описания, что я тебе дал, file.rdbuf() (который является потомком basic_streambuf) выполняет роль не просто какого-то тупого буфера, а именно он и читает/записывает данные из/в файл, попутно буферизуя их.

asaw ★★★★★
()
Ответ на: комментарий от asaw

Я это понял, только нигде не нашел, что она должна забивать на него. Много где именно этот способ советуют для установки своего буфера.

CatsCantFly
() автор топика
Ответ на: комментарий от CatsCantFly

Я, честно говоря, вообще не понимаю зачем тебе нужно городить какой-то огород с буферизацией. Почему то, что есть по умолчанию, тебя не устраивает?

asaw ★★★★★
()
Ответ на: комментарий от CatsCantFly

Я это понял, только нигде не нашел, что она должна забивать на него.

А она и не должна, но МОЖЕТ)

asaw ★★★★★
()
Последнее исправление: asaw (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

стандарт ничего не гарантирует. а реализация, видимо, ничего и не делает. можно сделать кастомный класс с принудительным использованием буфера, как написано здесь:
http://stackoverflow.com/questions/1448467/initializing-a-c-stdistringstream-...

Iron_Bug ★★★★★
()
Ответ на: комментарий от asaw

Что именно есть по умолчанию? Затем, что нужно извлечь разумный максимум производительности чтения по одному числу из файла, учитывая, что у нас доступно определенное известное количество памяти под это дело, при этом кроссплатформенно. Можно сделать внешний буфер, но я думал обойтись малой кровью с помощью всех этих setbuf.

CatsCantFly
() автор топика
Ответ на: комментарий от CatsCantFly

Что именно есть по умолчанию?

Некий черный ящик, который, впрочем, должен хорошо справляться с большинством ситуаций.

Можно сделать внешний буфер, но я думал обойтись малой кровью с помощью всех этих setbuf.

Не знаю что ты имеешь в виду под словом «внешний» - можно сделать просто свой - унаследовать его от basic_streambuf или std::streambuf, а потом использовать хоть напрямую с std::ostream/std::istream. Но вот смысла особенного в этом я как-то не вижу. Разве что сделать внутри отдельный поток для чтения/записи, чтобы эти операции были асинхронными. Но стоит ли овчинка выделки?

asaw ★★★★★
()
Последнее исправление: asaw (всего исправлений: 1)
Ответ на: комментарий от mix_mix

Разве что включенна и началась подкачка / просто память подходит к концу. Или есть ещё варианты?

pon4ik ★★★★★
()
Ответ на: комментарий от asaw

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

CatsCantFly
() автор топика
Ответ на: комментарий от CatsCantFly

Так basic_streambuf ровно этим и занимается. Точнее, basic_streambuf - это просто готовый интерфейс для этих упраженний.

читать через istream::read в нее

Только нужно не забывать, что там будет ещё один буфер, который, собственно, и будет читать данные из файла.

asaw ★★★★★
()
Последнее исправление: asaw (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

Проверил только что. malloc() и read() размером с файл работает примерно с такой же скоростью, как и mmap() этого самого файла. mmap() чуть-чуть быстрее

SZT ★★★★★
()
Ответ на: комментарий от asaw

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

Iron_Bug ★★★★★
()
Ответ на: комментарий от SZT

см. мой пост выше. файл может быть сотню гигов, например.

Iron_Bug ★★★★★
()
Ответ на: комментарий от asaw

Хорошо, тогда такой вопрос: Как заставить работать использоваться буфер, который задается через std::filebuf::pubsetbuf? Потому что, эксперимент показывает, что он не используется. Понятно, что надо наследоваться от basic_filebuf или basic_streambuf, но вот что переопределять у них для этого, учитывая, что не известно, как он хранит указатель на свой буфер

CatsCantFly
() автор топика
Ответ на: комментарий от CatsCantFly

Вызову std::filebuf::pubsetbuf соответствует вызов http://en.cppreference.com/w/cpp/io/basic_filebuf/setbuf Соответственно, можно попробовать отнаследоваться от std::filebuf и переопределить его setbuf, посмотрев предварительно имеющуюся реализацию. Но, понятно, что это опасно потому что ты влезаешь в реализацию.

PS вот тут вот простенькая реализация буфера есть как пример: http://stackoverflow.com/a/11115421/2935339

asaw ★★★★★
()
Ответ на: комментарий от CatsCantFly

Ох блин, ну зачем в этих плюсах все так сложно делать надо? Зачем вообще какие-то там steam? Почему нельзя просто сделать mmap и работать потом с памятью как с массивом байтов(можно даже его в std::vector превратить, примерно как тут http://stackoverflow.com/a/8777619 описано)?

SZT ★★★★★
()

Да, кстати, попробуй поменять местами открытие файла и вызов file.rdbuf()->pubsetbuf(buffer, MAX_MEMORY);

asaw ★★★★★
()
Последнее исправление: asaw (всего исправлений: 1)
Ответ на: комментарий от mix_mix

На mmap можно вообще ощутимо так всосать из-за дорогущих пейдж фолтов.

У read будут возникать (или не возникать) те же самые пейдж фолты

annulen ★★★★★
()
Ответ на: комментарий от CatsCantFly

Ну ссылку я выше дал. По ней вот:

Notes

The conditions when this function may be used and the way in which the provided buffer is used is implementation-defined.

GCC 4.6 libstdc++
setbuf() may only be called when the std::basic_filebuf is not associated with a file (has no effect otherwise). With a user-provided buffer, reading from file reads n-1 bytes at a time.

Clang++3.0 libc++
setbuf() may be called after opening the file, but before any I/O (may crash otherwise). With a user-provided buffer, reading from file reads largest multiples of 4096 that fit in the buffer.

Visual Studio 2010
setbuf() may be called at any time, even after some I/O took place. Current contents of the buffer, if any, are lost.
The standard does not define any behavior for this function except that setbuf(0, 0) called before any I/O has taken place is required to set unbuffered output.

asaw ★★★★★
()
Ответ на: комментарий от asaw

У топикстартера в требованиях кроссплатформенность, а такое implementation-defined поведение явно не способствует этому.

SZT ★★★★★
()
template <class T> class bufferized_reader;

template<class T> class bufferized_reader_iterator
{
public:
    typedef T value_type;
    typedef size_t size_type;
    typedef typename std::vector<T>::difference_type difference_type;
    typedef size_t pos_type;
    typedef bufferized_reader<T> reader_type;

public:
    bufferized_reader_iterator(const reader_type& reader, const pos_type position) :
        current_pos(position), reader_ptr(&reader)
    {}
    
    bufferized_reader_iterator(const bufferized_reader_iterator& other) = default;
    bufferized_reader_iterator& operator=(const bufferized_reader_iterator& other) = default;
    
    bool operator==(const bufferized_reader_iterator& other) const
    {
        return reader_ptr == other.reader_ptr && current_pos == other.current_pos;
    }
    
    bool operator!=(const bufferized_reader_iterator& other) const
    {
        return !(operator==(other));
    }
    
    bufferized_reader_iterator<T> operator++()
    {
        ++current_pos;
        return *this;
    }
    
    bufferized_reader_iterator<T> operator++(int)
    {
        bufferized_reader_iterator<T> tmp(*this);
        ++current_pos;
        return tmp;
    }
    
    bufferized_reader_iterator<T>& operator+=(const difference_type offset)
    {
        current_pos += offset;
        return *this;
    }
    
    bufferized_reader_iterator<T>& operator-=(const difference_type offset)
    {
        current_pos -= offset;
        return *this;
    }
    
    bufferized_reader_iterator<T> operator+(const difference_type offset) const
    {
        bufferized_reader_iterator<T> tmp(*this);
        tmp.current_pos += offset;
        return tmp;
    }
    
    bufferized_reader_iterator<T> operator-(const difference_type offset) const
    {
        bufferized_reader_iterator<T> tmp(*this);
        tmp.current_pos -= offset;
        return tmp;
    }
    
    value_type operator*() const
    {
        return reader_ptr->operator[](current_pos);
    }
    

private:
    pos_type current_pos;
    const reader_type* reader_ptr;
};

template <class T> class bufferized_reader
{
public:
    typedef T value_type;
    typedef size_t size_type;
    typedef size_t pos_type;
    typedef bufferized_reader_iterator<T> iterator;

private:
    mutable std::ifstream stream;
    char* inner_buf;
    size_type buf_size;
    size_type file_size;
    
//    std::ifstream::pos_type buffer_pos;
public:
    bufferized_reader(const std::string& filename, const size_t buffer_size) : 
        stream(filename, std::ios::binary), buf_size(buffer_size)
    {
        inner_buf = new char[buffer_size];
        stream.rdbuf()->pubsetbuf(inner_buf, buffer_size);
        stream.unsetf(std::ios::skipws);
        stream.seekg(0, stream.end);
        file_size = stream.tellg();
        stream.seekg(0, stream.beg);
    }
    
    bufferized_reader(const bufferized_reader& other) = delete;
    bufferized_reader& operator=(const bufferized_reader& other) = delete;
    
    ~bufferized_reader()
    {
        stream.close();
        delete[] inner_buf;
    }
    
    value_type operator[](const pos_type pos) const
    {
        stream.seekg(pos * sizeof(value_type), stream.beg);
        return read_next();
    }
    
    value_type read_next() const
    {
        value_type res = 0;
        stream.read(reinterpret_cast<char*>(&res), sizeof(value_type));
        return res;
    }
    
    size_type size() const
    {
        return file_size / sizeof(value_type);
    }
    
    size_type raw_size() const
    {
        return file_size;
    }
    
    iterator begin() const
    {
        return iterator(*this, 0);
    }
    
    iterator end() const
    {
        return iterator(*this, size());
    }
    
};

Вот такая читалка в итоге получилась. Можно использовать как стандартные stl контейнеры, только для чтения.

CatsCantFly
() автор топика
Ответ на: комментарий от Iron_Bug

Эмм, а я так запиливал, просто чтением кусочками, а последний кусочек либо равен либо меньше установленного размера. Это не само быстрое?

deep-purple ★★★★★
()
Ответ на: комментарий от mix_mix

Да ты, как я погляжу - крайне экспертный балаболок. Ну давай мне - выдай куллстори. А какой, собственно, жопой можно получить пейджолт в ммапе и не получить в днищериде? Расскажи.

TrueTsar1C77
()

Если за это не убивать, за что тогда вообще убивать?

char* buffer = new char[MAX_MEMORY];

Шёл 2015-й год...

anonymous
()
Ответ на: комментарий от deep-purple

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

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

из смещений в файле

Ну вот то же самое примерно у меня и было. Размер буфера подбирался таким, чтобы хватило зацепить нужную область для обработки. Ну а двигался в файле fseek'ами, точно зная куда именно, т.к. предварительно проиндексировал целевые позиции. Эти же позиции еще и в т.н. гуй (не было там гуя, в прямом смысле) отдавались.

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

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

Iron_Bug ★★★★★
()
Ответ на: комментарий от CatsCantFly

поэтому и в приоритете изначально кроссплатформенные библиотечные решения

и чем не устроил QFile ?

x905 ★★★★★
()
Ответ на: комментарий от x905

Тем что в проекте не должно быть Qt, особенно ради чтения файлов. Есть STL и Boost, остальное только при крайней необходимости.

CatsCantFly
() автор топика
Ответ на: комментарий от CatsCantFly

а что мешало выбрать qt в начале проекта ? )
просто интересуюсь

x905 ★★★★★
()
Ответ на: комментарий от TrueTsar1C77

царь со своей допотопной сишкой в крестотреде... все в машину

ты уже бпф допилил?

anonymous
()
Ответ на: комментарий от mix_mix

Данных много — это когда они не умещаются на терабайтный диск.

Ггг. На устройстве с общих объемом RAM в 100 мегабайт и десяток - это много

annulen ★★★★★
()
Ответ на: комментарий от Iron_Bug

Это неправда. mmap всегда медленнее или равен по скорости.

Если данные влазят в память, то mmap пройдя разок по всему файлу весь его подсосет в page cache (допустим оно потом никуда не вытеснится),после чего скорость будет такая же, как если весь файл вручную прочитать в память и держать там.

Если данных больше чем памяти и чтение рандомно, то mmap будет вызывать page fault внутри которого будет сначала просматриваться VMA процесса,изучаться права, затем читаться нужный кусок памяти и потом создаваться PTE и подшиваться в найденную VMA процесса. Все это в дополнение пополнению page cache. В итоге простой pread выходит дешевле и быстрее (как минимум на x86_64 где сисколлы быстрее).

redbaron ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.