Nodejs异步IO一定更好吗?

Nodejs异步IO一定更好吗?

在长林的文章《nodejs异步IO的实现》中提到,NodeJS通过libeio来实现IO操作的异步化,而libeio采用多线程的方式来模拟异步操作。 <br/> <br/>这里我需要强调一个观点,异步IO虽然是NodeJS一个非常重要的特点,但<strong><span style=“color: #ff0000”>异步IO并不总是最好的</span></strong>,其他语言也一样。我们来看这么一个例子: <br/><blockquote>在我的磁盘上有2个文件,我希望在一个程序里读取这2个文件,每次输出一个字符。请注意,我不要求一定要两个文件被交替着读取,也不要求一定要先读完一个再读另一个。</blockquote> <br/>怎么样,貌似还没说到问题的实质。那么我们再分几种情况: <br/><ol> <br/> <li>“磁盘”是指我的机器上只有一块机械式硬盘,或者有多个硬盘,但被读取的两个文件位于同一块硬盘上;</li> <br/> <li>被读取的文件位于不同的两个机械式硬盘上;</li> <br/> <li>被读取的两个文件位于同一块固态硬盘(SSD)上。</li> <br/></ol> <br/>如果你熟悉机械式硬盘的原理,我想已经明白我要说什么了。否则,我们继续往下看。我们知道,传统的机械式硬盘要读取某一个文件,首先要将磁头移动到某个位置(fseek)。这个过程是一个物理运动,所花的时间跟磁盘转速、磁头移动的距离有关。读取一个字节后,磁头顺便移动到紧挨着的下一个字节。我写了一段通过异步方式读取文件的测试代码如下: <br/><pre escaped=“true” lang=“javascript”>var fs = require(‘fs’); <br/> <br/>function test_stream_read(file, id) { <br/> var stream = fs.createReadStream(file, { <br/> flags: ‘r’, <br/> encoding: ‘utf8’, <br/> mode: 0666, <br/> bufferSize: 1 <br/> }); <br/> <br/> var tag = "[ " + id + " ] "; <br/> stream.on(‘data’, function(chunk) { <br/> console.log(tag + "data: " + chunk.trim()); <br/> }); <br/> <br/> console.log(tag + “start”); <br/>} <br/> <br/>test_stream_read(’/disk1/a.txt’, 1); <br/>test_stream_read(’/disk2/b.txt’, 2);</pre> <br/>在上面的代码中,我用NodeJS的ReadStream来读取文件,设定的bufferSize为1,即每次读取一个字符。由于NodeJS异步IO的多线程方式,供测试的两个文件在程序逻辑上可以被并行读取。所以影响性能的关键就落在存储介质(磁盘)能否并行处理上了。 <br/> <br/>在第一个case中,由于两个文件位于同一块机械式磁盘上,一块磁盘的磁头只有一个,所以只能是顺序的。你会发现,这种情况下磁头在盘面上移来移去,大量时间花在物理运动上,性能不差才怪呢!在这个case里,你还不如让NodeJS阻塞着读文件呢! <br/> <br/>而第二个case中,两个文件位于两块磁盘上,它们之间能互相独立的被操作,实际上落在单个磁盘上的读请求仍然是顺序读取,性能会较好。 <br/> <br/>第三个case你自己分析吧,我不多讲了。 <br/> <br/>总结一下: <br/><ol> <br/> <li>哪种方式读文件,要根据你的硬件和应用特点来权衡,切忌盲从。我认为要考虑的几点包括:硬盘原理、多少个硬盘、文件放在几个文件上、一次读取多少个文件、文件的平均大小、读取的缓冲区大小。</li> <br/> <li>NodeJS的fs.read方法实际上用的也是ReadStream,只不过bufferSize被写死了,为64K。如果你的文件大小都小于64K,那么你可以简单地忽略上面啰嗦的一大堆文字了。但是,这个原理你得清楚。</li> <br/></ol> <br/>关于这个话题,我希望有更多的讨论和测试代码。欢迎拍砖给我。


12 回复

Node.js 异步 I/O 是否一定更好?

在 Node.js 中,异步 I/O 是一个非常重要的特性,它使得 Node.js 能够高效地处理大量的并发请求。然而,异步 I/O 并不总是最佳选择,特别是在某些特定场景下。

案例分析

假设你有一个程序需要同时读取两个文件,并且每次输出一个字符。我们将通过几个不同的案例来分析异步 I/O 的性能。

案例 1:两个文件位于同一块机械式硬盘上

