我需要从大文件的几个位置读取字节数组。 我已经对文件进行了优化,以便尽可能少地阅读部分,并且这些部分尽可能紧密地结合在一起。
我有20个这样的电话:
m_content.resize(iByteCount);
fseek(iReadFile,iStartPos ,SEEK_SET);
size_t readElements = fread(&m_content[0], sizeof(unsigned char), iByteCount, iReadFile);
iByteCount平均约为5000。
在使用fread之前,我使用了内存映射文件,但结果大致相同。
第一次呼叫时,我的呼叫仍然太慢(大约200毫秒)。当我用相同的字节部分重复相同的调用来读取时,它非常快(大约1毫秒),但这对我没有帮助。
文件很大(大约200 mb)。 在此调用之后,我必须从文件的不同部分读取双值,但我无法避免这种情况。
我不想将其拆分为2个文件。我已经看到了其他人使用的“巨大的文件方法”,他们以某种方式克服了这个问题。
如果我使用内存映射,那么第一次读取调用总是很慢。如果我再重复阅读本节,它会快速闪电。当我从另一个部分读取时,它第一次很慢,但第二次快速闪电。
我不知道为什么会这样。
有人对我有任何想法吗? 谢谢。
答案 0 :(得分:6)
磁盘驱动器有两个(实际上是三个)限制其速度的因素:访问时间,顺序带宽和总线延迟/带宽。
您最感受的是访问时间。访问时间通常在毫秒球场。在典型的硬盘上,必须进行搜索需要超过5(通常超过10)毫秒。请注意,打印在磁盘驱动器上的数字是“平均”时间,而不是最差时间(在某些情况下,它似乎更接近“最佳”而非“平均”)。
即使对于慢速磁盘,顺序读取带宽通常高达60-80 MiB / s,对于更快的磁盘,顺序读取带宽通常为120-150 MiB / s(或者在固态下为> 400MiB)。总线带宽和延迟是您通常不关心的事情,因为总线速度通常超过驱动器速度(除非您在SATA-2上使用现代固态磁盘,或在SATA-1上使用15k硬盘,或通过USB使用任何磁盘) )。
另请注意,您无法更改驱动器的带宽,也无法更改总线带宽。你也不能改变寻找时间。但是,您可以更改搜索的数量。
在实践中,这意味着您必须尽可能避免寻求。如果这意味着读取您不需要的数据,请不要害怕这样做。读取100 kiB比读取5 kiB更快更多,提前读取90 kbB,读取另外5 kiB。
如果可以的话,一次阅读整个文件,只使用你感兴趣的部分.200 MiB不应该是现代计算机上的一大障碍。然而,使用fread
将200 MiB读入分配的缓冲区可能是禁止的(这取决于您的目标体系结构,以及您的程序正在做什么)。但不要担心,你已经有了解决问题的最佳方案:内存映射
虽然记忆映射不是一个“神奇的加速器”,但它仍然尽可能接近“神奇”。
内存映射的最大优点是可以直接从缓冲区缓存中读取。这意味着操作系统将预取页面,你甚至可以要求它更积极地预取,所以你的所有读取都将是“即时的”。此外,存储在缓冲区缓存中的内容在某种意义上是“免费的” 不幸的是,内存映射并不总是很容易(特别是因为操作系统通常提供的文档和提示标志具有欺骗性或适得其反的效果)。
虽然您无法保证曾经读过的内容一直停留在缓冲区中,但实际情况是任何“合理”大小的情况。当然,操作系统不能也不会将数TB的数据保存在RAM中,但是大约200 MiB的内容将非常可靠地保留在“普通”现代计算机上的缓冲区中。从缓冲区读取或多或少在零时间内工作 因此,您的目标是让操作系统尽可能按顺序将文件读入其缓冲区。除非机器耗尽物理内存,因此它被迫丢弃缓冲区页面,这将是快速的(如果发生这种情况,其他所有解决方案都会同样慢)。
Linux有readahead系统调用,可以预取数据。不幸的是,它会一直阻塞,直到获取数据,这不是您可能想要的(因此您必须使用额外的线程)。 madvise(MADV_WILLNEED)
不太可靠,但可能更好。 posix_fadvise
也可能有效,但请注意Linux将readahead限制为默认预读大小的两倍(即256kiB)。
不要让自己被文档愚弄,因为文档具有欺骗性。似乎MADV_RANDOM
是更好的选择,因为您的访问权限是“随机的”。对操作系统诚实地说你正在做什么是不合理的,不是吗?通常是的,但不是这里。 这只是关闭预取,这与你真正想要的完全相反。我不知道这背后的理由,也许是一些不明智的尝试来收敛记忆 - 无论如何它都会对你的表现产生不利影响。
Windows(因为Windows 8,仅适用于桌面)具有PrefetchVirtualMemory,它可以完全满足人们的需求,但不幸的是它只能在最新版本上使用。在旧版本中,只有......没有。
在映射中填充页面的一种非常简单,高效,和可移植的方法是启动一个错误每个页面的工作线程。这听起来很可怕,但效果非常好,并且与操作系统无关
像volatile int x = 0; for(int i = 0; i < len; i += 4096) x += map[i];
这样的东西就足够了。我正在使用这样的代码在访问它们之前对故障前页面进行故障排除,它的工作速度与任何其他填充缓冲区的方法无关,并且使用的CPU很少。
答案 1 :(得分:4)
(根据OP的要求转到答案)
你无法更快地从文件中读取(没有神奇的旗帜可以说“读得更快”)。您的硬件存在问题,或者200mS存在问题需要多长时间
答案 2 :(得分:1)
1)第一次阅读和后续阅读之间访问速度的差异是完全可以理解的:第一次调用实际上是从磁盘读取文件,this takes time。但是你的内核(不提磁盘控制器)会保持被访问的数据被缓冲,因此当你第二次访问它时,它是纯内存访问(1ms)。 即使您只需要访问文件的很小一部分,libc / kernel / controller优化也会在相当大的块中访问磁盘。您可以阅读libc / OS / controller doc以尝试在这些块上对齐读取。
2)您正在使用流输入,尝试使用直接open
/ read
/ close
函数:低级I / O具有较少的开销(显然)。没有什么比这更快,所以如果你仍然觉得这个太慢,你就会遇到操作系统或硬件问题。
答案 3 :(得分:0)
因为它看起来你有一个很好的基准,尝试在你的fread电话中切换大小和计数。读取1次1000字节将比1000 x 1字节快。
答案 4 :(得分:0)
磁盘速度很慢,正如您所指出的那样,延迟来自第一次访问 - 即磁盘启动并访问必要的扇区。你总是会花一次这笔费用。
使用内存映射IO可以稍微提高性能。请参阅mmap(Linux)或CreateFileMapping + MapViewOfFile(Windows)。
答案 5 :(得分:0)
我已经对文件进行了优化,以便尽可能少地阅读
如果我错了,请纠正我,但是在参考正在优化的文件时,我假设你的意思是你已经订购了这些部分,以尽量减少发生的读取次数,而不是我要建议的内容
受IO限制可能是由于寻道时间,因此除了获得更快的存储介质外,您的选择也是有限的。
我有两个可能的想法: -
1)压缩存储的数据,这可能会使您的读取时间稍微快一些,但仍然无助于寻道时间。你必须测试这是否有益。
2)如果相关,一旦您检索到一个数据块,将其移动到一个线程并开始处理它,同时进行另一个读取。你可能已经这样做了,但如果没有,我认为值得一提。