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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

luajit表记录监控(忆一次项目上线中遇到的luajit对象内存泄漏)

發布時間:2024/1/17 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 luajit表记录监控(忆一次项目上线中遇到的luajit对象内存泄漏) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 背景

我們項目為ARPG手游(也沒啥見不得人的,就叫暗黑血統手游,后期不少坑錢活動的實現出自我手,輕拍。。。)。我們的服務器底層設計源于某大廠,c/c++和luajit的實現,這次要說的是項目上線時(2014年11月左右)的一次luajit對象內存泄漏(廢棄的數據沒刪,我們都叫泄漏)和相應的解決方案。

2. 問題表現

內存增長,速率大概為200~300MB/天。

我們日志會周期性打印Tcmalloc內存(Tcmalloc分享另見同事Wallen的博客TCMalloc解密)和lua部分內存。獲取方法如下:

//tc malloc部分 size_t tc_memory = 0; MallocExtension::instance()->GetNumericProperty( "generic.current_allocated_bytes", &tc_memory );//luajit部分 int lua_memory = lua_gc( _L, LUA_GCCOUNT, 0 ); 復制代碼

通過日志發現在線人數相似時,lua部分內存和總內存在同步增長,c/c++部分內存(即上兩個部分差值,主要是網絡庫、對象體系對象和World部分的內存)基本穩定。日志顯示lua的gc正常。

3. 分析

  • c/c++部分沒有泄漏,player/monster等對象釋放沒有問題,c層對象析構基本由lua層觸發,lua層對應的對象內存釋放也是沒有問題的。
  • 通過日志顯示,場景/副本管理的釋放也是沒有問題的,在線導出各個場景/副本內的對象個數/類型,經過分析,也沒發現什么問題,日志監控的數據也都正常。
  • 最后懷疑是一些非主要的lua對象,存在表里沒清除。因為lua對象里,我們沒有太復雜的表結構,因此這些泄漏的內存會扁平地存在少量的幾個表里。因此,只要能知道各個表的記錄數,結合在線人數推算其最大可能數,二者相比較,就能找出泄漏的表,在檢查表的增刪邏輯,就可以找到泄漏的邏輯。

4. lua表記錄數告警方案

如前述,只要知道各個表的記錄數,結合在線人數推算其最大可能數,二者相比較,就能找出泄漏的表。但是如果直接這么做,勢必影響性能,即使熱更gm指令用lua全遍歷1次(因為table的值也可能是table,實際上要遍歷一棵樹),都是分鐘級別的。分幀做?這坑好大,如果不行也只能這么干了。按泄漏的速度,內存可以撐到下一次維護,所以,不慌。

然后看看c層luajit表的相關操作,看看有沒有更有效率的獲取方法(我們c層代碼不熱更,改c層代碼需要等維護重啟后才能生效,按泄漏速度,可以接受)。

代碼里主要關注的是lua table的增加和刪除記錄。然后看到lua table的resize(下面luajit相關代碼都是luajit2.1分支代碼,和1會長得有些不一樣,我們已升luajit2.1,將就一下)

/* Resize a table to fit the new array/hash part sizes. */ void lj_tab_resize(lua_State *L, GCtab *t, uint32_t asize, uint32_t hbits) { ... } 復制代碼

邏輯其實就是數組段或者哈希段每次超過2的n次冪,會重新分配內存。

我們不需要精確的記錄數,其實只要在他每次resize的時候打條日志就能知道這個table大概的記錄數,比如上一條日志是1024->2048,那么記錄數在1024~2048之間。日志的優化見工程化部分。

怎么確定是哪個table?這里我們能取到的是table的地址,取不到table的名字,根據地址取名字也是一場噩夢。這里我們曲線救國,既然能拿到lua_State,可以把lua的堆棧打出來,根據文件名和行號可以定位到代碼行號,一行代碼沒幾個table,這樣就能確定下來了。

