从文件加载(大)std::vector<std::vector<float>> 的更快方法
Faster way of loading (big) std::vector<std::vector<float>> from file
我已经实现了一种将 std::vector
向量保存到文件并使用此代码读取它们的方法(在 Whosebug 上找到):
节省:
void saveData(std::string path)
{
std::ofstream FILE(path, std::ios::out | std::ofstream::binary);
// Store size of the outer vector
int s1 = RecordData.size();
FILE.write(reinterpret_cast<const char*>(&s1), sizeof(s1));
// Now write each vector one by one
for (auto& v : RecordData) {
// Store its size
int size = v.size();
FILE.write(reinterpret_cast<const char*>(&size), sizeof(size));
// Store its contents
FILE.write(reinterpret_cast<const char*>(&v[0]), v.size() * sizeof(float));
}
FILE.close();
}
阅读中:
void loadData(std::string path)
{
std::ifstream FILE(path, std::ios::in | std::ifstream::binary);
if (RecordData.size() > 0) // Clear data
{
for (int n = 0; n < RecordData.size(); n++)
RecordData[n].clear();
RecordData.clear();
}
int size = 0;
FILE.read(reinterpret_cast<char*>(&size), sizeof(size));
RecordData.resize(size);
for (int n = 0; n < size; ++n) {
int size2 = 0;
FILE.read(reinterpret_cast<char*>(&size2), sizeof(size2));
float f;
//RecordData[n].reserve(size2); // This doesn't make a difference in speed
for (int k = 0; k < size2; ++k) {
FILE.read(reinterpret_cast<char*>(&f), sizeof(f));
RecordData[n].push_back(f);
}
}
}
这非常有效,但加载大型数据集(980MB,内部向量大小为 32000,其中 1600 个)需要大约 7-8 秒(与保存相比,保存时间不到 1 秒)。因为我可以看到 Visual Studio 中的内存使用量在加载过程中缓慢上升,所以我猜应该是大量内存分配。不过,注释掉的行 RecordData[n].resize(size2);
没有任何区别。
谁能给我一个更快的加载此类数据的方法?我的第一次尝试是将所有数据放在一个大的 std::vector<float>
中,但由于某种原因似乎会产生某种溢出(这不应该发生,因为 sizeof(int) = 4
,所以大约 40 亿应该足够了对于索引变量(std::vector 在内部使用其他东西吗?)。拥有 std::vector<std::vector<float>>
的数据结构也非常好。将来我将不得不处理更大的数据集(尽管我可能会使用 <short>
来节省内存并将其作为定点数处理),因此加载速度将更加重要......
编辑:
我应该指出,内部向量的 32000 和外部向量的 1600 只是一个示例。两者都可以变化。我想,我必须保存一个“索引向量”作为第一个内部向量来声明其余的项目数(就像我在评论中说的那样:我是第一次 file-reader/-writer 和避风港我用 std::vector
的时间超过一两周,所以我不确定)。我会在以后的编辑中查看块读取和 post 结果...
编辑2:
所以,这是 perivesta 的版本(谢谢你)。我所做的唯一更改是丢弃 RV& RecordData
因为这对我来说是一个全局变量。
奇怪的是,对于 perivesta,对于 980 GB 的文件,这只会使我的加载时间从 ~7000ms 减少到 ~1500ms,而对于 perivesta 的 2GB 文件,加载时间不会从 7429ms 减少到 644 ms(奇怪,不同系统上的速度有何不同 ;-) )
void loadData2(std::string path)
{
std::ifstream FILE(path, std::ios::in | std::ifstream::binary);
if (RecordData.size() > 0) // Clear data
{
for (int n = 0; n < RecordData.size(); n++)
RecordData[n].clear();
RecordData.clear();
}
int size = 0;
FILE.read(reinterpret_cast<char*>(&size), sizeof(size));
RecordData.resize(size);
for (auto& v : RecordData) {
// load its size
int size2 = 0;
FILE.read(reinterpret_cast<char*>(&size2), sizeof(size2));
v.resize(size2);
// load its contents
FILE.read(reinterpret_cast<char*>(&v[0]), v.size() * sizeof(float));
}
}
//RecordData[n].resize(size2); // This doesn't make a difference in speed
如果您使用这一行(同时不更改其余代码),代码会变慢,而不是变快!
resize
更改向量的大小,然后将更多元素推入它,导致向量的大小是您实际需要的两倍。
我猜你想要的是 reserve
。 reserve
只分配容量而不改变向量的大小,然后推送元素可以预期更快,因为内存只分配一次。
或者使用 resize
,然后分配给已经存在的元素。
首先,由于您预先知道元素的数量,因此您应该在向量中 reserve
space 以防止在向量增长时进行不必要的重新分配。其次,所有这些 push_back
可能会让您付出代价。该功能确实有一些开销。第三,正如 Alan 所说,一次读取整个文件不可能有什么坏处,如果您先调整(而不是保留)向量的大小,就可以做到这一点。
所以,综上所述,我会这样做(一旦您将数据大小读入 size2
):
RecordData.resize(size2); // both reserves and allocates space for size2 items
FILE.read(reinterpret_cast<char*>(RecordData.data()), size2 * sizeof(float));
我认为这是最优的。
不幸的是,在这种情况下,IMO,当您调用 resize
时,std::vector
坚持 zero-initialising 所有 size2
元素,因为您将立即覆盖他们,但我不知道有什么容易预防的方法。您需要使用自定义分配器,这可能不值得付出努力。
这是 Alan Birtles 的评论的实现:阅读时,通过一个 FILE.read
调用而不是许多单独的调用来读取内部向量。这大大减少了我系统上的时间:
这些是 2GB 文件的结果:
Writing took 2283 ms
Reading v1 took 7429 ms
Reading v2 took 644 ms
这里是产生这个输出的代码:
#include <vector>
#include <iostream>
#include <string>
#include <chrono>
#include <random>
#include <fstream>
using RV = std::vector<std::vector<float>>;
void saveData(std::string path, const RV& RecordData)
{
std::ofstream FILE(path, std::ios::out | std::ofstream::binary);
// Store size of the outer vector
int s1 = RecordData.size();
FILE.write(reinterpret_cast<const char*>(&s1), sizeof(s1));
// Now write each vector one by one
for (auto& v : RecordData) {
// Store its size
int size = v.size();
FILE.write(reinterpret_cast<const char*>(&size), sizeof(size));
// Store its contents
FILE.write(reinterpret_cast<const char*>(&v[0]), v.size() * sizeof(float));
}
FILE.close();
}
//original version for comparison
void loadData1(std::string path, RV& RecordData)
{
std::ifstream FILE(path, std::ios::in | std::ifstream::binary);
if (RecordData.size() > 0) // Clear data
{
for (int n = 0; n < RecordData.size(); n++)
RecordData[n].clear();
RecordData.clear();
}
int size = 0;
FILE.read(reinterpret_cast<char*>(&size), sizeof(size));
RecordData.resize(size);
for (int n = 0; n < size; ++n) {
int size2 = 0;
FILE.read(reinterpret_cast<char*>(&size2), sizeof(size2));
float f;
//RecordData[n].resize(size2); // This doesn't make a difference in speed
for (int k = 0; k < size2; ++k) {
FILE.read(reinterpret_cast<char*>(&f), sizeof(f));
RecordData[n].push_back(f);
}
}
}
//my version
void loadData2(std::string path, RV& RecordData)
{
std::ifstream FILE(path, std::ios::in | std::ifstream::binary);
if (RecordData.size() > 0) // Clear data
{
for (int n = 0; n < RecordData.size(); n++)
RecordData[n].clear();
RecordData.clear();
}
int size = 0;
FILE.read(reinterpret_cast<char*>(&size), sizeof(size));
RecordData.resize(size);
for (auto& v : RecordData) {
// load its size
int size2 = 0;
FILE.read(reinterpret_cast<char*>(&size2), sizeof(size2));
v.resize(size2);
// load its contents
FILE.read(reinterpret_cast<char*>(&v[0]), v.size() * sizeof(float));
}
}
int main()
{
using namespace std::chrono;
const std::string filepath = "./vecdata";
const std::size_t sizeOuter = 16000;
const std::size_t sizeInner = 32000;
RV vecSource;
RV vecLoad1;
RV vecLoad2;
const auto tGen1 = steady_clock::now();
std::cout << "generating random numbers..." << std::flush;
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_real_distribution<float> dis;
for(int i = 0; i < sizeOuter; ++i)
{
RV::value_type inner;
for(int k = 0; k < sizeInner; ++k)
{
inner.push_back(dis(rng));
}
vecSource.push_back(inner);
}
const auto tGen2 = steady_clock::now();
std::cout << "done\nSaving..." << std::flush;
const auto tSave1 = steady_clock::now();
saveData(filepath, vecSource);
const auto tSave2 = steady_clock::now();
std::cout << "done\nReading v1..." << std::flush;
const auto tLoadA1 = steady_clock::now();
loadData1(filepath, vecLoad1);
const auto tLoadA2 = steady_clock::now();
std::cout << "verifying..." << std::flush;
if(vecSource != vecLoad1) std::cout << "FAILED! ...";
std::cout << "done\nReading v2..." << std::flush;
const auto tLoadB1 = steady_clock::now();
loadData2(filepath, vecLoad2);
const auto tLoadB2 = steady_clock::now();
std::cout << "verifying..." << std::flush;
if(vecSource != vecLoad2) std::cout << "FAILED! ...";
std::cout << "done\nResults:\n" <<
"Generating took " << duration_cast<milliseconds>(tGen2 - tGen1).count() << " ms\n" <<
"Writing took " << duration_cast<milliseconds>(tSave2 - tSave1).count() << " ms\n" <<
"Reading v1 took " << duration_cast<milliseconds>(tLoadA2 - tLoadA1).count() << " ms\n" <<
"Reading v2 took " << duration_cast<milliseconds>(tLoadB2 - tLoadB1).count() << " ms\n" <<
std::flush;
}
我已经实现了一种将 std::vector
向量保存到文件并使用此代码读取它们的方法(在 Whosebug 上找到):
节省:
void saveData(std::string path)
{
std::ofstream FILE(path, std::ios::out | std::ofstream::binary);
// Store size of the outer vector
int s1 = RecordData.size();
FILE.write(reinterpret_cast<const char*>(&s1), sizeof(s1));
// Now write each vector one by one
for (auto& v : RecordData) {
// Store its size
int size = v.size();
FILE.write(reinterpret_cast<const char*>(&size), sizeof(size));
// Store its contents
FILE.write(reinterpret_cast<const char*>(&v[0]), v.size() * sizeof(float));
}
FILE.close();
}
阅读中:
void loadData(std::string path)
{
std::ifstream FILE(path, std::ios::in | std::ifstream::binary);
if (RecordData.size() > 0) // Clear data
{
for (int n = 0; n < RecordData.size(); n++)
RecordData[n].clear();
RecordData.clear();
}
int size = 0;
FILE.read(reinterpret_cast<char*>(&size), sizeof(size));
RecordData.resize(size);
for (int n = 0; n < size; ++n) {
int size2 = 0;
FILE.read(reinterpret_cast<char*>(&size2), sizeof(size2));
float f;
//RecordData[n].reserve(size2); // This doesn't make a difference in speed
for (int k = 0; k < size2; ++k) {
FILE.read(reinterpret_cast<char*>(&f), sizeof(f));
RecordData[n].push_back(f);
}
}
}
这非常有效,但加载大型数据集(980MB,内部向量大小为 32000,其中 1600 个)需要大约 7-8 秒(与保存相比,保存时间不到 1 秒)。因为我可以看到 Visual Studio 中的内存使用量在加载过程中缓慢上升,所以我猜应该是大量内存分配。不过,注释掉的行 RecordData[n].resize(size2);
没有任何区别。
谁能给我一个更快的加载此类数据的方法?我的第一次尝试是将所有数据放在一个大的 std::vector<float>
中,但由于某种原因似乎会产生某种溢出(这不应该发生,因为 sizeof(int) = 4
,所以大约 40 亿应该足够了对于索引变量(std::vector 在内部使用其他东西吗?)。拥有 std::vector<std::vector<float>>
的数据结构也非常好。将来我将不得不处理更大的数据集(尽管我可能会使用 <short>
来节省内存并将其作为定点数处理),因此加载速度将更加重要......
编辑:
我应该指出,内部向量的 32000 和外部向量的 1600 只是一个示例。两者都可以变化。我想,我必须保存一个“索引向量”作为第一个内部向量来声明其余的项目数(就像我在评论中说的那样:我是第一次 file-reader/-writer 和避风港我用 std::vector
的时间超过一两周,所以我不确定)。我会在以后的编辑中查看块读取和 post 结果...
编辑2:
所以,这是 perivesta 的版本(谢谢你)。我所做的唯一更改是丢弃 RV& RecordData
因为这对我来说是一个全局变量。
奇怪的是,对于 perivesta,对于 980 GB 的文件,这只会使我的加载时间从 ~7000ms 减少到 ~1500ms,而对于 perivesta 的 2GB 文件,加载时间不会从 7429ms 减少到 644 ms(奇怪,不同系统上的速度有何不同 ;-) )
void loadData2(std::string path)
{
std::ifstream FILE(path, std::ios::in | std::ifstream::binary);
if (RecordData.size() > 0) // Clear data
{
for (int n = 0; n < RecordData.size(); n++)
RecordData[n].clear();
RecordData.clear();
}
int size = 0;
FILE.read(reinterpret_cast<char*>(&size), sizeof(size));
RecordData.resize(size);
for (auto& v : RecordData) {
// load its size
int size2 = 0;
FILE.read(reinterpret_cast<char*>(&size2), sizeof(size2));
v.resize(size2);
// load its contents
FILE.read(reinterpret_cast<char*>(&v[0]), v.size() * sizeof(float));
}
}
//RecordData[n].resize(size2); // This doesn't make a difference in speed
如果您使用这一行(同时不更改其余代码),代码会变慢,而不是变快!
resize
更改向量的大小,然后将更多元素推入它,导致向量的大小是您实际需要的两倍。
我猜你想要的是 reserve
。 reserve
只分配容量而不改变向量的大小,然后推送元素可以预期更快,因为内存只分配一次。
或者使用 resize
,然后分配给已经存在的元素。
首先,由于您预先知道元素的数量,因此您应该在向量中 reserve
space 以防止在向量增长时进行不必要的重新分配。其次,所有这些 push_back
可能会让您付出代价。该功能确实有一些开销。第三,正如 Alan 所说,一次读取整个文件不可能有什么坏处,如果您先调整(而不是保留)向量的大小,就可以做到这一点。
所以,综上所述,我会这样做(一旦您将数据大小读入 size2
):
RecordData.resize(size2); // both reserves and allocates space for size2 items
FILE.read(reinterpret_cast<char*>(RecordData.data()), size2 * sizeof(float));
我认为这是最优的。
不幸的是,在这种情况下,IMO,当您调用 resize
时,std::vector
坚持 zero-initialising 所有 size2
元素,因为您将立即覆盖他们,但我不知道有什么容易预防的方法。您需要使用自定义分配器,这可能不值得付出努力。
这是 Alan Birtles 的评论的实现:阅读时,通过一个 FILE.read
调用而不是许多单独的调用来读取内部向量。这大大减少了我系统上的时间:
这些是 2GB 文件的结果:
Writing took 2283 ms
Reading v1 took 7429 ms
Reading v2 took 644 ms
这里是产生这个输出的代码:
#include <vector>
#include <iostream>
#include <string>
#include <chrono>
#include <random>
#include <fstream>
using RV = std::vector<std::vector<float>>;
void saveData(std::string path, const RV& RecordData)
{
std::ofstream FILE(path, std::ios::out | std::ofstream::binary);
// Store size of the outer vector
int s1 = RecordData.size();
FILE.write(reinterpret_cast<const char*>(&s1), sizeof(s1));
// Now write each vector one by one
for (auto& v : RecordData) {
// Store its size
int size = v.size();
FILE.write(reinterpret_cast<const char*>(&size), sizeof(size));
// Store its contents
FILE.write(reinterpret_cast<const char*>(&v[0]), v.size() * sizeof(float));
}
FILE.close();
}
//original version for comparison
void loadData1(std::string path, RV& RecordData)
{
std::ifstream FILE(path, std::ios::in | std::ifstream::binary);
if (RecordData.size() > 0) // Clear data
{
for (int n = 0; n < RecordData.size(); n++)
RecordData[n].clear();
RecordData.clear();
}
int size = 0;
FILE.read(reinterpret_cast<char*>(&size), sizeof(size));
RecordData.resize(size);
for (int n = 0; n < size; ++n) {
int size2 = 0;
FILE.read(reinterpret_cast<char*>(&size2), sizeof(size2));
float f;
//RecordData[n].resize(size2); // This doesn't make a difference in speed
for (int k = 0; k < size2; ++k) {
FILE.read(reinterpret_cast<char*>(&f), sizeof(f));
RecordData[n].push_back(f);
}
}
}
//my version
void loadData2(std::string path, RV& RecordData)
{
std::ifstream FILE(path, std::ios::in | std::ifstream::binary);
if (RecordData.size() > 0) // Clear data
{
for (int n = 0; n < RecordData.size(); n++)
RecordData[n].clear();
RecordData.clear();
}
int size = 0;
FILE.read(reinterpret_cast<char*>(&size), sizeof(size));
RecordData.resize(size);
for (auto& v : RecordData) {
// load its size
int size2 = 0;
FILE.read(reinterpret_cast<char*>(&size2), sizeof(size2));
v.resize(size2);
// load its contents
FILE.read(reinterpret_cast<char*>(&v[0]), v.size() * sizeof(float));
}
}
int main()
{
using namespace std::chrono;
const std::string filepath = "./vecdata";
const std::size_t sizeOuter = 16000;
const std::size_t sizeInner = 32000;
RV vecSource;
RV vecLoad1;
RV vecLoad2;
const auto tGen1 = steady_clock::now();
std::cout << "generating random numbers..." << std::flush;
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_real_distribution<float> dis;
for(int i = 0; i < sizeOuter; ++i)
{
RV::value_type inner;
for(int k = 0; k < sizeInner; ++k)
{
inner.push_back(dis(rng));
}
vecSource.push_back(inner);
}
const auto tGen2 = steady_clock::now();
std::cout << "done\nSaving..." << std::flush;
const auto tSave1 = steady_clock::now();
saveData(filepath, vecSource);
const auto tSave2 = steady_clock::now();
std::cout << "done\nReading v1..." << std::flush;
const auto tLoadA1 = steady_clock::now();
loadData1(filepath, vecLoad1);
const auto tLoadA2 = steady_clock::now();
std::cout << "verifying..." << std::flush;
if(vecSource != vecLoad1) std::cout << "FAILED! ...";
std::cout << "done\nReading v2..." << std::flush;
const auto tLoadB1 = steady_clock::now();
loadData2(filepath, vecLoad2);
const auto tLoadB2 = steady_clock::now();
std::cout << "verifying..." << std::flush;
if(vecSource != vecLoad2) std::cout << "FAILED! ...";
std::cout << "done\nResults:\n" <<
"Generating took " << duration_cast<milliseconds>(tGen2 - tGen1).count() << " ms\n" <<
"Writing took " << duration_cast<milliseconds>(tSave2 - tSave1).count() << " ms\n" <<
"Reading v1 took " << duration_cast<milliseconds>(tLoadA2 - tLoadA1).count() << " ms\n" <<
"Reading v2 took " << duration_cast<milliseconds>(tLoadB2 - tLoadB1).count() << " ms\n" <<
std::flush;
}