in c c++ ~ read.

pread/pwrite,read/write,fread/fwrite几种文件IO方式

最近有一个性能优化问题,需要研究下大数据量文件IO操作与多次小数据量文件IO操作的性能区别,查了相关的资料,得到下面的信息,对于文中的内容的理解和描述依旧不够深入,后面还需要继续研究学习。

首先从内核态及用户态进行区分:
内核态的函数的每一次调用都涉及到系统调用sys-call,用户态则不一定。

  • 内核态:
    下面两组IO读写接口由POSIX定义
    • read/write
      函数本身的调用只涉及一次sys-call,但是受限于其接口使用方式(按行),在使用时通常我们会结合seek函数一起使用,相当于每次操作是2次sys-call
      本组接口针对的是设备文件,不仅仅是普通文件。
    • pread/pwrite
      函数本身的调用只涉及一次sys-call,但它与read/write不同,本组接口在使用时不需要我们主动去调用seek函数,它自身实际上是一个原子的操作,该原子操作会同时调用lseek接口,整体依然是一次sys-call
      它采用基于文件首地址的偏移量的方式读写文件,不用不停的seek移动文件的指针,方便多线程同时操作,避免调用方通过上锁控制并发,从而影响性能。
      本组接口仅适用于真实文件。
  • 用户态:
    下面的IO读写接口由标准C定义
    • fread/fwrite
      fread

      用户应用(App)在调用fread时,直接读取的是clib的缓存(cache),fread首先会从IPC中将数据通过sys-call读取到cache中(数据量大小未知),而后将cache中符合fread所需大小的数据返回给App。

      用户应用(App)在调用fwrite时,直接写入的是clib的缓存(cache),fwrite首先会将数据写入到cache中,当cache写满(数据量大小未知),而后会将cache中数据sync到IPC上,也可以通过主动调用fsync来达到sync的目的。

      可以看到,App->cache之间是一个MemCopy,cache->IPC之间是一个sys-call

选择

通常,在read/writepread/pwrite两组IO接口中,倾向于选择pread/pwrite,因为其方便和并发的安全性。
pread/pwritefread/fwrite两组IO接口中,选择就不太很明确,需要根据实际情况进行选择,下图会进行简单的描述,说明选择方式:
pread-1

  • 场景一
    现做如下假设:文件File大小为8K,APP允许开辟的Buffer大小为2K,clib会自主开辟的cache空间为8K;
    如果走左侧路线,APP需要调用4次pread接口才能读完这个8K的文件,也就是4次的sys-call;
    如果走右侧路线,APP需要调用4次fread接口能读完这个8K的文件,但是clib会一次读取8K至cache,需要一次的sys-call,和4次MemCopy
    对于这种小数据量,MemCopy的损耗远低于sys-call,所以调用fread性能会高于pread

  • 场景二
    现做如下假设:文件File大小为4000M,APP允许开辟的Buffer大小为400M,clib会自主开辟的cache空间为8K;
    如果走左侧路线,APP需要调用10次pread接口才能读完这个4000M的文件,也就是10次的sys-call;
    如果走右侧路线,APP需要调用10次fread接口能读完这个4000M的文件,但是clib会一次读取8K至cache,会需要很多次的sys-call(不确定,还需继续研究其缓存策略),和许多次大块内存的MemCopy
    对于这种大数据量,可能会产生多次大块内存空间的MemCopy操作,此时MemCopy反而比sys-call的性能更差,所以调用pread性能会高于fread

    由以上两种假设的场景可以看出来,在实际使用时,还是要根据实际情况综合考虑的,可以得到一个粗糙的结论:对于每次读写字节数较大的操作,内存拷贝比系统调用更消耗性能,可以使用pread来减少拷贝;对于每次读写字节数较少的操作,系统调用比内存拷贝更消耗性能,使用fread来减少系统调用次数。