//打印lua層堆棧,編譯lua加上調試信息 int32_t c_bt( lua_State* _L ) { lua_Debug ldb;LOG("[LUAWRAPPER](lua_stack) begin .......... ");for(int32_t i = 0; lua_getstack( _L, i, &ldb)==1; i++){lua_getinfo(_L, "Slnu", &ldb);const char * name = ldb.name;if (!name)name = "";const char * filename = ldb.source;LOG("[LUAWRAPPER](bt) #%d: %s:'%s', '%s' line %d", i, ldb.what, name, filename, ldb.currentline );}LOG("[LUAWRAPPER](lua_stack) end .......... ");return 0; } 復制代碼

到此,表的記錄數我們能拿到個粗略的值,也知道是哪張表了,每張表最大的數值也可以根據在線人數估計(大部分近似在線人數+暫時斷線的+跨服的,Buff之類的可以乘以一個最大倍數),剩下的就交給時間和人工分析日志比較了。過濾掉正常的日志,就能得到包含了泄漏對象的表了,在分析增刪邏輯就能找到廢棄又沒有清楚的數據了。

5. 工程化

前面講的只是方案,真正應用的時候,需要減少日志的條數,以減輕分析的工作量。減少日志通過下面兩種方式:

  • 超過一定大小,才打日志,我們一個服在線是3k左右,閥值取4096。
  • 實踐發現,如果表在2的n次冪邊界發生頻繁切換時,resize日志會重復打,所以修改了表結構,實現每個邊界只打一次。

所以,如果漏的不嚴重(<4096)又不會隨時間增長,是查不出來的,也就算了。

5.1. lua table表結構的修改

typedef struct GCtab {...//extended by ludongint32_t max_sizearray;int32_t max_sizeobj; } GCtab; 復制代碼

5.2. 初始化(luajit1的數值是不一樣的)

/* Create a new table. Note: the slots are not initialized (yet). */ static GCtab *newtab(lua_State *L, uint32_t asize, uint32_t hbits) {...t->max_sizearray = 4096;t->max_sizeobj = 4096;return t; } 復制代碼

5.3. 寫日志

為了解決庫編譯依賴的問題,將上層日志函數定義為一個函數變量,進程啟動時注冊賦值。函數實現賦值就不貼了。

/* -- Table resizing ------------------------------------------------------ */typedef void (*lua_large_table_warn_func)( lua_State *L, char* fmt, ... ); lua_large_table_warn_func g_lua_large_table_warn = NULL;/* Resize a table to fit the new array/hash part sizes. */ void lj_tab_resize(lua_State *L, GCtab *t, uint32_t asize, uint32_t hbits) {...//--------------------------------------------------// by ludong// check resize timeif (t->asize > t->max_sizearray || t->hmask > t->max_sizeobj) {int32_t old_max_sizearray = t->max_sizearray;int32_t old_max_sizeobj = t->max_sizeobj;t->max_sizearray = (t->asize > t->max_sizearray) ? t->asize : t->max_sizearray;t->max_sizeobj = (t->hmask > t->max_sizeobj) ? t->hmask : t->max_sizeobj;if (g_lua_large_table_warn) {g_lua_large_table_warn( L, "[ltable.c](resize) table:0x%x, array check size:%d, now:%d, node check size:%d, now:%d",(size_t)t,old_max_sizearray, t->asize,old_max_sizeobj, t->hmask);}}} 復制代碼

5.4. 性能影響

每個表多8字節(我們64位了,對齊后一樣的,32位自己摳一摳),對大部分表多兩個邏輯判斷,對大表每次日志邊界打一條日志和lua堆棧,內存和cpu基本都沒沒感覺。

6. 后記

這個方案做好之后基本是我們項目上線必檢查的日志了,總會有一些不小心就沒刪的。

總結

以上是生活随笔為你收集整理的luajit表记录监控(忆一次项目上线中遇到的luajit对象内存泄漏)的全部內容,希望文章能夠幫你解決所遇到的問題。

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