分类: c/c
2013-08-22 16:34:57
其次,先说一下pwrite和write, 它们都属于文件io,数据流是从“进程=>fd=>文件”,都是直接调用系统调用的函数。两者不同的是,pwrite相当于顺序调用 lseek 和 write , 然后调用pread时,无法中断其定位和读操作,也就是说lseek和write相当于是原子操作;另外一点是pwrite不会更新文件的指针。
在多线程io操作中,对io的操作尽量使用pread和pwrite,否则,如果使用seek write/read的方式的话,就需要在操作时加锁。这种加锁会直接造成多线程对同一个文件的操作在应用层就串行了。从而,多线程带来的好处就被消除了。
使 用pread方式,多线程也比单线程要快很多,可见pread系统调用并没有因为同一个文件描述符而相互阻塞。pread和pwrite系统调用在底层实 现中是如何做到相同的文件描述符而彼此之间不影响的?多线程比单线程的iops增高的主要因素在于调度算法。多线程做pread时相互未严重竞争是次要因 素。
内 核在执行pread的系统调用时并没有使用inode的信号量,避免了一个线程读文件时阻塞了其他线程;但是pwrite的系统调用会使用inode的信 号量,多个线程会在inode信号量处产生竞争。pwrite仅将数据写入cache就返回,时间非常短,所以竞争不会很强烈。
在使用pread/pwrite的前提下,如果各个读写线程使用各自的一套文件描述符,是否还能进一步提升io性能?
每 个文件描述符对应内核中一个叫file的对象,而每个文件对应一个叫inode的对象。假设某个进程两次打开同一个文件,得到了两个文件描述符,那么在内 核中对应的是两个file对象,但只有一个inode对象。文件的读写操作最终由inode对象完成。所以,如果读写线程打开同一个文件的话,即使采用各 自独占的文件描述符,但最终都会作用到同一个inode对象上。因此不会提升io性能。
最后,说一下pwrite/fwrite。 虽然他们的功能都是将内存中的数据存入文件。但原理和过程都有所不同。刚刚说过pwrite是属于文件io,数据流是从“进程=>fd=>文 件”,而fwrite是流/标准io,其数据流是从“进程=>fp(file对象)=>流/缓冲=>文件”;原本直接对文件的操作,在 fwrite库函数中变为对流对象的操作,而“流=>文件”这一层的操作将由库函数为我们完成。流的逻辑表示就是file对象,而流的实体就是流使 用的缓冲区,这些缓冲区相对于应用进程来说就是文件的代表。
全 随机写无疑是最慢的写入方式,在logic dump测试中很惊讶的发现,将200m的内存数据随机的写入到100g的磁盘数据里面,竟然要2个小时之多。原因就是虽然只有200m的数据,但实际上 却是200万次随机写,根据测试,在2850机器上,这样完全的随机写,r/s 大约在150~350之间,在180机器上,r/s难以达到250,这样计算,难怪需要2~3个小时之久。
如 何改进这种单线程随机写慢的问题呢。一种方法就是尽量将完全随机写变成有序的跳跃随机写。实现方式,可以是简单的在内存中缓存一段时间,然后排序,使得在 写盘的时候,不是完全随机的,而是使得磁盘磁头的移动只向一个方向。根据测试,再一次让我震惊,简单的先在内存中排序,竟然直接使得写盘时间缩短到 1645秒,磁盘的r/s也因此提升到1000以上。写盘的速度,一下子提高了5倍。
一 个需要注意的地方,这种跳跃写对性能的提升,来至与磁头的单方向移动,它非常容易受其他因素的影响。测试中,上面提到的测试是只写block文件,但如果 在每个tid的处理中再增加一个写index的小文件。虽然如果只写index小文件,所用时间几乎可以忽略,但如果夹杂在写block文件中间的话,对 整体的写性能可能影响巨大,因为他可能使得磁盘的磁头需要这两个地方来回跑。根据测试,如果只写index文件,只需要300s就可以写完所有200万个 tid,单如果将写索引和写block放在一起,总时间就远大于分别写这两部分的时间的和。针对这种情况,一种凯发app官方网站的解决方案就是就不要将小数据量的数据实时的 刷盘,使用应用层的cache来缓存小数据量的index,这样就可以消除对写block文件的影响。
从 原理上解释上面的表象,一般来说,硬盘读取数据的过程是这样的,首先是将磁头移动到磁盘上数据所在的区域,然后才能进行读取工作。磁头移动的过程又可以分 解为两个步骤,其一是移动磁头到指定的磁道,也就是寻道,这是一个在磁盘盘片径向上移动的步骤,花费的时间被称为“寻道时间”;其二就是旋转盘片到相应扇 区,花费的时间被称为“潜伏时间”(也被称为延迟)。那么也就是说在硬盘上读取数据之前,做准备工作上需要花的时间主要就是“寻道时间”和“潜伏时间”的 总和。真正的数据读取时间,是由读取数据大小和磁盘密度、磁盘转速决定的固定值,在应用层没有办法改变,但应用层缺可以通过改变对磁盘的访问模式来减少 “寻道时间”和“潜伏时间”, 我们上面提到的在应用层使用cache然后排序的方式,无疑就是缩短了磁盘的寻址时间。由于磁头是物理设备,也很容易理解,为什么中间插入对其他小文件的 读写会导致速度变慢很多。
建议:尽量避免完全的随机写,在 不能使用多线处理的时候,尽量使用应用层cache,确保写盘时尽量有顺序性。对于小数据量的其他文件,可以一直保存在应用层cache里面,避免对其他大数据量的数据写入产生影响。
多线程随机读的处理速度可以达到单线程随机读的10倍以上,但同上也带来了响应时间的增大。测试结论如下:(每个线程尽量读)
读线程数 | 读出100次耗时(um) | 读平均相应时间(um) |
1 | 1329574 | 13291 |
5 | 251765 | 12976 |
10 | 149206 | 15987 |
20 | 126755 | 25450 |
50 | 96595 | 48351 |
结论标明增加线程数,可以有效的提升程序整体的io处理速度。但同时,也使得每个io请求的响应时间上升很多。
从 底层的实现上解释这个现象:应用层的io请求在内核态会加入到io请求队列里面。内核在处理io请求的时候,并不是简单的先到先处理,而是根据磁盘的特 性,使用某种电梯算法,在处理完一个io请求后,会优先处理最临近的io请求。这样可以有效的减少磁盘的寻道时间,从而提升了系统整体的io处理速度。但 对于每一个io请求来看,由于可能需要在队列里面等待,所以响应时间会有所提升。
响应时间上升,应该主要是由于我们测试的时候采用每个线程都尽量读的方式。在实际的应用中,我们的程序都没有达到这种压力。所以,在io成为瓶颈的程序里面,应该尽量使用多线程并行处理不同的请求。对于线程数的选择,还需要通过性能测试来衡量。
系统通常会在下面三种情况下回写dirty页
本文给大家提供了一份不同存储模式下的性能测试数据,方便大家在今后的程序开发过程中可以利用这份数据选择合适的数据存储模式。同时讲述了关于文件io读写操作以及系统缓存层面的一些问题。
ref: http://blog.163.com/zhaoxin851055@126/blog/static/811292982012112421330845/