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

Използвам write(fd, buffer, count) в io.h за запис на буфер във файлов дескриптор.

Сега открих огромен проблем с производителността, защото всеки път, когато извикам "write", той ще извърши IO операция.

Искам да добавя буфер, преди действително да запиша съдържание във файл.

Кой е най-добрият начин да направите това?

Търсих и намерих това:

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__File-Stream/. И двамата използвате streambuf, но реализацията е различна. Мога ли да използвам този в линка? Но друг въпрос е, че връзката няма контрол върху размера на buf. - 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() (версията на Nico не се нуждае от това, тъй като не буферира). Ако вземете предвид тези неща, мисля, че ще откриете, че почти достигате до кода по-горе...! - 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. В противен случай можете да погледнете книгата на Nicolai Josuttis (The C++ Standard Library) или IOStreams and Locales на Angelika Langer и Klaus Kreft. По отношение на 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