• 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 (作者)

  • 相关阅读:
    C++语言的url encode 和decode
    ICE实现服务器客户端
    ICE:slice语言常识整理
    SSH框架总结(框架分析+环境搭建+实例源代码下载)
    MyEclipse下XFire开发Webservice实例
    关于hashCode与equals
    xcode 4.5 new feature __ ios6 新特性
    js中substr与substring的差别
    MP算法和OMP算法及其思想
    android传感器;摇一摇抽签功能
  • 原文地址:https://www.cnblogs.com/lijiajia/p/8468054.html
Copyright © 2020-2023  润新知