c ++有效地读取每个X字节中的前几个字节

时间:2016-08-05 12:57:45

标签: c++ windows performance file bytebuffer

我想读取文件的每个X * 16字节中的前16个字节。我编写的代码可以工作,但由于函数调用很多,所以代码很慢。

std::vector<Vertex> readFile(int maxVertexCount) {
    std::ifstream in = std::ifstream(fileName, std::ios::binary);
    in.seekg(0, in.end);
    int fileLength = in.tellg();
    int vertexCount = fileLength / 16;

    int stepSize = std::max(1, vertexCount / maxVertexCount);

    std::vector<Vertex> vertices;
    vertices.reserve(vertexCount / stepSize);
    for (int i = 0; i < vertexCount; i += stepSize) {
        in.seekg(i * 16, in.beg);
        char bytes[16];
        in.read(bytes, 16);
        vertices.push_back(Vertex(bytes));
    }
    in.close();
}

有人可以给我一些建议来提高这段代码的性能吗?

4 个答案:

答案 0 :(得分:7)

不要使用seek,我会mmap整个文件,然后只需读取所需位置的字节。

我不打算为你编写代码,但它应该符合以下几行:

  1. 打开文件,计算文件大小
  2. mmap整个文件
  3. 迭代计算每个块的地址的步长
  4. 根据块构建每个Vertex并推入向量
  5. 返回矢量。

答案 1 :(得分:2)

可能不是函数调用自身,而是非顺序访问模式,从大文件中选取小段。即使您只读取16个字节,存储子系统也可能会读取(并缓存)较大的块。您的访问模式对于典型的I / O是致命的。

(分析应该显示磁盘访问是否是瓶颈。如果是&#34;许多函数调用&#34;,CPU将是。)

所以,首先,你可以改变这个要求吗?
这在所有情况下都是最简单的方法。

你能少分散吗?例如。而不是读取顶点0,20,40,...,1000,读取顶点0,1,2,3,4,100,101,102,103,104,200,201,202,203,204,... - 相同数量的顶点,来自&#34;所有部分&#34;该文件。

其次,特定于操作系统的优化 没有可移植的方法来控制操作系统级别的缓存。

一种解决方案是内存映射文件(Windows上为CreaterFileMapping,Linuxy系统上为mmap),如@Nim所示。这可以省略一个内存副本,但仍会读取整个文件。

对Linux没什么帮助,但在Windows上你有CreateFile的参数:

  • FILE_FLAG_NO_BUFFERING这基本上意味着进行缓冲,让你更好地控制发生的缓存,但是你不能无所畏惧地寻求+阅读。

  • FILE_FLAG_SEQUENTIAL_SCAN只是将缓存告知不存储旧数据

这些都不会解决您的访问模式的问题,但第一个可能会稍微调整它 - 特别是如果您的步骤大于磁盘扇区,第二个可以从子系统承受压力。

第三,快照。
最佳选择可以是将交错快照存储在关联文件中。

对于特定的maxVertexCount,快照可能只是您的操作的结果。或多个快照,如mipmapping。我们的想法是通过顺序读取来替换分散的读取。

或者,快照可以以交错顺序存储数据。对于128个顶点,您可以按顺序存储顶点(粗略地,要注意off-by&lt; -one,零vs-one-based和别名效果,以及我的错误):

64, 32, 96, 16, 48, 80, 112 8, 24, 40, 56, 72, 88, 104, 120 ...

无论您是读取前3个或前7个,前15个还是前31个......,样本均匀分布在整个文件中,就像您的原始代码一样。将它们重新排列在内存中会快得多 - 特别是如果它只是一小部分。

注意:您需要一个强大的算法来检测您的快照是否过时,而不是发生在&#34;最后写入日期&#34;在不同的文件系统上。 A&#34;改变计数器&#34;在主文件中将是最安全的(尽管它会增加更改的成本)。

第四,更改文件格式

(如果你可以控制那个) 上面建议的交错存储可以用于整个文件。但是,这对处理有很大的影响 - 特别是如果你需要恢复原始的&#34;在某些时候订购。

优雅的选项是将这样的交错子集作为文件的一部分,以原始顺序的完整顶点列表。有一个截止stepSize,这对此不再有帮助,可能是磁盘的2 *扇区/块大小。因此文件大小只会增加几个百分点。但是,写入会花费更多,顶点数量的变化会更加严重。

别名警告

如果这是为了得到一个&#34;统计&#34;或者&#34;视觉上足够&#34;采样时,固定的stepSize可能会有问题,因为它可以使用数据中存在的任何模式创建混叠效果(想想莫尔模式)。

在这种情况下,随机抽样将是可取的。这可能听起来很可怕并使一些解决方案更难以实现,但通常是避免许多次优案例的最简单方法。

答案 2 :(得分:1)

...如果由于某种原因你无法使用map,请将文件读入一个缓冲区中,然后再大肆宣传#34;&#34; ...缓冲区大小是X字节的倍数。继续读入该缓冲区(注意注意 读取的字节数)。直到你到达文件的末尾。

您专门尝试避免的是一大堆物理 I / O操作:磁盘读/写机制的移动。由于这个原因,操作系统喜欢缓冲内容,但它只能猜测 你的应用程序正在尝试做什么,它可能会猜错。一旦磁盘将读/写磁头定位到正确的磁道(&#34;寻找时间&#34;),它就可以在一次旋转中检索整个磁道的数据。但是&#34;寻找时间&#34;比较慢。

映射文件,然后读取映射文件中的数据非随机,显然是最有利的策略,因为现在操作系统确切知道究竟是什么&#39继续。

答案 3 :(得分:1)

首先,我告诉你,即使您发布的代码缺少return语句,也会从定义中返回值,因此必须复制该向量。通过引用将它传递给您的方法,因此不需要复制。

您可以使用低级别pread()进行阅读,而无需寻求:

void readFile( size_t maxVertexCount, std::vector<Vertex> &vertices )
{
    struct stat sb;
    int fd = std::open( fileName, O_RDONLY );
    std::fstat( fd, &sb );

    size_t vertexCount = sb.st_size / 16;

    size_t stepSize = std::max( 1, vertexCount / maxVertexCount );

    vertices.reserve( vertexCount / stepSize );

    for ( off_t i = 0; i < vertexCount; i += stepSize)
    {
        char bytes[ 16 ];
        std::pread( fd, bytes, 16, 16 * i );
        vertices.push_back( Vertex( bytes ) );
    }

    std::close( fd );
}

您应该能够找出所需的错误处理和头文件。

这利用了内核的页面缓存和可能的预读。根据您的操作系统和配置,其他方法(如mmap()或读取整个文件)可能会更快,也可能不会更快。