как добавить буфер записи в запись, определенную в io.h

Я использую write(fd, buffer, count) в io.h для записи буфера в файловый дескриптор.

Теперь я обнаружил огромную проблему с производительностью, потому что каждый раз, когда я вызываю «запись», он выполняет операцию ввода-вывода.

Я хочу добавить буфер перед записью содержимого в файл.

Как лучше всего это сделать?

Я искал и нашел это:

http://www.java2s.com/Tutorial/Cpp/0240__File-Stream/filedescriptoroutbuffer.htm

Должен ли я использовать этот метод и интегрировать его в свой код? Это хороший подход, который я должен использовать?

Но в нем я не вижу, как определить размер буфера.

Ниже приведен окончательный код:

class fdoutbuf : public std::streambuf {
    protected:
        enum { size = 4096 };
        int fd;    // file descriptor
        char buf_[size];
    public:
    // constructor
    fdoutbuf (int _fd) : fd(_fd) {
        setp(this->buf_, this->buf_ + size - 1);
    }
    protected:
    // write one character
    virtual int overflow (int c){
        if (c != EOF) {
            char z = c;
            *this->pptr() = z;
            this->pbump(1);
        }
        return this->sync() == -1? EOF: c;
    }
    virtual int sync(){
         if (this->pbase() == this->pptr()) {
            return 0;
        }
        int count(this->pptr() - this->pbase());
        int rc = write(fd, this->buf_, count);
        this->setp(this->buf_, this->buf_ + size - 1);
        return count == rc? 0: -1;
    }
};


class fdostream : public std::ostream {
  protected:
    fdoutbuf buf;
  public:
    fdostream (int fd) : std::ostream(0), buf(fd) {
        rdbuf(&buf);
    }
};

person performanceuser    schedule 10.02.2012    source источник


Ответы (2)


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

struct fdbuf: std::streambuf {
    enum { size = 4096 };
    fdbuf(int fd): fd_(fd) { this->setp(this->buf_, this->buf_ + size - 1); }
    int overflow(int c) {
        if (!traits_type::eq_int_type(c, traits_type::eof())) {
            *this->pptr() = traits_type::to_char_type(c);
            this->pbump(1);
        }
        return this->sync() == -1? traits_type::eof(): traits_type::not_eof(c);
    }
    int sync() {
        if (this->pbase() == this->pptr()) {
            return 0;
        }
        int count(this->pptr() - this->pbase());
        int rc = write(this->fd_, this->buf_, count);
        this->setp(this->buf_, this->buf_ + size - 1);
        return count == rc? 0: -1;
    }
    int  fd_;
    char buf_[size];
};

