Запись / чтение больших векторов данных в двоичный файл на языке c++


У меня есть программа на c++, которая вычисляет популяции в пределах заданного радиуса, считывая данные о населении из файла ascii в большой 8640x3432-элементный вектор двойников. Чтение ascii-данных в вектор занимает ~30 секунд (циклическое выполнение над каждым столбцом и каждой строкой), в то время как остальная часть программы занимает всего несколько секунд. Меня попросили ускорить этот процесс, записав данные о населении в двоичный файл, который, предположительно, будет считываться быстрее.

Файл данных ascii имеет несколько строк заголовка, которые дают некоторые спецификации данных, такие как количество столбцов и строк, а затем данные о населении для каждой ячейки сетки, которая отформатирована как 3432 строки из 8640 чисел, разделенных пробелами. Данные о населении имеют смешанные форматы и могут быть просто 0, десятичное значение (0.000685648) или значение в научной нотации (2.687768 e-05).

Я нашел несколько примеров чтения / записи структур, содержащих векторы в двоичный код, и попытался реализовать что-то подобное, но столкнулся с проблемы. Когда я одновременно пишу и читаю вектор в / из двоичного файла в одной и той же программе, он, кажется, работает и дает мне все правильные значения, но тогда это заканчивается либо "ошибкой сегмента: 11", либо ошибкой выделения памяти, что "освобождаемый указатель не был выделен". И если я попытаюсь просто прочитать данные из ранее записанного двоичного файла (без перезаписи его в той же программе запуска), то он дает мне переменные заголовка просто отлично, но дает мне segfault, прежде чем дать мне векторные данные.

Любой совет о том, что я мог сделать неправильно, или о лучшем способе сделать это, был бы очень признателен! Я компилирую и запускаю на mac, и в настоящее время у меня нет boost или других нестандартных библиотек. (Примечание: Я очень новичок в кодировании и должен учиться, прыгая в глубокий конец, поэтому я могу пропустить много основных понятий и терминологии-извините!).

Вот код, который я придумал:

# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <fstream>
# include <iostream>
# include <vector>
# include <string.h>

using namespace std;

//Define struct for population file data and initialize one struct variable for reading in ascii (A) and one for reading in binary (B)
struct popFileData
{
    int nRows, nCol;
    vector< vector<double> > popCount; //this will end up having 3432x8640 elements
} popDataA, popDataB;

int main() {

    string gridFname = "sample";

    double dum;
    vector<double> tempVector;

    //open ascii population grid file to stream
    ifstream gridFile;
    gridFile.open(gridFname + ".asc");

    int i = 0, j = 0;

    if (gridFile.is_open())
    {
        //read in header data from file
        string fileLine;
        gridFile >> fileLine >> popDataA.nCol;
        gridFile >> fileLine >> popDataA.nRows;

        popDataA.popCount.clear();

        //read in vector data, point-by-point
        for (i = 0; i < popDataA.nRows; i++)
        {
            tempVector.clear();

            for (j = 0; j<popDataA.nCol; j++)
            {
                gridFile >> dum;
                tempVector.push_back(dum);
            }
            popDataA.popCount.push_back(tempVector);
        }
        //close ascii grid file
        gridFile.close();
    }
    else
    {
        cout << "Population file read failed!" << endl;
    }

    //create/open binary file
    ofstream ofs(gridFname + ".bin", ios::trunc | ios::binary);
    if (ofs.is_open())
    {
        //write struct to binary file then close binary file
        ofs.write((char *)&popDataA, sizeof(popDataA));
        ofs.close();
    }
    else cout << "error writing to binary file" << endl;

    //read data from binary file into popDataB struct
    ifstream ifs(gridFname + ".bin", ios::binary);
    if (ifs.is_open())
    {
        ifs.read((char *)&popDataB, sizeof(popDataB));
        ifs.close();
    }
    else cout << "error reading from binary file" << endl;

    //compare results of reading in from the ascii file and reading in from the binary file
    cout << "File Header Values:n";
    cout << "Columns (ascii vs binary): " << popDataA.nCol << " vs. " << popDataB.nCol << endl;
    cout << "Rows (ascii vs binary):" << popDataA.nRows << " vs." << popDataB.nRows << endl;

    cout << "Spot Check Vector Values: " << endl;
    cout << "Index 0,0: " << popDataA.popCount[0][0] << " vs. " << popDataB.popCount[0][0] << endl;
    cout << "Index 3431,8639: " << popDataA.popCount[3431][8639] << " vs. " << popDataB.popCount[3431][8639] << endl;
    cout << "Index 1600,4320: " << popDataA.popCount[1600][4320] << " vs. " << popDataB.popCount[1600][4320] << endl;

    return 0;
}

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

File Header Values:
Columns (ascii vs binary): 8640 vs. 8640
Rows (ascii vs binary):3432 vs.3432
Spot Check Vector Values: 
Index 0,0: 0 vs. 0
Index 3431,8639: 0 vs. 0
Index 1600,4320: 25.2184 vs. 25.2184
a.out(11402,0x7fff77c25310) malloc: *** error for object 0x7fde9821c000: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6

И вот результат, который я получаю, если я просто пытаюсь прочитать из уже существующего двоичного файла:

File Header Values:
Columns (binary): 8640
Rows (binary):3432
Spot Check Vector Values: 
Segmentation fault: 11

Заранее спасибо за любую помощь!

1 2

1 ответ:

Когда вы записываете popDataA в файл, вы записываете двоичное представление вектора векторов. Однако это действительно довольно маленький объект, состоящий из указателя на фактические данные (сам ряд векторов, в данном случае) и некоторой информации о размере.

Когда его читают обратно в popDataB, это вроде как работает! Но только потому, что сырой указатель, который был в popDataA, теперь находится в popDataB, и он указывает на тот же материал в памяти. Вещи сходят с ума в конце, потому что когда память для векторы освобождаются, код пытается освободить данные, на которые ссылается popDataA дважды (один раз для popDataA, и еще раз для popDataB.)

Короче говоря, это не разумная вещь, чтобы записать вектор в файл таким образом.

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

В полу-псевдокоде написание будет выглядеть примерно так:

int nrow=...;
int ncol=...;
ofs.write((char *)&nrow,sizeof(nrow));
ofs.write((char *)&ncol,sizeof(ncol));
for (int i=0;i<nrow;++i) {
    for (int j=0;j<ncol;++j) {
        double val=data[i][j];
        ofs.write((char *)&val,sizeof(val));
    }
}

А чтение будет обратным:

ifs.read((char *)&nrow,sizeof(nrow));
ifs.read((char *)&ncol,sizeof(ncol));
// allocate data-structure of size nrow x ncol
// ...
for (int i=0;i<nrow;++i) {
    for (int j=0;j<ncol;++j) {
        double val;
        ifs.read((char *)&val,sizeof(val));
        data[i][j]=val;
    }
}

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

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

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

Более легкие варианты включают в себя ускорение библиотека сериализации и буферы протоколов Google, но они решают только некоторые из перечисленных выше проблем.