日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

如何缓解Golang大型游戏服务器的GC压力

發布時間:2024/3/26 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何缓解Golang大型游戏服务器的GC压力 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

背景

Golang的垃圾回收器使用的是并行三色標記回收算法。該算法對比分代算法的最大問題就是,無法區分年輕代和老年代對象,如果老年代對象非常多的話,新生代對象的回收效率就會下降。如果程序沒有減慢對象分配速度的話,golang為了避免出現內存不足,會強制降低相關協程的執行速度,擠出cpu幫助垃圾回收。結果就是程序出現延遲和卡頓,尤其業務頻繁的時候。這對游戲玩家來說是非常糟糕的體驗。

有沒有辦法解決這個問題呢?當然有,思路很顯然,對象太多就減少對象分配,回收慢就提高回收效率。但是具體怎么操作,又要保證安全,又要保證有可操作性,就很難了。


分析

減少對象分配,一個是少創建對象,邏輯層面可以合并一些對象,用值代替指針,都是需要調整邏輯結構的微觀層面的操作方法,有效但是杯水車薪。

重用對象也能減少分配,首當其沖的辦法就是對象池。對象池簡單實用,但是缺點就是太簡陋了。分配的對象類型固定,還要使用者手動放回,而且保證不再操作已放回的對象。這對對象類型非常繁多,關系非常復雜的程序來說,可操作性不高。而且如果沒有gc兜底,對象泄露了也很難捕捉。對于某些大型對象,關系簡單的情況,效果很好。

提高回收效率的話,golang本身做了很多底層回收優化,留給應用層的空間不大。針對老生代對象問題,如果能夠讓GC跳過這些對象的掃描,必然節約很大的開銷。可惜golang沒有直接提供對應的支持,唯一發現的就是go:notinheap標記,可惜主要作用是優化寫屏障,卡的很死。

還有一個大殺器CGO,CGO調用C運行庫跳過GC分配內存,也跳過了GC掃描,非常好的思路。但是C內存對象在go里面使用限制太多,像map這種常用數據接口根本沒有對應辦法實現,因為不能操作符重載和泛型,結果就是使用起來太復雜了。所有的數據操作方式都要走C的風格,像是回到了遠古時代,簡直是對高級語言的侮辱。

因為實際情況是,對象數量確實是減少不了多少的,剩下唯一的辦法只有減少GC或者不GC。其實CGO如果可行的話,對象被搬運到C,既能減少go這邊的大量對象分配,也就減少了GC的壓力,非常完美??上Р钜稽c就達成。如果哪天CGO可以像微軟的C++/CLI一樣流暢的跨語言操作.NET,就牛逼了。這點還是不得不佩服微軟的技術實力。當然.NET設計之初就是為了跨語言而生的,情況有點不一樣。扯遠了。

回頭來說,有沒有辦法用go實現一個GC透明的分配器呢。

答案是肯定的。

但是,需要考慮幾個問題,是池子加強版嗎?那本質還是池子啊!肯定不能是池子,我們要的是一個真正的分配器。他能自己管理內存,繞過GC的掃描,能從內存中任意分配對象,這才能滿足我們的需求。


思路

終于知道我們需要什么了!為了不重復造輪子,先看看有沒有現成的成熟方案吧。

  • https://github.com/shoenig/offheap 太簡陋了,基本等于啥都沒有
  • offheap package - github.com/glycerine/offheap - pkg.go.dev 這個好像功能很多,但是好像主要是優化hash表,而且還是要手動釋放,接口使用起來有點繁瑣,對老代碼改動太大了。
  • https://github.com/cachelot/cachelot好像主要是像cache一樣使用呢,也是要大改代碼

沒有一個符合我們需求的輪子呢,自己擼一個可行嗎?那就試試吧。

純CGO的實現還是算了,用起來麻煩,調試也麻煩,編譯還賊慢,直接pass。既然選擇golang就是為了方便啊。那就直接用golang實現吧,還能利用golang的高級功能呢!

內存不就是byte數組嗎?這個簡單。怎么把一個地址轉成對象呢?unsafe指針強轉貌似可以,reflect.NewAt也可以,這個NewAt不就是簡化版C++的placement new嗎?愛了。

分配的對象怎么回收呢?一個個手動放回嗎?那還是洗洗睡吧。干嘛要放回呢?我們是老生代對象,可以永遠不回收的。配置數據就是全局數據,加載好了永遠都不用釋放。那就好處理了。

