请问如何提高文件的读写速度

摘抄某个位置的帖子.URL忘记了.

 

在提取游戏资源的时候,我编程用的是C++中的 ifstream , ofstream, 可是在读写文件的时候速度很慢,硬盘狂转,请问一下有人知道如何提高速度么?多谢!

悠久扬笛 2007-05-15 11:22
用内存映射方式可能会好点

 

Asakura倉貓 2007-05-15 13:26
直接用win32 API试试如何
CreateFileA、ReadFile、WriteFile

记住CreateFileA时dwFlagsAndAttributes参数至少要FILE_FLAG_RANDOM_ACCESS+FILE_FLAG_SEQUENTIAL_SCAN,这样速度会快很多

 

rednaxela 2007-05-15 14:47
硬盘狂转很正常.程度问题.读写大量数据的时候肯定得狂转,但是实际的读写次数却未必一样.简单说如果能用较少的读写次数完成较多的工作,那么硬盘负荷会降低并且程序的速度也会加快.同样是4K的数据一次写出会比写出4K次的1字节会快非常多.相对的,减少硬盘读写次数意味着消耗更多的内存来作为缓冲.

传说中C++标准库里的stream实现...这在高强度的读写操作时是个瓶颈.涉及大量文件操作的ACM题向来会提示不要用C++标准库里的istream/ostream/iostream/ifstream/ofstream/fstream.
Quote:
In general the stream classes should be pretty efficient, but performance can be improved further in applications in which I/O is performance critical.

效率低不是标准库的错...它本来设计是要实现出不错的性能的.不过它在一般使用的时候还有许多你或许根本用不到的功能,例如说一些格式化的操作.于是就慢了.另外stream的buffering也可能造成效率问题;至于说为了I/O操作创建了一大堆对象...那个也罢.要想"修理"这些问题可以自己写basic_streambuf<>的具体特化.或者直接使用stream buffer会稍微快一点:

像这样直接写出
Copy code
#include

int main()
{
    // copy all standard input to standard output
    std::cout << std::cin.rdbuf();
}


和这样直接读入
Copy code
#include

int main()
{
    // copy all standard input to standard output
    std::cin >> std::cout.rdbuf();
}


cin和cout不过是连接到stdin和stdout的istream和ostream实例.对ifstream和ofstream的实例也是一样的.

不过...不用C++标准库里的stream或许更根本一些.在Windows上的话直接用Win32 API然后自己管理buffer对于简单操作来说很顺手.使用mapping也是一种不错的方案.boost库也有对mapping _file的支持.

 

joyful 2007-05-15 15:05
Quote:
引用第1楼悠久扬笛于2007-05-15 11:22发表的  :
用内存映射方式可能会好点

内存映射?请问一下具体该怎么做?

 

悠久扬笛 2007-05-15 16:01
Quote:
引用第4楼joyful于2007-05-15 15:05发表的  :

内存映射?请问一下具体该怎么做?


用CreateFileMapping()
具体可以参考msdn

 

Asakura倉貓 2007-05-15 16:15
CreateFileMapping()在单线程时对效率提升不会很明显恩,因为读入和写出磁盘的实际速度都差不多

这个API的最佳使用情况是在对一个不太大的文件做多线程异步操作,比如对一个文件数据进行重排列,这样效率提升才很明显

其他方面的用处就是在操作一个文件比操作内存更方便而又不能改变物理文件的实际内容的情况,比如使用比较复杂的算法对文件进行加密解密,因为对这个函数产生的句柄进行操作并不会马上写入文件


Quote:
引用第3楼rednaxela于2007-05-15 14:47发表的  :
硬盘狂转很正常.程度问题.读写大量数据的时候肯定得狂转,但是实际的读写次数却未必一样.简单说如果能用较少的读写次数完成较多的工作,那么硬盘负荷会降低并且程序的速度也会加快.同样是4K的数据一次写出会比写出4K次的1字节会快非常多.相对的,减少硬盘读写次数意味着消耗更多的内存来作为缓冲.
.......


FX君你这个观点有点小问题
虽然从硬盘寿命来说,写入的次数的确是越少越好,但是对效率来说却并非如此
实际操作多些就能发现,一个大文件并不是以越少次数写入就越快
每次写入数据的最佳大小要依照硬盘缓存大小来决定
如果写的是一个要在别人机器上使用的通用程序,那么就要考虑不确定的缓存
现在硬盘一般缓存为4M,考虑到还有其他程序在使用缓存(比如QQ这个磁盘缓存强盗),所以一般将数据分块大小定为1M左右为好
再者就是要在写入磁盘的函数之后马上加一句等待系统的指令,这样在硬盘完成写入过程后程序才会继续,这在提高效率上非常重要

