zoukankan      html  css  js  c++  java
  • 浅析nodejs的buffer类(转)

    最近翻阅了node v0.10.4buffer类的源代码,收获不少,也很久没有在cnode上发表文章了,想把一些收获分享给大家,有什么错误的地方希望大牛们指正啊。

    前阵子有位rrestjs框架的使用者YanQ报告给我这样一个错误,跟我说在用户post很多内容的文章时会crash进程然后报如下错误:(热心的老雷帮我解决了问题)

    buffer.js:523

    throw new RangeError('targetStart out of bounds');

    错误的原因是apiClass Method: Buffer.concat(list, [totalLength])的第二个参数 totalLength list中所存储的所有buffer.length的最大小,而不是list的长度,这边大家需要注意下啊。

    言归正传,简单总结一下吧: 1、什么时候该用buffer,什么时候不该用 我看一下如下的测试代码,分别是拼接各种不同长度的字符串,最后直接拼接了10MB的字符串

    var string,string2,string3;

    var bufstr,bufstr2,bufstr3;

    var j;

     

    console.time('write 100 string')

    for(j=0;j<1000;j++){

        var x = j+'';

        string += x;

    }

    console.timeEnd('write 100 string')

     

    console.time('write 100 buffer')

    bufstr = new Buffer(100)

    for(j=0;j<1000;j++){

        var x = j+'';

        bufstr.write(x,j);

    }

    console.timeEnd('write 100 buffer')

     

     

    console.time('write 100000 string')

    for(j=0;j<100000;j++){

        var x = j+'';

        string2 += x;

    }

    console.timeEnd('write 100000 string')

     

    console.time('write 100000 buffer')

    bufstr2 = new Buffer(100000)

    for(j=0;j<100000;j++){

        var x = j+'';

        bufstr2.write(x,j);

    }

    console.timeEnd('write 100000 buffer')

     

    console.time('write 1024*1024*10 string')

    for(j=0;j<1024*1024*10;j++){

        var x = j+'';

        string3 += x;

    }

    console.timeEnd('write 1024*1024*10 string')

     

    console.time('write 1024*1024*10 buffer')

    bufstr3 = new Buffer(1024*1024*10)

    for(j=0;j<1024*1024*10;j++){

        var x = j+'';

        bufstr3.write(x,j);

    }

    console.timeEnd('write 1024*1024*10 buffer')

    接着是输出结果:

    write 100 string: 0ms

    write 100 buffer: 6ms

    write 100000 string: 37ms

    write 100000 buffer: 150ms

    write 1024*1024*10 string: 4262ms

    write 1024*1024*10 buffer: 8904ms

    读取速度都不需要测试了,肯定string更快,buffer还需要toString()的操作。 所以我们在保存字符串的时候,该用string还是要用string,就算大字符串拼接string的速度也不会比buffer慢。 那什么时候我们又需要用buffer呢?没办法的时候,当我们保存非utf-8字符串,2进制等等其他格式的时候,我们就必须得使用了。

    2buffer不得不提的8KB

    buffer著名的8KB载体,举个例子好比,node把一幢大房子分成很多小房间,每个房间能容纳8个人,为了保证房间的充分使用,只有当一个房间塞满8个人后才会去开新的房间,但是当一次性有多个人来入住,node会保证要把这些人放到一个房间中,比如当前房间A4个人住,但是一下子来了5个人,所以node不得不新开一间房间B,把这5个人安顿下来,此时又来了4个人,发现5个人的B房间也容纳不下了,只能再开一间房间C了,这样所有人都安顿下来了。但是之前的两间房AB都各自浪费了4个和3个位置,而房间C就成为了当前的房间。

    具体点说就是当我们实例化一个新的Buffer类,会根据实例化时的大小去申请内存空间,如果需要的空间小于8KB,则会多一次判定,判定当前的8KB载体剩余容量是否够新的buffer实例,如果够用,则将新的buffer实例保存在当前的8KB载体中,并且更新剩余的空间。

    我们做个简单的实验,模拟一个比较严重的内存泄露情况:

    第一次我们将内存泄漏点那行代码注释掉,运行4分钟后,得到如下打印信息,V8已经自动把我分配的内存释放掉了,free men又回到了开始的数值,第二次我们将泄漏点那行代码放开,让全局变量 leak_buf_ary 始终引用着buffer,同样执行10分钟

    var os = require('os');

    var leak_buf_ary = [];

    var show_memory_usage = function(){ //打印系统空闲内存

        console.log('free mem : ' + Math.ceil(os.freemem()/(1024*1024)) + 'mb');

    }

     

    var do_buf_leak = function(){

        var leak_char = 'l'; //泄露的几byte字符

        var loop = 100000;//10万次

        var buf1_ary = []

        while(loop--){

            buf1_ary.push(new Buffer(4096)); //申请buf1,占用4096byte空间,会得到自动释放

     

            //申请buf2,占用几byte空间,将其引用保存在外部数据,不会自动释放

            //*******

            leak_buf_ary.push(new Buffer(loop+leak_char));

            //*******

        }

        console.log("before gc")

        show_memory_usage();

        buf1_ary = null;

        return;

    }

     

     

    console.log("process start")

    show_memory_usage()

     

    do_buf_leak();

     

    var j =10000;

    setInterval(function(){

        console.log("after gc")

        show_memory_usage()

    },1000*60)

    第一次结果:

    process start

    free mem : 5362mb

    before gc

    free mem : 5141mb

    after gc

    free mem : 5163mb

    after gc

    free mem : 5151mb

    after gc

    free mem : 5148mb

    after gc

    free mem : 5556mb

    第二次结果:

    process start

    free mem : 5692mb

    before gc

    free mem : 4882mb

    after gc

    free mem : 4848mb

    after gc

    free mem : 4842mb

    after gc

    free mem : 4843mb

    after gc

    free mem : 4816mb

    after gc

    free mem : 4822mb

    after gc

    free mem : 4816mb

    after gc

    free mem : 4809mb

    after gc

    free mem : 4810mb

    after gc

    free mem : 4831mb

    after gc

    free mem : 4830mb

    虽然我们释放了4096bytebuffer,但是由于那几byte的字节没有释放掉,将会造成整个8KB的内存都无法释放,如果继续执行循环最终我们的系统内存将耗尽,程序将crash。同样由于我们是依次循环分配 4096+ byte内存的,所以每块8KB的内存空间都将浪费409Xbyte,在执行循环之后,我们明显发现第二次的内存占用比第一次要大很多。这里我们将近多出了300MB左右的内存消耗。

    3buffer字符串的连接 我们接受post数据时,node是以流的形式发送上来的,会触发ondata事件,所以我们见到很多代码是这样写的:

    var http = require('http');

    http.createServer(function (req, res) {

     

    var body = '';

    req.on('data',function(chunk){

        //console.log(Buffer.isBuffer(chunk))

        body +=chunk

    })

    req.on('end',function(){

         console.log(body)

         res.writeHead(200, {'Content-Type': 'text/plain'});

    res.end('Hello World ');

    })

     

     

    }).listen(8124);

     

    console.log('Server running at http://127.0.0.1:8124/');

    下面我们比较一下两者的性能区别,测试代码:

    var buf = new Buffer('nodejsv0.10.4&nodejsv0.10.4&nodejsv0.10.4&nodejsv0.10.4&');

    console.time('string += buf')

    var s = '';

    for(var i=0;i<10000;i++){

        s += buf;

    }

    s;

    console.timeEnd('string += buf')

     

     

    console.time('buf concat')

    var list = [];

    var len=0;

    for(var i=0;i<10000;i++){

        list.push(buf);

        len += buf.length;

    }

    var s2 = Buffer.concat(list, len).toString();

    console.timeEnd('buf concat')

    输出结果,相差近一倍:

    string += buf: 15ms

    buf concat: 8ms

    1000次拼接过程中,两者的性能几乎相差一倍,而且当客户上传的是非UTF8的字符串时,直接+=还容易出现错误。

    4、独享的空间 如果你想创建一个独享的空间,独立的对这块内存空间进行读写,有两种办法,1是实例化一个超过8KB长度的buffer,另外一个就是使用slowbuffer类。

    5buffer的释放 很遗憾,我们无法手动对buffer实例进行GC,只能依靠V8来进行,我们唯一能做的就是解除对buffer实例的引用。

    6、清空buffer 刷掉一块buffer上的数据最快的办法是buffer.fill

    最后如果也想看看buffer源码,希望我的博客对你有帮助: 浅析nodebuffer模块(一创建) 浅析nodebuffer模块(二写入) 浅析nodebuffer模块(三读取)

    转自: http://cnodejs.org/topic/5189ff4f63e9f8a54207f60c

  • 相关阅读:
    C++数据类型与C#对应关系 c#调用WINDWOS API时,非常有用(转)
    Web应用系统中关闭Excel进程
    jquery下一个空格带来的血案
    导出Excel时发生COM组件失败的解决方案
    水晶报表的交叉表中增加超级链接
    JavaScript和ExtJS的继承 Ext.extend Ext.applyIf (转)
    SQL SERVER 2000数据库置疑处理
    PHP中对淘宝URL中ID提取
    树莓派+蓝牙适配器连接蓝牙设备
    树莓派摄像头模块转成H264编码通过RTMP实现Html输出
  • 原文地址:https://www.cnblogs.com/time-is-life/p/5385855.html
Copyright © 2011-2022 走看看