在这种情况下,尽管 Node.js 可以异步地读取这两个文件,但由于机械式硬盘只有一个磁头,磁头需要在两个文件之间频繁移动。这会导致大量的时间浪费在磁头移动上,从而降低整体性能。因此,在这种情况下,使用同步 I/O 可能会更好。

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('/disk1/b.txt', 2);

案例 2:两个文件位于不同的机械式硬盘上

在这种情况下,每个文件可以独立地被不同硬盘上的磁头读取。因此,尽管 Node.js 使用异步 I/O,两个文件可以并行读取,从而提高整体性能。

案例 3:两个文件位于同一块固态硬盘(SSD)上

SSD 没有机械式硬盘那样的磁头限制,它可以更高效地并行读取数据。因此,在这种情况下,异步 I/O 的优势仍然存在。

总结

  • 硬件类型:机械式硬盘和 SSD 的性能差异会影响异步 I/O 的效果。
  • 文件位置:文件是否位于同一块硬盘或不同的硬盘也会影响性能。
  • 文件大小:如果文件较小,Node.js 默认的 64KB 缓冲区可能已经足够,此时异步 I/O 的优势更为明显。

总之,选择异步 I/O 还是同步 I/O 应该基于具体的应用场景和硬件条件进行综合考虑。希望这些案例能够帮助你更好地理解何时使用异步 I/O 更合适。


有道理,倒是可以测一下不同情况异步和同步读取多个数据文件的时间,做个比较。简单的处理方法可以在读取多个文件时对路径进行检查归类,然后分类异步读取

考虑磁盘因素确实有道理,但我觉得对io优化更有帮助的是研究下os中io调度的算法和原理,因为os已经根据不同磁盘介质进行了优化,应用程序只要用好os的api就行了。

一文惊醒梦中人,之前我还真没考虑过这个问题,只是都想着让服务器进程同时处理更多的客户端请求。

pengchun 的例子挺特殊的,异步是应对阻塞的方法,有效利用系统CPU资源,并不是说异步一定比同步的效果更好,对于IO这种需要具体问题具体分析啊,此外,还有个问题,就是目前多核架构是主流,而且很长一段时间内都是,那异步架构如何来提高系统整体CPU的利用率而不仅仅是单核。

关于测试代码的一点疑问: <br/> <br/>为什么每次不从disk多读一些数据到buffer中呢,然后让另外一个打印线程去完成打印任务?而不是每次让aio去读一个字节,这样是对aio的一种浪费吧!

为了说明这个问题,故意这样搞的。

呵呵,写出来的程序还是尽量去迎合它的优势,毕竟它不是怎么用都是最好的。

In future versions, Node will be able to fork new processes (using the Web Workers API )

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

一般应用都是读取数据库的,除非有些应用必须是读文件,那是要好好考虑了。

Node.js 的异步 IO 并不是在所有情况下都优于同步 IO。具体来说,在某些场景下,使用同步 IO 可能会更高效。

例如,当两个文件位于同一块机械式硬盘上时,尽管 Node.js 使用异步 IO,磁头在磁盘上频繁移动会导致性能下降。相比之下,同步 IO 可以更好地利用硬件特性,提高效率。

让我们看一个具体的例子:

示例代码

const fs = require('fs');

// 同步读取文件
function readFileSync(file) {
    const content = fs.readFileSync(file, 'utf8');
    console.log(`[Sync] ${file}:`, content);
}

console.time('Sync');
readFileSync('/disk1/a.txt');
readFileSync('/disk1/b.txt');
console.timeEnd('Sync');

// 异步读取文件
function readFileAsync(file, callback) {
    fs.readFile(file, 'utf8', (err, data) => {
        if (err) throw err;
        console.log(`[Async] ${file}:`, data);
    });
}

console.time('Async');
readFileAsync('/disk1/a.txt', () => {});
readFileAsync('/disk1/b.txt', () => {});
console.timeEnd('Async');

在这个例子中,假设 /disk1/a.txt/disk1/b.txt 位于同一块机械式硬盘上。可以看到,同步读取文件时,磁头只需移动一次,效率较高。而异步读取文件时,磁头需要多次移动,导致性能较差。

总结

  • 机械式硬盘:如果文件位于同一块机械式硬盘上,异步 IO 可能不如同步 IO 高效。
  • SSD 硬盘:对于 SSD 硬盘,异步 IO 通常表现更好,因为 SSD 没有磁头移动的限制。
  • 不同硬盘:如果文件分布在不同的硬盘上,异步 IO 通常表现更好。

选择合适的 IO 方式需要根据实际应用场景和硬件特性来决定。

回到顶部