2014年7月24日 星期四

別讓make的頻率過快,gc不是隨時都在作用

我以為使用俱有gc的語言,就可以為所欲為的配置記憶體(反正有gc在後面擦屁股)。但是從C寫系統程式上來的經驗告訴我:真的有這麼好嗎?雖然抱持著懷疑,但還是先下手寫點code吧。

於是我開始寫go,不疑有他、放心的make([]byte,hugeSize),直到最近的案子中,需要模擬5萬條長連線到一臺測試機器。只送出了幾千個連線請求後,就被OOM Killer 關心了。code大概是這樣:

for {
        bigArray := make([]byte, 1024*1024*10)
readFromTcp(bigArray)
}

debug 之後讓我發現不要在一個for迴圈裏面make,尤其是我的測試需要跑5萬個迴圈,雖然每次回圈的make只用來讀取一次,但是gc並不會“瞬間”就把剛剛沒用到的記憶體清掉。因此,要善用每一次的make,盡可能地重複使用它。

bigArray := make([]byte, 1024*1024*10)
for {
readFromTcp(bigArray)
}

另外,參考 io.Copy() 裡面的實作方式,是在回圈之外先make一個小塊的記憶體,再用回圈的方式去將資料全部複製。

buf := make([]byte, 32*1024)
for {
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if nw > 0 {

但,如果 io.Copy 頻繁的呼叫(1024次/s),記憶體以近32M/s的速度消耗,又該怎麼辦?可能就不能使用io.Copy,而是自己管理記憶體了。

有人提出以buffer pool搭配channel來明確(explicit)的還回記憶體,來減少make的頻率。在記憶體未使用完前,讓gc有機會工作。http://blog.cloudflare.com/recycling-memory-buffers-in-go

沒有留言: