Lua内存分析工具

最近给公司写了一个lua内存分析工具,可以方便的分析出Lua内存泄露问题(虽然还没正式使用,但我是这样想的,哈哈哈),有图形化界面操作,方便手机端上传快照等功能

内存分析我是在c语言端写的,也有人写过lua端的分析工具,也蛮好用的,不过lua分析工具本身也会影响到lua的内存占用(尽管用的是弱表缓存的),也会有些不准确。
Lua方案:https://github.com/yaukeywang/LuaMemorySnapshotDump

然后找到了云风大神写的C语言解决方案
https://blog.codingnow.com/2012/12/lua_snapshot.html
这个库功能颇为简单,简单到连对象引用链都没有,只打印出key名和内存地址

所以我还是决定自己造轮子改进一下云风大神的方案,也是更进一步的去学习一下lua的c api

C实现起来比Lua复杂一些

  1. 因为要操作Lua栈,稍微写错一个栈没对称弹出,就会导致溢出,调试起来非常麻烦
  2. 因为c语言就像一块空地,什么都要自己造,连一些最基本的数据结构,都没有...
  3. 你需要编译成各个平台的库,这个后面会讲到如何跟tolua c编译到一起
工具分为2个部分
  1. c库生成快照
  2. web端接收上传快照,快照分析


    web端图

Lua中哪些数据类型是需要GC的?

lua源码中定义了这些数据类型

/*
** basic types
*/
#define LUA_TNONE       (-1)

#define LUA_TNIL        0
#define LUA_TBOOLEAN        1
#define LUA_TLIGHTUSERDATA  2
#define LUA_TNUMBER     3
#define LUA_TSTRING     4
#define LUA_TTABLE      5
#define LUA_TFUNCTION       6
#define LUA_TUSERDATA       7
#define LUA_TTHREAD     8

使用GCObject的联合体将所有需要进行垃圾回收的数据囊括了进来。

/*
** Union of all collectable objects
*/
union GCObject {
  GCheader gch;
  union TString ts;
  union Udata u;
  union Closure cl;
  struct Table h;
  struct Proto p;
  struct UpVal uv;
  struct lua_State th;  /* thread */
};

但是还有一些不需要GC的数据类型,所以又定义了一个Value的联合体

/*
** Union of all Lua values
*/
typedef union {
  GCObject *gc;
  void *p;
  lua_Number n;
  int b;
} Value;

这样就可以将Lua中所有的数据类型表示出来了,Lua还使用了一个宏来判断哪些数据类型是需要GC的

#define iscollectable(o)    (ttype(o) >= LUA_TSTRING)

通过这个我们可以知道,定义在LUA_TSTRING后的数据类型,都需要GC。一共有:LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA、LUA_TTHREAD

通过这样的遍历方式,从根节点开始递归整颗GC树


遍历方式

如何遍历table?

void mark_object(lua_State *L, const char *desc, struct lua_gc_node *parent)
{
    luaL_checkstack(L, LUA_MINSTACK, NULL);
    int t = lua_type(L, -1);
    switch (t) {
    case LUA_TTABLE:
        mark_table(L, desc, parent);
        break;
    case LUA_TUSERDATA:
        mark_userdata(L, desc, parent);
        break;
    case LUA_TFUNCTION:
        mark_function(L, desc, parent);
        break;
    case LUA_TTHREAD:
        mark_thread(L, desc, parent);
        break;
    default:
        lua_pop(L,1);
        break;
    }
}

