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万,就变成龟速?
这个问题涉及到 Node.js 中 fs.createWriteStream
的工作原理以及操作系统和硬件的限制。当使用 fs.createWriteStream
写入文件时,Node.js 会在内部使用一个缓冲区来管理数据的写入。当缓冲区满时,数据会被推送到操作系统中去写入磁盘。如果写入速度跟不上数据产生的速度,那么就会出现性能瓶颈。
原因分析
-
缓冲区大小:Node.js 默认的缓冲区大小有限制。当缓冲区满时,它会阻塞直到操作系统处理完这些数据。这会导致写入操作变得缓慢。
-
磁盘 I/O 限制:磁盘的写入速度也是有限的,特别是对于机械硬盘(HDD)。当大量数据需要写入时,磁盘可能无法及时处理这些数据,导致性能下降。
-
系统资源限制:操作系统对文件操作有一定的资源限制,如文件描述符的数量等。当写入量过大时,可能会触发这些限制,从而影响性能。
示例代码
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();
解决方案
-
调整缓冲区大小:可以通过设置
highWaterMark
参数来调整缓冲区的大小。例如,将缓冲区大小设为 64KB:const out = fs.createWriteStream(path.join(__dirname, 'test1.txt'), { highWaterMark: 64 * 1024 });
-
异步写入:确保在所有数据写入完毕后再关闭写入流。可以监听
finish
事件来确保所有数据被正确写入。 -
优化磁盘 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');
通过这种方式,你可以更有效地管理写入操作,避免因缓冲区溢出而导致的性能下降。