好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

异步IO一定更好吗?

异步IO一定更好吗?

http://cnodejs.org/blog/?p=1015

异步IO一定更好吗?

在长林的文章《nodejs异步IO的实现》中提到,NodeJS通过libeio来实现IO操作的异步化,而libeio采用多线程的方式来模拟异步操作。

这里我需要强调一个观点,异步IO虽然是NodeJS一个非常重要的特点,但 异步IO并不总是最好的 ,其他语言也一样。我们来看这么一个例子:

在我的磁盘上有2个文件,我希望在一个程序里读取这2个文件,每次输出一个字符。请注意,我不要求一定要两个文件被交替着读取,也不要求一定要先读完一个再读另一个。

怎么样,貌似还没说到问题的实质。那么我们再分几种情况:

“磁盘”是指我的机器上只有一块机械式硬盘,或者有多个硬盘,但被读取的两个文件位于同一块硬盘上; 被读取的文件位于不同的两个机械式硬盘上; 被读取的两个文件位于同一块固态硬盘(SSD)上。

如果你熟悉机械式硬盘的原理,我想已经明白我要说什么了。否则,我们继续往下看。我们知道,传统的机械式硬盘要读取某一个文件,首先要将磁头移动到某个位置(fseek)。这个过程是一个物理运动,所花的时间跟磁盘转速、磁头移动的距离有关。读取一个字节后,磁头顺便移动到紧挨着的下一个字节。我写了一段通过异步方式读取文件的测试代码如下:

var fs  = require('fs');

function test_stream_read(file, id) {
        var stream      = fs.createReadStream(file, {
                flags: 'r',
                encoding: 'utf8',
                mode: 0666,
                bufferSize: 1
        });

        var tag = "[ " + id + " ] ";
        stream.on('data', function(chunk) {
                console.log(tag + "data: " + chunk.trim());
        });

        console.log(tag + "start");
}

test_stream_read('/disk1/a.txt', 1);
test_stream_read('/disk2/b.txt', 2);

在上面的代码中,我用NodeJS的ReadStream来读取文件,设定的bufferSize为1,即每次读取一个字符。由于NodeJS异步IO的多线程方式,供测试的两个文件在程序逻辑上可以被并行读取。所以影响性能的关键就落在存储介质(磁盘)能否并行处理上了。

在第一个case中,由于两个文件位于同一块机械式磁盘上,一块磁盘的磁头只有一个,所以只能是顺序的。你会发现,这种情况下磁头在盘面上移来移去,大量时间花在物理运动上,性能不差才怪呢!在这个case里,你还不如让NodeJS阻塞着读文件呢!

而第二个case中,两个文件位于两块磁盘上,它们之间能互相独立的被操作,实际上落在单个磁盘上的读请求仍然是顺序读取,性能会较好。

第三个case你自己分析吧,我不多讲了。

总结一下:

哪种方式读文件,要根据你的硬件和应用特点来权衡,切忌盲从。我认为要考虑的几点包括:硬盘原理、多少个硬盘、文件放在几个文件上、一次读取多少个文件、文件的平均大小、读取的缓冲区大小。 NodeJS的fs.read方法实际上用的也是ReadStream,只不过bufferSize被写死了,为64K。如果你的文件大小都小于64K,那么你可以简单地忽略上面啰嗦的一大堆文字了。但是,这个原理你得清楚。

关于这个话题,我希望有更多的讨论和测试代码。欢迎拍砖给我。

续:异步IO一定更好吗?

我之前的一篇文章《 异步IO一定更好吗? 》中举了一个很变态的例子,用以说明在单碟机械式硬盘上异步IO反而可能降低性能的问题,大家的讨论很热烈。前天的NodeParty杭州站分享会上,来自上海的@老赵 随口带出了这个问题,大概说是多个异步IO请求会使得性能提高,因为磁头走到哪里就处理到哪里,很多请求在“路上”就可以被处理掉了。

乍一看,我和老赵的结论是相反的。所以从nodeparty回来后查了查资料,并且做了一些实验,发现这两种情况在各自特定的场景里都可能是正确的,在别的场景又都可能是错误的。为什么呢?

我之前的结论没有认真考虑操作系统对IO调度的优化,对此一知半解就拿出来大放厥词,实在不应该!

Linux2.6内核对IO调度提供了四种算法:Completely Fair Queuing(CFQ)、Deadline、NOOP和Anticipatory(as),详细情况请点击 这里 阅读。在2.6内核中,系统默认的IO调度算法是CFQ,也就是OS会对请求的偏移量首先进行排序,按照这个排序顺序进行磁头的移动和请求响应,这正是老赵的观点的出处。而假如某个磁盘采用了NOOP的IO调度算法,也就是说内核不会对IO调度做任何优化,容易理解,正是我在《异步IO一定更好吗?》一问中的观点。除此之外,用户还可以为不同磁盘指定不同的IO调度算法。查看某个磁盘的IO调度算法的命令如下:

$[pengchun]$ cat /sys/block/sda/queue/scheduler
noop anticipatory deadline [cfq]

原文这里的测试方法不对,已删掉

总结我的几点经验:

1. 异步IO不一定是性能最好的;

2. 操作系统对磁盘IO的各种调度算法中,没有哪一个是绝对更好的;随着应用场景的不同,很可能某一个算法的性能远远好于其他几个;而在另外的场景中则相反。例如,某个磁盘专门用来写日志的,那么你用NOOP就最好了;而如果要作为数据库的磁盘来使用,deadline可能是最好的算法了;

3. 尽管操作系统会做这样那样的优化,但不要懒惰地依赖操作系统;对机械式磁盘的访问,尽可能把随机访问变成顺序访问。

Related posts:

异步IO一定更好吗?

iReader

查看更多关于异步IO一定更好吗?的详细内容...

  阅读:34次