void mark_table(lua_State *L, const char *desc, struct lua_gc_node *parent)
{
    const void *p = lua_topointer(L, -1);
    if(p == NULL)
    {
        return;
    }
    if(isMark(p))
    {
        lua_pop(L, 1);
        return;
    }

    struct lua_gc_node *currNode = gen_node(L, p, desc, parent);

    bool weakk = false;
    bool weakv = false;

    if(lua_getmetatable(L, -1))
    {
        lua_pushliteral(L, "__mode");
        lua_rawget(L, -2);
        if (lua_isstring(L,-1)) 
        {
            const char *mode = lua_tostring(L, -1);
            if (strchr(mode, 'k')) 
            {
                weakk = true;
            }
            if (strchr(mode, 'v')) 
            {
                weakv = true;
            }
        }
        lua_pop(L,1);

        luaL_checkstack(L, LUA_MINSTACK, NULL);
        mark_table(L, ".[metatable]", currNode);
    }
 
    lua_pushnil(L);
    while (lua_next(L, -2) != 0) 
    {
        if(weakv)
        {
            lua_pop(L, 1);
        }
        else
        {
            char temp[128];
            const char * _key = keystring(L, -2, temp);
            mark_object(L, _key, currNode);
        }
        if(!weakk)
        {
            lua_pushvalue(L,-1);
            mark_object(L, ".[key]", currNode);
        }
    }
    lua_pop(L, 1);
}

const void *p = lua_topointer(L, -1);
取出栈顶的指针,下面用到指针做key存入一个哈希表里,来标记是否被遍历过

从metatable中取出__mode,来判断key,value是否为弱引用。如果是弱引用就不需要继续递归了,否则就继续调用mark_object递归

通过lua_next方法可以取出table中的key,value压入栈中

这里一定要严谨使用lua_pop(L, 1)管理虚拟栈的平衡,否则栈很快就溢出了

其他的函数可以多查找Lua手册,里面说的很详细,我就不一一列举啦。

另外在c语言中自己创建的内存,需要手动释放,否则也会有内存溢出问题

s = malloc(sizeof(struct lua_gc_node)); 通过malloc开辟内存
free(current_node); 对应使用free释放

递归完毕后,输出成Json格式的快照文件,方便Web端操作。

Web端功能

  1. 手机上文件传到PC上不太方便,所以弄了个web端直接接收上传的快照文件
  2. 取补集(可以取出2个快照之间,新创建了哪些东西没释放掉),比如战斗前快照,跟战斗后快照进行取补集,就可以知道战斗内有哪些是没释放的,立马就能查出泄露
  3. 取交集(可以查询常住内存)

上传文件的php代码

<?php
  if ($_FILES["file"]["error"] > 0)
  {
      echo "错误:" . $_FILES["file"]["error"] . "<br>";
  }
  else
  {
      if (file_exists("upload/" . $_FILES["file"]["name"]))
      {
          echo $_FILES["file"]["name"] . " 文件已经存在。 ";
      }
      else
      {
          // 如果 upload 目录不存在该文件则将文件上传到 upload 目录下
          move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
          echo "文件存储在: " . "upload/" . $_FILES["file"]["name"];
      }
  }
?>

如何把我们的代码编译到toluac中?

在网上搜这方面的资料,找到了之前同事(外号姐夫)写的博客,哈哈
https://www.jianshu.com/p/5a35602adef8

他讲的很清楚,我这里就不写了,可以看这篇文章把环境搭好。

另外还需要在tolua#中的LuaDLL.cs类里加上一个方法引入我们的库函数


image.png

然后在LuaManager.cs中把函数注册进去给lua使用

lua.OpenLibs (LuaDLL.luaopen_snapshot37);
lua.LuaSetField(-2, "snapshot37");

这样在lua代码里,我们就可以通过

local snapLib = require "snapshot37"

来引入我们的函数了

总结

  1. Lua内存分析工具的一些解决方案
  2. Lua中各种数据类型是怎么表示的
  3. 遍历GCObject的步骤
  4. 具体的一些LUA C API有不明白的可以多查看Lua官方文档

本文主要介绍了以上内容,欢迎找我一起交流讨论

参考

https://blog.codingnow.com/2012/12/lua_snapshot.html
http://www.cnblogs.com/yaukey/p/unity_lua_memory_leak_trace.html
https://www.jianshu.com/p/5a35602adef8
https://troydhanson.github.io/uthash
《Lua设计与实现》—— codedump (作者)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容