Кажется, стоит объяснить, что на самом деле делает вышеприведенный буфер потока (учитывая длинное обсуждение ниже). Итак, вот разбивка:

  • Код использует буфер фиксированного размера. Это просто для уменьшения размера примера: его можно легко расширить, чтобы использовать буфер переменного размера, который выделяется, например. используя std::vector<char>: размер станет параметром по умолчанию.
  • the function setp() sets up a buffer used by the stream buffer, consisting of three pointers:
    • pbase() is the start of the buffer, i.e. the first parameter to setp()
    • epptr() это конец буфера, т.е. второй параметр до setp()
    • pptr() устанавливается на то же значение, что и pbase(), но это указатель, который фактически перемещается, чтобы указать позицию следующего символа. Если это epptr(), когда записывается символ, вызывается overflow().
  • Возможно, виртуальной функции overflow() присвоен символ, вызвавший переполнение буфера. Если она не вызывается пользовательским кодом с аргументом eof(), это тот случай, когда стандартная библиотека вызывает функцию. Чтобы справиться с этим дополнительным символом, буферу дается на один элемент меньше доступного места: символ может быть добавлен к буферу до того, как буфер будет фактически записан.
  • traits_type, унаследованный от std::streambuf, определяет ряд функций для определения свойств символов. При проверке параметра, переданного в overflow(), используется функция eq_int_type(), которая сравнивает объекты целочисленного типа потока на равенство. Он сравнивает аргумент overflow() со значением, указывающим на недопустимый символ, полученным с использованием eof(). Все члены типа трейтов static или typedefs, т.е. нет необходимости использовать объект этого типа. Использование traits_type важно при использовании различных экземпляров шаблона std::basic_streambuf.
  • Хотя буфер обычно имеет размер от pbase() до epptr() с pptr() между этими двумя концами, pptr() можно переместить за пределы epptr() с помощью pbump(n): эта функция просто добавляет n к pptr(). Это используется для помещения текущего символа в буфер прямо перед вызовом sync(), который просто записывает содержимое буфера и сбрасывает его.
  • Возврат overflow() равен eof(), если функция не удалась. При успешном выполнении функция возвращает нечто отличное от eof(), и обычно используется символ, переданный в качестве аргумента. Поскольку аргумент может быть eof(), недостаточно просто вернуть аргумент: если это eof(), это значение необходимо преобразовать в какое-то другое полезное значение, что и делает not_eof(): он возвращает свои аргументы, если аргумент не eof() в этом случае он возвращает другое подходящее значение.
  • Виртуальная функция sync() отвечает за очистку буфера. В этом случае он сначала проверяет, что нужно сделать, и если да, то записывает содержимое буфера. На самом деле в этой функции нет никакого трюка, хотя я думаю, что правильный был бы более осторожным, если write() терпит неудачу, то есть возвращает меньше символов, чем символов в буфере. Эта версия просто делает вид, что можно потерять символы, которые не могут быть записаны, хотя это будет указывать на ошибку.
  • Функция по-прежнему сбрасывает буфер, используя тот же подход, оставляя один пробел в конце буфера.
  • В случае неудачи функция возвращает -1, в случае успеха — 0. Никаких трюков.
