我想读取文件的每个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();
}
有人可以给我一些建议来提高这段代码的性能吗?
答案 0 :(得分:7)
不要使用seek
,我会mmap
整个文件,然后只需读取所需位置的字节。
我不打算为你编写代码,但它应该符合以下几行:
mmap
整个文件Vertex
并推入向量答案 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()
或读取整个文件)可能会更快,也可能不会更快。