還有一堆新生代對象呢?好多臨時對象,比如發到網絡上的包,發完了就廢棄了。簡單,我直接把整個分配器一起扔了不就可以了,nice。

所以大概的思路就出來了,我就一直分配對象不回收,最后整個分配器一起丟掉。中間只有分配器占用了一個byte數組對象需要GC掃描,但是因為Go認為數組里面沒有指針,所以就只標記數組頭為可達,掃描就完成了。目標達成!

:為什么byte數組化分出來的對象也是被指針引用,GC就不掃描呢,因為GO的GC是根據GO分配對象當時的對象信息來確定要不要掃描的,細節請看go的垃圾回收文檔。雖然有指針引用我們分配的對象,但是從這個指針指向的地址去找heap中對應的內存塊的時候,找到的是byte數組,他的對象信息里面是沒有子對象的,因此此處的掃描結束。


實現

這一直分配的方式,熟悉的人一下就發現是線性分配器。搞C++的游戲開發不會不知道這些。在C++里面定制分配器簡直就是家常便飯。終于發現C++搞多了還是有很多好處的。

文章有點長了,長話短說不賣關子了。要注意的幾點:

  • 線性分配器分配的對象對GC是透明的,那就不能把原生分配器分配的對象掛到上面,會被GC認為是無人引用的對象回收掉,所以需要能自動識別這種情況。方法就是分配器分配的對象保存下來,通過反射挨個遞歸地從每個對象遍歷子對象,檢查是否有不是分配器分配的內存。(用golang來實現的一個好處就是能用golang的反射,COG的方案就沒有這個好處)
  • 不能在分配器銷毀后,繼續使用分配器分配的對象。C++的內存診斷工具的做法是把對應的內存頁設為不可讀,讀和寫會觸發硬件中斷。不過這個實現起來有點復雜,而且內存開銷非常大。簡單點的方法,分配器返回id,使用的時候再去取對象,這個需要改變對象的使用方式,對老代碼很不友好。其他簡單實用的方法目前沒有找到,如果在宏觀層面能保證的話,問題也不大。
  • 分配器不能分配失敗。簡單,buffer不夠了再加就行了,然后用新buffer繼續分配。絕對不能使用原生的分配器分配對象再掛到分配器上,因為掛載的過程不一定是gc原子性的(我發明的詞匯,參考GC safe-point),坑跟data race一樣隱蔽(不過data race有detector而已)。
  • 分配器也可以重用,用完reset丟到池子里面,取出來又是一條好漢。免得頻繁創建分配器,雖然大部分時候go的原生分配器很快,但是高峰的時候就不一定了。(而且有golang的gc兜底,忘了放回去也不會有大問題。COG的方案就沒有這個好處)

大的問題都解決了,其他就是小細節了,可以參看具體實現:

GitHub - crazybie/linear_ac: Speed up the memory allocation and improve the garbage collection performance.

倉庫還在測試階段不一定穩定。等上線了再反饋優化一波。但是請放心,上線的項目完全夠壓力。

好了,該睡覺了。仿佛夢見CPU再也不飚了,游戲再也不延遲了,完美。

參考:

  • Go內存分配
  • CGO優化內存分配
  • notinheap介紹
go:notinheapapplies to type declarations. It indicates that a type must never be allocated from the GC'd heap or on the stack. Specifically, pointers to this type must always fail theruntime.inheapcheck. The type may be used for global variables, or for objects in unmanaged memory (e.g., allocated withsysAlloc,persistentalloc,fixalloc, or from a manually-managed span).
  • The GC Pacer
If need be, the Pacer slows down allocation while speeding up marking. At a high level the Pacer stops the Goroutine, which is doing a lot of the allocation, and puts it to work doing marking. The amount of work is proportional to the Goroutine’s allocation. This speeds up the garbage collector while slowing down the mutator.
  • GitHub - crazybie/linear_ac: Speed up the memory allocation and improve the garbage collection performance.
Go has all goroutines reach a garbage collection safe point with a process calledstop the world. This temporarily stops the program from running and turns awrite barrieron to maintain data integrity on the heap. This allows for concurrency by allowing goroutines and the collector to run simultaneously.

總結

以上是生活随笔為你收集整理的如何缓解Golang大型游戏服务器的GC压力的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。