在c++中快速加载数值数据

Quickly loading numerical data in C++

本文关键字:加载 数值数据 c++      更新时间:2023-10-16

我正在研究一个特征检测程序,它使用图像中各种地标的统计模型。该模型使用了大约100个不同的地标,每个地标的相关数据由16个双精度矩阵组成,每个矩阵的大小约为160x160。

我目前为每个地标使用一个文本文件,并将每个矩阵的值存储为空格分隔的行。为了读取数据,我每次从每个文件中读取一行,并将其传递给一个函数,该函数从该行生成一个stringstream,然后一次一个地从该流中读取矩阵的值。

在我的电脑上,这大约需要90秒来加载模型使用的~ 4000万倍。肯定有更快的方法来做这件事,但我没有从谷歌上找到任何有用的东西,我也没有这方面的经验。

如有任何建议,我将不胜感激。

编辑:洛基让我张贴代码,所以我把它显示在下面。loadFromFile对每个地标被调用一次。每个里程碑文件的第一行说明模型为该里程碑使用了多少个级别(每个级别使用四个矩阵;默认情况下有四个级别)。这是一个可怕的混乱,但我不确定为什么这是如此惊人的慢。

void loadFromFile(string filename)
{
    ifstream modelData(filename.c_str(), ifstream::in);
    string line;    
    getline(modelData,line);
    int numberOfLevels = atoi(line.c_str());
    for(size_t ii = 0; ii < numberOfLevels; ++ii)
        readProfileStats(modelData);        
    modelData.close();              
}
void readProfileStats(ifstream& fileStream)
{
    string line;
    getline(fileStream, line);
    Vector meanProfile = readMatrixFromString(line);
    getline(fileStream, line);
    Matrix principalComponents = readMatrixFromString(line);
    getline(fileStream, line);  
    Matrix covarianceMatrixInverse = readMatrixFromString(line);
    m_statsLevels.push_back(ProfileStats(meanProfile, principalComponents, covarianceMatrixInverse));
}
Matrix readMatrixFromString(const string& line)
{
    stringstream stream(line);
    size_t numRows;
    size_t numCols; 
    stream >> numRows;  
    stream >> numCols;      
    Matrix matrix(numRows,numCols);
    for(int ii = 0; ii < numRows; ++ii)
    {                                       
        for(int jj = 0; jj < numCols; ++jj)             
            stream >> matrix(ii,jj);                                    
    }                                                       
    return matrix;                      
}

尝试使用scanf库:

r1.cpp

> cat r1.cpp 
#include <iostream>
int main()
{
    double x;
    long   count = 0;
    while(std::cin >> x)
    {
        ++count;
    }
    std::cout << count << ": " << x << "n";
}

r2.cpp

> cat r2.cpp 
#include <iostream>
#include <stdio.h>
int main()
{
    double x;
    long   count = 0;
    while(fscanf(stdin, "%lf", &x) == 1)
    {
        ++count;
    }
    std::cout << count << ": " << x << "n";
}

结果串行

> g++ -O3 r1.cpp -o r1
> time (cat t | ./r1)
40000000: 9.36e+08
real    0m57.669s
user    0m56.992s
sys 0m1.688s
> g++ -O3 r2.cpp -o r2
> time (cat t | ./r2)
40000000: 9.36e+08
real    0m14.419s
user    0m13.897s
sys 0m1.352s

所以用IOstream读取40,000,000个数字花的时间比我预期的要长大约60秒。而使用scanf只需要15秒。所以大约快了4倍。

我做了同样的事情,只是将双精度数的二进制值写入文件。
注意,你必须把它们写成二进制,当然,你失去了所有的类型安全性和可移植性。

double x;
std::cout.write((char*)&x, sizeof(x));

r1b.cpp

> cat r1b.cpp 
#include <iostream>
int main()
{
    double x;
    long   count = 0;
    while(std::cin.read((char*)&x, sizeof(double)))
    {
        ++count;
    }
    std::cout << count << ": " << x << "n";
}

r2b.cpp

> cat r2b.cpp 
#include <iostream>
#include <stdio.h>
int main()
{
    double x;
    long   count = 0;
    while(fread(&x, sizeof(double), 1, stdin) == 1)
    {
        ++count;
    }
    std::cout << count << ": " << x << "n";
}

结果二进制

> time (cat t2 | ./r1b )
40000000: 9.36e+08
real    0m3.930s
user    0m3.592s
sys 0m0.984s
> time (cat t2 | ./r2b )
40000000: 9.36e+08
real    0m2.110s
user    0m1.840s
sys 0m0.804s

正如在注释中建议的那样,这里的问题是必须将数据从文本转换为数值。通过以二进制格式存储数据,可以完全消除这种情况。有一些库可以处理这个问题,比如hdf5。使用像这样的流行库有很多优点,因为您可以获得完整的预构建工具链,并支持除c++之外的许多其他语言。然而,缺点是在学习如何使用这些系统之前需要做很多工作。如果这是一个一次性的研究项目,我强烈建议您考虑一种不同的、更简单的方法:一旦第一次创建了结构,只需将数据结构写入或mmap到磁盘文件中。然后,创建一个函数,将二进制文件直接读入或mmap到您的数据结构中。为程序提供调用mmap函数而不是解析函数的选项。通过这种方式,您将看到显著的加速。