эффективный способ сохранения объектов в двоичные файлы - PullRequest
4 голосов
/ 01 июля 2011

У меня есть класс, который состоит в основном из матрицы векторов: vector< MyFeatVector<T> > m_vCells, где внешний вектор представляет матрицу.Каждый элемент в этой матрице - это vector (я расширил класс stl vector и назвал его MyFeatVector<T>).

Я пытаюсь написать эффективный метод для хранения объектов этого класса в двоичных файлах.До сих пор мне требуются три вложенных цикла:

foutput.write( reinterpret_cast<char*>( &(this->at(dy,dx,dz)) ), sizeof(T) );

, где this->at(dy,dx,dz) возвращает элемент dz вектора в позиции [dy,dx].

Есть ли возможность сохранить приватный элемент m_vCells без использования циклов?Я пробовал что-то вроде: foutput.write(reinterpret_cast<char*>(&(this->m_vCells[0])), (this->m_vCells.size())*sizeof(CFeatureVector<T>));, который, кажется, не работает правильно.Мы можем предположить, что все векторы в этой матрице имеют одинаковый размер, хотя приветствуется и более общее решение: -)

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

Я пытался следовать предложению в http://forum.allaboutcircuits.com/showthread.php?t=16465, но не смог найти правильное решение.

Спасибо!

Ниже приведен упрощенный пример моих методов serialization и unserialization.

template < typename T >
bool MyFeatMatrix<T>::writeBinary( const string & ofile ){

    ofstream foutput(ofile.c_str(), ios::out|ios::binary);
    foutput.write(reinterpret_cast<char*>(&this->m_nHeight), sizeof(int));
    foutput.write(reinterpret_cast<char*>(&this->m_nWidth), sizeof(int));
    foutput.write(reinterpret_cast<char*>(&this->m_nDepth), sizeof(int));

    //foutput.write(reinterpret_cast<char*>(&(this->m_vCells[0])), nSze*sizeof(CFeatureVector<T>));
    for(register int dy=0; dy < this->m_nHeight; dy++){
       for(register int dx=0; dx < this->m_nWidth; dx++){
          for(register int dz=0; dz < this->m_nDepth; dz++){
              foutput.write( reinterpret_cast<char*>( &(this->at(dy,dx,dz)) ), sizeof(T) );
          }
       }
    }

    foutput.close();
    return true;
}

template < typename T >
bool MyFeatMatrix<T>::readBinary( const string & ifile ){

    ifstream finput(ifile.c_str(), ios::in|ios::binary);

    int nHeight, nWidth, nDepth;
    finput.read(reinterpret_cast<char*>(&nHeight), sizeof(int));
    finput.read(reinterpret_cast<char*>(&nWidth), sizeof(int));
    finput.read(reinterpret_cast<char*>(&nDepth), sizeof(int));

    this->resize(nHeight, nWidth, nDepth);

    for(register int dy=0; dy < this->m_nHeight; dy++){
        for(register int dx=0; dx < this->m_nWidth; dx++){
            for(register int dz=0; dz < this->m_nDepth; dz++){
                finput.read( reinterpret_cast<char*>( &(this->at(dy,dx,dz)) ), sizeof(T) );
            }
        }
    }
    finput.close();
    return true;
}

Ответы [ 4 ]

3 голосов
/ 01 июля 2011

Самый эффективный метод - сохранить объекты в массиве (или смежном пространстве), а затем передать буфер в файл. Преимущество состоит в том, что дисковые пластины не тратят время впустую, а запись может выполняться непрерывно, а не в случайных местах.

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

Редактировать 1: Пример сериализации
Следующий код не был скомпилирован и предназначен только для иллюстративных целей.

#include <fstream>
#include <algorithm>

using std::ofstream;
using std::fill;

class binary_stream_interface
{
    virtual void    load_from_buffer(const unsigned char *& buf_ptr) = 0;
    virtual size_t  size_on_stream(void) const = 0;
    virtual void    store_to_buffer(unsigned char *& buf_ptr) const = 0;
};

struct Pet
    : public binary_stream_interface,
    max_name_length(32)
{
    std::string     name;
    unsigned int    age;
    const unsigned int  max_name_length;

    void    load_from_buffer(const unsigned char *& buf_ptr)
        {
            age = *((unsigned int *) buf_ptr);
            buf_ptr += sizeof(unsigned int);
            name = std::string((char *) buf_ptr);
            buf_ptr += max_name_length;
            return;
        }
    size_t  size_on_stream(void) const
    {
        return sizeof(unsigned int) + max_name_length;
    }
    void    store_to_buffer(unsigned char *& buf_ptr) const
    {
        *((unsigned int *) buf_ptr) = age;
        buf_ptr += sizeof(unsigned int);
        std::fill(buf_ptr, 0, max_name_length);
        strncpy((char *) buf_ptr, name.c_str(), max_name_length);
        buf_ptr += max_name_length;
        return;
    }
};


int main(void)
{
    Pet dog;
    dog.name = "Fido";
    dog.age = 5;
    ofstream    data_file("pet_data.bin", std::ios::binary);

    // Determine size of buffer
    size_t  buffer_size = dog.size_on_stream();

    // Allocate the buffer
    unsigned char * buffer = new unsigned char [buffer_size];
    unsigned char * buf_ptr = buffer;

    // Write / store the object into the buffer.
    dog.store_to_buffer(buf_ptr);

    // Write the buffer to the file / stream.
    data_file.write((char *) buffer, buffer_size);

    data_file.close();
    delete [] buffer;
    return 0;
}

Редактировать 2: класс с вектором строк

