Node.js的write性能为何急剧下降?

Node.js的write性能为何急剧下降?

偶然发现一个现象:通过for循环向文件写入数据,性能表现不一 代码如下: var fs=require(‘fs’); var out=fs.createWriteStream(’./test1.txt’); console.time(‘forNode’); for(var i=1;i<=200000;i++){ var flag=out.write(‘9999’); //console.log(flag); } console.timeEnd(‘forNode’);

问题如下: 当改变 i 的值分别是1万、5万、10万、15万、20万时,其所谓的时间测试基本线性增长,但问题来了: test1.txt的实际写入完成时间却在超过10万之后,急剧下降,几乎是龟速。(每次都是新建空文件之后进行测试) 例如, 在i=10万时,实际写入时间大约不到20秒(口算),体积为391KB。 在i=20万时, 实际写入时间大约为N秒 (实在记不下去了,口算30秒后才15KB,最后体积大约是700多KB。测了多次,都是这样。手动不断刷新,会发现test1.txt在1K、1K的增长。

为什么在i在1万、5万、10万,写入时间基本都线性增长,为何到了20万,就变成龟速?


7 回复

这个问题涉及到 Node.js 中 fs.createWriteStream 的工作原理以及操作系统和硬件的限制。当使用 fs.createWriteStream 写入文件时,Node.js 会在内部使用一个缓冲区来管理数据的写入。当缓冲区满时,数据会被推送到操作系统中去写入磁盘。如果写入速度跟不上数据产生的速度,那么就会出现性能瓶颈。

原因分析

  1. 缓冲区大小:Node.js 默认的缓冲区大小有限制。当缓冲区满时,它会阻塞直到操作系统处理完这些数据。这会导致写入操作变得缓慢。

  2. 磁盘 I/O 限制:磁盘的写入速度也是有限的,特别是对于机械硬盘(HDD)。当大量数据需要写入时,磁盘可能无法及时处理这些数据,导致性能下降。

  3. 系统资源限制:操作系统对文件操作有一定的资源限制,如文件描述符的数量等。当写入量过大时,可能会触发这些限制,从而影响性能。

示例代码

const fs = require('fs');
const path = require('path');

// 创建写入流
const out = fs.createWriteStream(path.join(__dirname, 'test1.txt'), { highWaterMark: 64 * 1024 }); // 设置高水位标记为64KB

console.time('forNode');

for (let i = 1; i <= 200000; i++) {
    const flag = out.write('9999');
    // console.log(flag);
}

// 当所有数据都被写入后关闭写入流
out.on('finish', () => {
    console.timeEnd('forNode');
});

out.end();

解决方案

  1. 调整缓冲区大小:可以通过设置 highWaterMark 参数来调整缓冲区的大小。例如,将缓冲区大小设为 64KB:

    const out = fs.createWriteStream(path.join(__dirname, 'test1.txt'), { highWaterMark: 64 * 1024 });
    
  2. 异步写入:确保在所有数据写入完毕后再关闭写入流。可以监听 finish 事件来确保所有数据被正确写入。

  3. 优化磁盘 I/O:使用更快的存储设备,如固态硬盘(SSD)。

通过上述方法,可以显著提高写入性能,并避免在大量数据写入时出现性能急剧下降的情况。


额 你这是在测v8的性能 跟nodejs关系不大 而且你测试的时间也不准确 你可以试着把write操作换成其他试试

var fs = require(‘fs’); 
var out = fs.createWriteStream(‘./test1.txt’);
var start = Date.now();
(function write(i) {
    if (i >=0 && i < 200000) {
        out.write('9999', function () {
            write(i+1);
        });
    } else {
        console.log('time : %dms', Date.now() - start);
    }
}(0));

time : 15413ms

我没记错的话,javascript的字符串是16位,16ב9999’=64位=8字节。 连续20万次操作,相当于让缓冲区排队20万×8字节,约1.6M内存了。 最好是用串行写入。这样排队,缓冲区最大只有8字节。

多谢二位。 其实我不在测试性能。只是发现了这个现象,在10万时,十多秒文件的写入就完成了(判断标志是程序返回,物理文件体积不再变化,而不是console打印的时间)。 但在20万时,就不是多少秒了,而是很长很长时间,同时物理文件的体积从一开始就几乎每秒才增加1K,不知道为什么!

你改用并行写就行了。哦,node没法并行⊙▽⊙

调用C或C++会怎样?试试。。。

Node.js 中 fs.createWriteStream 使用的是非阻塞 I/O 操作,这意味着它不会一直阻塞主线程来等待 I/O 完成。当你的程序写入数据的速度超过磁盘的实际写入速度时,就会出现缓存堆积,导致性能下降。

具体来说,在 for 循环中调用 out.write('9999') 时,如果写入操作不能立即完成,它会返回 false 并将剩余的数据放入内部缓冲区。只有当缓冲区中有可用空间时,才会继续写入。如果缓冲区满了,write 方法会阻塞,直到有足够的空间为止。

因此,当你增加循环次数时,内部缓冲区的大小也会相应增加。当超过一定阈值时,操作系统可能需要频繁地将数据写入磁盘,这会导致性能急剧下降。

你可以通过监听 'drain' 事件来解决这个问题,确保在缓冲区为空时再继续写入数据:

var fs = require('fs');
var out = fs.createWriteStream('./test1.txt');

console.time('forNode');
for (var i = 1; i <= 200000; i++) {
    var flag = out.write('9999');
    if (!flag) {
        out.once('drain', function() {
            // 当缓冲区为空时,继续写入数据
            console.log(`Buffer drained at iteration ${i}`);
            // 这里可以继续写入更多数据
        });
    }
}
console.timeEnd('forNode');

通过这种方式,你可以更有效地管理写入操作,避免因缓冲区溢出而导致的性能下降。

回到顶部