標籤:listen 位元組 執行個體 plain bsp 空閑 二次 運行 建立
1、什麼時候該用buffer,什麼時候不該用 看一下如下的測試代碼,分別是拼接各種不同長度的字串,最後直接拼接了10MB的字串
var string,string2,string3;var bufstr,bufstr2,bufstr3;var j;console.time(‘write 1000 string‘);for(j=0;j<1000;j++){var x = j+‘‘;string += x;}console.timeEnd(‘write 1000 string‘);console.time(‘write 1000 buffer‘);bufstr = new Buffer(1000);for(j=0;j<1000;j++){var x = j+‘‘;bufstr.write(x,j);}console.timeEnd(‘write 1000 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‘);
接著是輸出結果:
讀取速度都不需要測試了,肯定string更快,buffer還需要toString()的操作。 所以我們在儲存字串的時候,該用string還是要用string,就算大字串拼接string的速度也不會比buffer慢。 那什麼時候我們又需要用buffer呢?沒辦法的時候,當我們儲存非utf-8字串,2進位等等其他格式的時候,我們就必須得使用了。
2、buffer不得不提的8KB
buffer著名的8KB載體,舉個例子好比,node把一幢大房子分成很多小房間,每個房間能容納8個人,為了保證房間的充分使用,只有當一個房間塞滿8個人後才會去開新的房間,但是當一次性有多個人來入住,node會保證要把這些人放到一個房間中,比如當前房間A有4個人住,但是一下子來了5個人,所以node不得不新開一間房間B,把這5個人安頓下來,此時又來了4個人,發現5個人的B房間也容納不下了,只能再開一間房間C了,這樣所有人都安頓下來了。但是之前的兩間房A和B都各自浪費了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 startfree mem : 5362mbbefore gcfree mem : 5141mbafter gcfree mem : 5163mbafter gcfree mem : 5151mbafter gcfree mem : 5148mbafter gcfree mem : 5556mb
第二次結果:
process startfree mem : 5692mbbefore gcfree mem : 4882mbafter gcfree mem : 4848mbafter gcfree mem : 4842mbafter gcfree mem : 4843mbafter gcfree mem : 4816mbafter gcfree mem : 4822mbafter gcfree mem : 4816mbafter gcfree mem : 4809mbafter gcfree mem : 4810mbafter gcfree mem : 4831mbafter gcfree mem : 4830mb
雖然我們釋放了4096byte的buffer,但是由於那幾byte的位元組沒有釋放掉,將會造成整個8KB的記憶體都無法釋放,如果繼續執行迴圈最終我們的系統記憶體將耗盡,程式將crash。同樣由於我們是依次迴圈分配 4096+幾 byte記憶體的,所以每塊8KB的記憶體空間都將浪費409Xbyte,在執行迴圈之後,我們明顯發現第二次的記憶體佔用比第一次要大很多。這裡我們將近多出了300MB左右的記憶體消耗。
3、buffer字串的串連 我們接受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\n‘); }) }).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: 15msbuf concat: 8ms
在1000次拼接過程中,兩者的效能幾乎相差一倍,而且當客戶上傳的是非UTF8的字串時,直接+=還容易出現錯誤。
4、獨享的空間 如果你想建立一個獨享的空間,獨立的對這塊記憶體空間進行讀寫,有兩種辦法,1是執行個體化一個超過8KB長度的buffer,另外一個就是使用slowbuffer類。
5、buffer的釋放 很遺憾,我們無法手動對buffer執行個體進行GC,只能依靠V8來進行,我們唯一能做的就是解除對buffer執行個體的引用。
6、清空buffer 刷掉一塊buffer上的資料最快的辦法是buffer.fill
淺析nodejs的buffer類