在benchmarkgame(世界上最火的性能對比網(wǎng)站)上,Go語(yǔ)言一直有一個(gè)槽點(diǎn),就是極其慢的binary tree性能,執行用時(shí)40秒 (我的機器上,16秒),與此對比,Java版本是6秒,那么問(wèn)題來(lái)了:為什么慢得令人發(fā)指?我們來(lái)深入研究下慢的原因,然后看看能否對其進(jìn)行改進(jìn)。
對于binary tree算法中,最耗性能的地方就是海量的node分配和bottomUpTree()遞歸函數的調用,與這兩項對應的go的特性就是gc的goroutine的堆棧分配。
這個(gè)世界沒(méi)有完美的GC,任何選擇都有代價(jià),一般來(lái)說(shuō)就是低延遲和高吞吐的權衡。
問(wèn)題描述
Java采用的是分代GC,優(yōu)點(diǎn)是node的分配非常輕量,缺點(diǎn)就是分代gc需要使用更多的內存空間,同時(shí)當對象被移動(dòng)到tenured堆時(shí),會(huì )發(fā)生大量的內存拷貝。
Go的gc不是分代的,因此在node的分配上需要消耗更多的資源。Go的gc選擇了超低的延遲,同時(shí)犧牲了部分吞吐,對于絕大多數應用來(lái)說(shuō),Go的選擇是非常正確的,但是對于binary tree這種算法來(lái)說(shuō),就不是很適合了。
解決方案
針對GC的優(yōu)化,有兩個(gè)通用的解決方案就是提前分配合適的堆??臻g和對象復用。對于binary tree算法,就是Nodes的預先分配和復用。
輕量的Goroutine是Go語(yǔ)言的靈魂所在
問(wèn)題描述
為了讓goroutine盡可能輕量,go僅僅為每個(gè)goroutine分配了2KB的初始堆棧大小,在之后Go會(huì )根據需要動(dòng)態(tài)的擴展堆棧大小。同樣,對于絕大多數場(chǎng)景,這種選擇都是非常正確的,但是針對binary tree算法,這種選擇就有了一些問(wèn)題。
Go是在每次函數調用之前檢查goroutine的堆棧大小,如果發(fā)現當前堆棧不夠用,就會(huì )重新分配一個(gè)新的堆??臻g,然后將舊的堆??截惖叫碌睦?。這種操作開(kāi)銷(xiāo)是很小的,但是在binary tree中,bottomUpTree()基本上不做什么工作,調用卻是極其頻繁,這樣一來(lái)再小的開(kāi)銷(xiāo)累積起來(lái)也會(huì )非??捎^(guān)。而且這個(gè)函數的調用是深遞歸,當堆棧需要增長(cháng)時(shí),可能會(huì )拷貝幾次,不僅僅是一次!
解決方案
將bottomUpTree()改為非遞歸的函數,雖然不易實(shí)現,但是還是可以做到的。
沒(méi)有對比,就沒(méi)有傷害!
運行用時(shí):
> time go run old.go 20stretch tree of depth 21 check: -12097152 trees of depth 4 check: -2097152524288 trees of depth 6 check: -524288131072 trees of depth 8 check: -13107232768 trees of depth 10 check: -327688192 trees of depth 12 check: -81922048 trees of depth 14 check: -2048512 trees of depth 16 check: -512128 trees of depth 18 check: -12832 trees of depth 20 check: -32long lived tree of depth 20 check: -1real 0m16.279suser 1m47.569ssys 0m2.663s運行用時(shí)
time go run new.go 20stretch tree of depth 21 check: -12097152 trees of depth 4 check: -2097152524288 trees of depth 6 check: -524288131072 trees of depth 8 check: -13107232768 trees of depth 10 check: -327688192 trees of depth 12 check: -81922048 trees of depth 14 check: -2048512 trees of depth 16 check: -512128 trees of depth 18 check: -12832 trees of depth 20 check: -32long lived tree of depth 20 check: -1dur: 1.71074946sreal 0m1.914suser 0m10.149ssys 0m0.157s性能從16.28秒提升到了1.91秒,提升巨大!
這里提出的解決方案看似是針對binary tree,其實(shí)對于任何GC語(yǔ)言和使用場(chǎng)景來(lái)說(shuō)都是通用的。
牢記這兩種解決方案吧:
聯(lián)系客服