然后就能发现,这样写入大文件时速度绝对比一次写入要快多而且也不会让系统卡(当然你有啥算法在占用CPU的话就不能算数…… OTL)

 

joyful 2007-05-15 17:50
那用 C 的 fscanf 和 fprintf 会不会好一点?

 

rednaxela 2007-05-15 18:12
Quote:
引用第6楼フェイト于2007-05-15 16:15发表的  :
实际操作多些就能发现,一个大文件并不是以越少次数写入就越快
每次写入数据的最佳大小要依照硬盘缓存大小来决定
如果写的是一个要在别人机器上使用的通用程序,那么就要考虑不确定的缓存
现在硬盘一般缓存为4M,考虑到还有其他程序在使用缓存(比如QQ这个磁盘缓存强盗),所以一般将数据分块大小定为1M左右为好

嗯,学到了~我平时写出是用512K的缓存,速度尚且可以(与单字节写出相比=_=),所以一直没仔细想清楚这关系...

Quote:
引用第7楼joyful于2007-05-15 17:50发表的  :
那用 C 的 fscanf 和 fpritf 会不会好一点?

C的FILE系列至少比C++现有标准库里的stream要快一些(可能是错觉).FILE系列,fopen/fread/fgetc/fgets/fscanf/fseek/fwrite/fputc/fputs/fprintf/fclose,其实在底层也是有系统控制的缓存.大小...我不知道.不过至少它不会像stream那样需要创建很多对象实例,相对来说效率应该是高些的.scanf类函数要注意泄漏问题就是了...

 

悠久扬笛 2007-05-15 18:43
FILE自己维护了一套缓存机制
FILE会使用默认的一个缓存值作为io缓存(4k),或者也可以通过setbuf来设置这个缓存的大小

假设你fread 1字节 会导致ReadFile 4k,然后fread再将要读取的数据copy到指定的缓冲区中。以后访问只要不过这个边界,就一直从该io缓存中读取,fwrite也是,直到超过io缓存边界才真正的调用WriteFile。可以调用flush主动强制刷新从而调用WriteFile 或者fclose被动刷新调用WriteFile(这时fclose会阻塞)。

再说一下硬盘的 硬盘的cache由硬盘控制器管理和使用 就像处理器的cache没法直接操作一样 写硬盘的时候会先写入cache 然后硬盘内部会把数据慢慢写入磁盘 这个过程中没有优化 也就是说硬盘驱动按什么顺序写的 写入磁盘就是什么顺序
而实际上 硬盘是个随机访问设备 先写哪个后写哪个无所谓 所以一般在把应用层的io访问转化为底层的io请求后 内核层会做io请求优化排序

假设一个io队列上目前挂着10个请求 内核层会事先计算每个请求在物理上的位置 然后进行排序 以保证磁头转动一周,尽量让10个请求中的多个在一周内完成,想像一下 最好的情况 10个请求都在一个盘面上 磁头旋转1周 10个请求全部完成 最坏的情况 要转10周 10周的原因是一次只能操作一个磁头 而10个请求可能不幸的在10个盘面上(这时候内核也不会再排序了)

因此让自己的io操作尽可能维持在连续的磁盘空间 且在物理上不跨越盘面 这样效果最好。为此你可能需要硬盘的准确的参数 并精确计算。

缓存的优势在高强度的io操作会被抵消 因为硬盘的写入速度始终跟不上处理器的请求 cache只能帮助缓冲一下 cache越大 缓冲的时间越长 当cache填满 硬件上ready信号为无效 硬盘驱动不能再写了 只能挂起内核的io队列 这时候上层还在不停的请求 内核层要么继续往io请求队列上挂装请求 要么阻塞发起io的进程 等到cache有空间了 硬件使能ready信号 驱动重新从内河的io请求队列上摘取io请求 再填cache 又满 。。。。 也就是说cache的优势只在一开始的缓存时间上 这个优势对于小的io请求特别有好处 因为能在填满cache之前不会遭到阻塞或挂起

纵上所述 软件上其实做的很有限而且也很累 何必呐 orz。。。。

 

ravenex 2007-05-31 03:38
想请教一下各位大大,不考虑数据在硬盘上的读写,仅考虑在内存的操作,那同样大的数据块,以较小的单位,例如1字节,或者以较大的单位,例如16字节,分别做遍历,速度的差异是什么因素带来的?假设要给一个数据块按字节做固定的异或,可以在(1)unsigned char, (2)unsigned int, (3)unsigned long long等单位上做,同时假设机器上的通用寄存器是32位,那(2)肯定比(1)快,因为循环次数少了而机器码长度几乎一样,但是(2)与(3)的差别在什么地方?

还想请教一下,现在有很多硬盘装在外接盒子里,盒子上也有cache吗?一般有多大?对硬盘的读写性能是否有影响?

 

悠久扬笛 2007-05-31 10:22
首先unsigned long long 这个类型 我记得在vc下没有 在gcc上有 代表64位长的数据类型 在32位机器上使用64位数据类型的话,结果就是编译器对64位数据长的数据进行两次访问。

cahce的问题我上面解释过了 对于高压力的io操作 cache的大小作用没那么特别明显了 但是对于轻量和中量的访问 cache越大 阻塞硬盘驱动程序对硬盘的访问的机会越少,速度自然越快。

软件上所有对磁盘的访问 最终都变成硬盘驱动程序对硬盘设备的访问。
硬盘是慢速设备 对他写入的时候 如果没有cache 那么每次对他访问 他只有确切的完成后才允许驱动程序再访问它,但是等待它完成io操作是很费时的 因此引入了cache 每次驱动程序将数据写入硬盘的cache 因为cahce有一定容量 因此不用等待硬盘完成io操作 就可以继续写入 而另一方面硬盘从cache中取数 然后一次次的执行io操作 这样cache就像个fifo cache越大 容量越多 驱动程序写入被阻塞的可能性就越少 但是硬盘毕竟慢 cache也只是权宜之计 如果在cache被填满前不再有请求了 那么8M的cache就比2M的cache能缓存更多的数据 自然速度更快 但是如果你的io请求数据量非常大 那么8M也好 2M也好 总会被快速填满 届时2者就没什么分别了 因此cache大小对性能的影响只在轻量级和中量特别有效果。

 

悠久扬笛 2007-05-31 10:31
另外就是实际的性能还和很多因素相关 比如你用的读写函数 像fread、fwrite这样的 都在用户层实现了自己的io缓冲 你读 实际上它会读1块(和文件系统的块的含义不同) 你写1字节 它只把数据写入内存的io缓冲里 而不实际写入
另外就是文件系统 因为文件系统的基本单位不是字节 而是块 具体的块大小 每个文件系统都不同
比如你用ReadFile直接读取1字节 那么文件系统可能会读取1个块的数据 这样文件系统自己也有一个缓存
再往下是内核提供的通用io缓存 这个缓存和具体文件系统无关 只要有对io的访问 就把数据缓存在自己维护的基于页的缓存里 最后就是硬件级别的cache了 其中文件系统和内核的通用缓存 这2个设计操作系统的实现 每个操作系统的具体实现方式可能不同。

 

ravenex 2007-05-31 15:26
那在32位机器上使用64位的数据类型并不会比使用32位的数据类型来执行固定的循环操作要快,是这样的吗?小的还是有点不明白,还得多多修炼才行

多数游戏的封包里,单个文件都不会达到4M吧?那研究一下cache的最佳使用经验还是有意义。不然也不会出现B树等的数据结构专门着眼于硬盘读写的特点

 

悠久扬笛 2007-05-31 18:49
Quote:
引用第13楼ravenex于2007-05-31 15:26发表的  :
那在32位机器上使用64位的数据类型并不会比使用32位的数据类型来执行固定的循环操作要快,是这样的吗?小的还是有点不明白,还得多多修炼才行

多数游戏的封包里,单个文件都不会达到4M吧?那研究一下cache的最佳使用经验还是有意义。不然也不会出现B树等的数据结构专门着眼于硬盘读写的特点


不 实际上会快 因为你无形中对2个4字节的访问放在一个循环里做了 这个优化叫循环展开 这种优化通常编译器做不到 是人为的优化 有数据显示展开16-32(也就是一个循环内,每次处理64-128字节,每次4字节)能够提高性能。(主要是充分利用了指令流水线)

我认为专门研究硬盘的cache意义不大 因为从你的程序到实际写入硬盘 中间过了好几层 而且不能只把硬盘当作你一个人使用的对象 因此优化的时候主要不是研究针对硬盘cache的优化 当然除非你跨过所有的软件层 直接和驱动打交道(数据库通常会这么做)

 

ravenex 2007-05-31 19:11
怪小的没说清楚,是想说自己写的程序所管理的buffer的大小,还是值得研究的
硬盘本身的cache,还有DMA什么的,每层上都有缓存,隔那么远也管不了了。像小的这样只写应用的的确不需要关心那么底层的实现

你可能感兴趣的:(C/C++)