person Dietmar Kühl    schedule 10.02.2012
comment
Это немного продвинуто для меня. Не могли бы вы взглянуть на эту ссылку: java2s.com/Tutorial/Cpp /0240__Файловый поток/. Вы оба используете streambuf, но реализация отличается. Можно ли использовать тот, что по ссылке? Но другой вопрос в том, что ссылка не имеет контроля над размером буфера. - person performanceuser; 11.02.2012
comment
На самом деле ссылка указывает на другую версию моего кода! ;) Изначально я написал это для книги Нико, когда переводил его немецкую книгу. За исключением ошибок (приведенный выше код не тестировался), это примерно то же самое, но без буфера, и в приведенной выше версии также используются несколько изящных приемов. Вы можете выделить подходящий буфер (std::vector<char> соответствующего размера) в любой версии. Книга Нико объясняет, что происходит: может быть, вам стоит туда заглянуть. - person Dietmar Kühl; 11.02.2012
comment
Какое совпадение. Тогда вы можете сказать мне, как добавить буфер в код в ссылке? Мне он кажется более читабельным. - person performanceuser; 11.02.2012
comment
После дополнительных исследований я изменил код в ссылке следующим образом: fdoutbuf (int _fd, char_type* buffer, std::streamsize bufferLength): fd(_fd) { setp(buffer, buffer + bufferLength); } Я выделю буфер перед инициализацией fdoutbuf. Это хорошо? Или, может быть, мне нужно реализовать setbuf()? - person performanceuser; 11.02.2012
comment
Нет необходимости использовать setbuf(). Однако обратите внимание, что вы должны быть готовы к тому, что overflow() добавляет символ (если его аргумент не равен traits_type::eof()): поэтому я использую size-1. Кроме того, обратите внимание, что вы хотите иметь возможность сбрасывать буфер, что и делает sync() (версия Нико не нуждается в этом, поскольку она не буферизируется). Если вы примете это во внимание, я думаю, вы обнаружите, что в значительной степени пришли к приведенному выше коду...! - person Dietmar Kühl; 11.02.2012
comment
Большое спасибо за ваши комментарии. Я изучал его весь день, и мне очень срочно нужно его реализовать. Я был бы очень признателен, если бы вы могли расширить код в ссылке с настройкой размера буфера. Он не обязательно должен совпадать с приведенным выше кодом (включая буфер). - person performanceuser; 11.02.2012
comment
Ваш измененный код устанавливает буфер, но не использует буфер в overflow()! Вам нужно использовать буфер. Кроме того, если вы выполняете буферизацию, вам, вероятно, следует избавиться от xsputn(): на самом деле это необходимо только для того, чтобы избежать вызова overflow() для отдельного символа. Кроме того, буфер не сбрасывается. Взамен вы определенно хотите sync(). Что не так с использованием кода в моем ответе? - person Dietmar Kühl; 11.02.2012
comment
Теперь я понимаю, что вы имеете в виду. Позвольте мне изменить мой код. В то же время, можете ли вы указать место, объясняющее внутренний механизм работы? - person performanceuser; 11.02.2012
comment
Также я не знаком с чертами. Можете ли вы изменить свой код, чтобы избавиться от него? - person performanceuser; 11.02.2012
comment
Ну, описания можно найти, например, на выполнив поиск Dietmar Kuehl или James Kanze в comp.lang.c++ и comp.lang.c++.moderated. В противном случае вы можете взглянуть на книгу Николая Йосуттиса (Стандартная библиотека C++) или IOStreams and Locales Анжелики Лангер и Клауса Крефта. Что касается traits_type: вы просто хотите использовать это! - person Dietmar Kühl; 11.02.2012
comment
В любом случае использование traits_type довольно прямолинейно: eof() просто дает вам правильное использование значения для недопустимого символа; eq_int_type() сравнивает два объекта целочисленного типа потока на равенство, т. е. traits_type::eq_int_type(x, y) по сути является x == y; интересным является to_char_type(), который преобразуется в int, переданный в качестве аргумента в char. В основном это необходимо, когда буфер потока является шаблоном (единственное сложное преобразование — char в int, и оно не используется). - person Dietmar Kühl; 11.02.2012
comment
Благодаря чертам выглядит очень мощно. Я продолжу изучать его после решения этой проблемы. Я обновил свой пост, вы можете взглянуть. Кстати, а если он никогда не переполнится? и как обращаться с буфером после попадания в EOF? В основном последние пару символов. Как это записать в файл. Должен ли вызывающий абонент справиться с этим? - person performanceuser; 11.02.2012
comment
Вы хотите избавиться от OsdWrite() в overflow(): вы уже пишете символ в sync(). Это выглядело бы неуместно до написания буфера (для тестирования вы можете временно установить размер буфера, например, на 10, чтобы увидеть, как происходит переполнение). В overflow() нет ничего плохого, он просто сигнализирует о том, что пора записать текущее содержимое буфера и/или освободить место в буфере. Что происходит, когда буфер никогда не переполняется? В этом случае он должен быть сброшен, о чем sync(): когда std::ostream сбрасывается, вызывается sync(). - person Dietmar Kühl; 11.02.2012
comment
Возможно, вы захотите добавить вызов sync() в деструктор. Кроме этого, если буфер потока используется std::ostream, буфер потока очищается при вызове std::ostream. Чтобы разобраться с последней парой символов, вероятно, стоит также вызвать sync() из деструктора. - person Dietmar Kühl; 11.02.2012
comment
Большое спасибо. Это мне очень помогло. Я действительно ценю твою помощь. - person performanceuser; 11.02.2012

Если вы пишете на C++, вы можете попробовать использовать STL и <fstream> для вывода в файлы. Он уже имеет встроенный буфер и не имеет таких проблем со скоростью.

person Seagull    schedule 10.02.2012
comment
Но мне нужно использовать файловый дескриптор. Инфраструктура есть - person performanceuser; 11.02.2012