Запись / чтение больших векторов данных в двоичный файл на языке 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 ответ:
Когда вы записываете
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, но они решают только некоторые из перечисленных выше проблем.