用IO完成端口提高读写速度的探讨试验

来源:互联网 发布:跑跑卡丁车有mac版吗 编辑:程序博客网 时间:2024/05/21 20:28

问题提出:

我在写硬盘时通常采用简单的同步IO技术,这可以解决多数工作中的问题,所以并没有注意使用异步模式。后来对一个盘阵进行测速时,发现用我们自研的测速工具的测试结果达不到从高速数据源接收记录数据的要求,而对其用Iometer测出的速度则很理想,可以达到项目要求。据我所知IO完成端口适用于小数据量快速并行处理的场合,比如网络服务器。对于大数据量(大于4G)的应用未知。于是决定用IO完成端口设计一个初步具备可靠性的原理程序,尽量提高数据读、写速度。

设计基本要求

易测试。设计多线程程序令人头疼的原因之一是线程之间无序切换,故障难复现

可靠。不成功的IO操作可以重新尝试,允许按照某种原则放弃IO操作。

⑶有效。 我的期望是在多磁盘系统盘阵里采用IO完成端口能充分发挥系统的全部能力,显著提高读写磁盘的性能,在单盘上的速度也不应该小于同步IO的速度。

 

如何满足要求:

对于⑴,随机生成数据写成单独一个文件不是好主意,同时异步写成2份文件,事后再比对是可以的。甚至1份用同步写,另1份用IO完成端口也不错。在日常中,我们肯定是从一个高速数据源接收数据,然后快速写到磁盘上,因此我计划从现有的磁盘文件读数据,然后异步写到磁盘上。我想这样做可以便于在工程中重用这个原理程序。

对于⑵,我现在能想到的是重试失败的IRP,在达到trynum次重试失败后放弃操作。但是失败的读操作不能影响写操作,可以把全0数据或者错误信息写到文件中(在原理程序中只须做到丢弃它即可);失败的写操作的处理方式是简单地丢弃这个包。

对于⑶,虽然它是最基本的要求,但只能在完成程序的基础上予以验证。

 

Ⅲ 基本数据结构抽象:

IRP。异步IO中要用到一个OVERLAPPED结构,我在设计时脑中挥之不去的一个念头就是那个驱动中IRP,于是把交给ReadFileWriteFileOVERLAPPED用一个叫做Irp的结构略加包装。

Pending(待决的)读操作管理——读队列。对于异步IO中大量并发甚至并行的IRP,笼统地讲,在资源紧张时会有一些IRPtrynum次数超限。为了统计那些IRPtrynum次数,应该有一个单独的共享的pending读队列统计这个信息。

所有发起的读IRP都被放入Pending读队列,check in

IO完成端口获得成功的通知后,从读队列中摘除,check out

如果可以重试,将读队列中该IRPtrynum1,重新发起读IRP

如果彻底失败,放弃IO操作,check out

 

Pending写操作管理——写队列。与读队列的行为相似。

用上述所谓的读写队列管理并发的FILE_FLAG_NO_BUFFERING的读写IRP,由于IO的异步性,首先先入队check in的操作并不一定首先出队check out;所以这里所谓的读、写队列,从行为上看其实是链表。这个设计还蛮简单的。

公用缓冲区管理CmmBufManager。读写、操作之间的数据交互需要一个公用缓冲区进行互斥访问。这是个很简单的操作系统中的生产者消费者问题。略有不同的是这里的生产者和消费者的生产和消费都有可能失败。所以借鉴windows虚存分配思想,我想可以在生产者生产前,先在CmmBuf中为IRP预留空间(Reserve),生产成功再提交(Commit)给消费者。生产失败要撤销空间的预留(DeReserve)。消费者请求一个提交的产品(GetCommi),消费后撤销空间的预留(DeReserve)。

 

Ⅳ 数据结构细化:

ReadQueue: Init(), Destroy(), EnQueue(Irp), SearchQueue(Irp), SearchAndDel(Irp).

WriteQueue: 同上。

CmmBufManager: Init(), Destroy(), Reserve(Irp), Commit(IRP), GetComit(IRP), DeReserve(IRP).

Ⅴ 流程控制:

 

其中需要说明的是所有对ReadIrp QueueWriteIrp QueueCmmBuf的操作都是互斥的。只有++trynum这个对Irp的操作我偷了懒没有互斥。因为没有证据表明IO完成端口会对一个IO操作发出多个通知从而导致访问冲突。文件的读写都附加了FILE_FLAG_NO_BUFFERING标志。

 

设计工作已经做完,剩下的是机械的编码工作。唯一值得一提的是ReadFile, WriteFile中的OVERLAPPED结构是需要自己在安全的空间中分配的,应该保持长久的生命,比如在堆空间中,而绝不是临时空间,否则会发现程序“跑飞了”。我是在Read PumpWrite Pump中分配的堆空间,在监听IO完成端口的线程中对空间进行维护。

异步操作有三种返回结果:1、操作成功。于是我可以安心地释放内存。2、失败,但是我的OVERLAPPED结构还健在,于是我可以选择重新IO或者放弃操作,释放这块内存。3、失败,并且我分配的OVERLAPPED结构找不到了!(微软啊微软,你咋能这样呢)。于是一定有一个内存泄漏,并且我的IO队列中会有一项再也得不到check out,这个项会一直占用队列空间。不过还好我在测试过程中没发现这种现象。

测试参数配置

       测试完毕后,将Read Pump的读文件改成生成随机数据,把监听完成端口的线程数目设置成2*cpu_num+1

       写盘的数据量为每包8MB,队列长度16

测试结果

公司使用的盘阵已经交付,没有机会实践。这个问题在这个项目中如何解决的呢?很简单,增大其物理缓冲,直接用同步IO可以达到项目要求(当然在这情况下Iometer的效率更高)。并且有多个系统要分别向盘阵中读写数据,我们很难要求让大家这么笨拙地完成一个简单的写盘工作。

       从原理上讲,我一点也不怀疑程序的所谓scalabe弹性,一定能在盘阵系统中满足⑶。但是不得不承认,在我自己的PC中的测试结果并不乐观。

在小数据量(小于2G)写盘测试中,异步IO与同步IO的速度不相上下。在大数据量(大于4G)写入时,异步IO速度稳定地超过同步IO的速度,大约高2-3 MB/S,这个结果差异并不显著。

异步IO的速度跟设定的每次操作的数据量有极大关系,操作的数据太小速度会很低,并且引起过IO失败,每次设定的数据量又与磁盘的IO缓冲大小相关。

队列的长短也有一定影响。

总之,异步操作使用上的这两个参数对操作效率的影响较大,参数调节不当会明显降低IO速度,在单盘主机上带来的性能提升与带来的使用上的麻烦相比极其有限。