class Many_Strings
    : public binary_stream_interface
{
    enum {MAX_STRING_SIZE = 32};

    size_t    size_on_stream(void) const
    {
        return m_string_container.size() * MAX_STRING_SIZE  // Total size of strings.
               + sizeof(size_t); // with room for the quantity variable.
    }

    void      store_to_buffer(unsigned char *& buf_ptr) const
    {
        // Treat the vector<string> as a variable length field.
        // Store the quantity of strings into the buffer,
        //     followed by the content.
        size_t string_quantity = m_string_container.size();
        *((size_t *) buf_ptr) = string_quantity;
        buf_ptr += sizeof(size_t);

        for (size_t i = 0; i < string_quantity; ++i)
        {
            // Each string is a fixed length field.
            // Pad with '\0' first, then copy the data.
            std::fill((char *)buf_ptr, 0, MAX_STRING_SIZE);
            strncpy(buf_ptr, m_string_container[i].c_str(), MAX_STRING_SIZE);
            buf_ptr += MAX_STRING_SIZE;
        }
    }
    void load_from_buffer(const unsigned char *& buf_ptr)
    {
        // The actual coding is left as an exercise for the reader.
        // Psuedo code:
        //     Clear / empty the string container.
        //     load the quantity variable.
        //     increment the buffer variable by the size of the quantity variable.
        //     for each new string (up to the quantity just read)
        //        load a temporary string from the buffer via buffer pointer.
        //        push the temporary string into the vector
        //        increment the buffer pointer by the MAX_STRING_SIZE.
        //      end-for
     }
     std::vector<std::string> m_string_container;
};
2 голосов
/ 01 июля 2011

Я бы посоветовал вам прочитать C ++ FAQ по сериализации , и вы можете выбрать, что лучше всего подходит для ваших

Когда вы работаете со структурами и классами, вы должны принятьзабота о двух вещах

  • Указатели внутри класса
  • Байты заполнения

Оба эти параметра могут принести некоторые печально известные результаты в вашем выводе.IMO, объект должен быть реализован для сериализации и десериализации объекта.Объект может хорошо знать о структурах, указателях данных и т. Д. Таким образом, он может решить, какой формат может быть эффективно реализован.

Вам все равно придется повторять или где-то оборачивать.Как только вы закончили реализацию функции сериализации и десериализации (вы можете писать, используя операторы или функции).Особенно, когда вы работаете с потоковыми объектами, перегрузка операторов << и >> будет легко передавать объект.

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


Обновление в соответствии с обновлением вопроса.

Есть несколько вещей, которые вы должны помнить, прежде чем переопределять членов STL.Они не очень хороший кандидат на наследование, потому что у него нет виртуальных деструкторов.Если вы используете базовые типы данных и POD-подобные структуры, это не вызовет особых проблем.Но если вы используете его действительно объектно-ориентированным способом, вы можете столкнуться с неприятным поведением.

Относительно вашего кода

  • Почему вы типизируете его в char *?
  • Способ сериализации объекта - ваш выбор.IMO, что вы сделали, это базовая операция записи файла во имя сериализации.
  • Сериализация выполняется до объекта.т.е. параметр 'T' в вашем классе шаблона.Если вы используете POD или базовые типы, нет необходимости в специальной синхронизации.В противном случае вы должны тщательно выбрать способ записи объекта.
  • Выбор текстового или двоичного формата - ваш выбор.Текстовый формат всегда имеет свою стоимость, в то же время его проще манипулировать, чем двоичным форматом.

Например, следующий код предназначен для простой операции чтения и записи (в текстовом формате).

fstream fr("test.txt", ios_base::out | ios_base::binary );
for( int i =0;i <_countof(arr);i++)
    fr << arr[i] << ' ';

fr.close();

fstream fw("test.txt", ios_base::in| ios_base::binary);

int j = 0;
while( fw.eof() || j < _countof(arrout))
{
    fw >> arrout[j++];
}
0 голосов
/ 05 июля 2011

Прежде всего, вы смотрели на Boost.multi_array ? Всегда хорошо взять что-то готовое, а не изобретать велосипед.

Тем не менее, я не уверен, что это полезно, но вот как я бы реализовал базовую структуру данных, и было бы довольно легко сериализовать:

#include <array>

template <typename T, size_t DIM1, size_t DIM2, size_t DIM3>
class ThreeDArray
{
  typedef std::array<T, DIM1 * DIM2 * DIM3> array_t;
  array_t m_data;

public:

  inline size_t size() const { return data.size(); }
  inline size_t byte_size() const  { return sizeof(T) * data.size(); }

  inline T & operator()(size_t i, size_t j, size_t k)
  {
     return m_data[i + j * DIM1 + k * DIM1 * DIM2];
  }

  inline const T & operator()(size_t i, size_t j, size_t k) const
  {
     return m_data[i + j * DIM1 + k * DIM1 * DIM2];
  }

  inline const T * data() const { return m_data.data(); }
};

Вы можете сериализовать буфер данных напрямую:

ThreeDArray<int, 4, 6 11> arr;
/* ... */
std::ofstream outfile("file.bin");
outfile.write(reinterpret_cast<char*>(arr.data()), arr.byte_size());
0 голосов
/ 01 июля 2011

Мне кажется, что самый прямой корень для создания бинарного файла, содержащего вектор, состоит в том, чтобы отобразить файл в памяти и поместить его в отображаемую область. Как указывает sarat , вам нужно беспокоиться о том, как указатели используются в классе. Но библиотека boost-interprocess имеет учебник о том, как сделать это, используя свои области общей памяти, которые включают в себя сопоставленные файлы памяти .

...