Lua高级应用

    技术2025-03-29  33

    一、lua数据结构及内存占用分析

    1.基础数据结构

    lua的基本数据表示是type+union的方式,根据不同类型映射到union的不同结构上面,统一的表示结构lua_TValue

    typedef union Value { GCObject *gc; /* collectable objects */ void *p; /* light userdata */ int b; /* booleans */ lua_CFunction f; /* light C functions */ long long i; /* lua_Integer integer numbers */ double n; /* lua_Number float numbers */ } Value; struct lua_TValue { Value value_; int tt_; } TValue;

    lua的table占用内存明显高于C/C++,主要有以下几个原因:

    1、lua table支持动态插入,所以为了性能必须要分配更大内存,减少因为每次的插入而导致的重新分配。这样极端情况就会多消耗掉一倍的内存。

    2、lua table的节点使用的是Node,为了追求通用性,对应kv字段基本都是TValue类型,而这个类型占用16字节,kv消耗加起来就是32字节,明显高于C/C++里面简单字段类型的消耗。当然lua table引入array可以不需要key字段,内存接近省一半。

    3、lua table本身的管理数据有56字节,如果是一个很大的表,这个占用比率并不明显。但如果是多个小表,占用比例就会很大。

    4、在实际开发过程中,一般都会表嵌套表,很多层,由于每层都有内存冗余和浪费,这样嵌套下来消耗就会叠加的更明显。

    collectgarbage("stop");--先停止GC local gc1 = collectgarbage("count"); local nullTable = {}; local gc2 = collectgarbage("count"); print( gc2 - gc1 );--一个空表是56B local gc3_1 = collectgarbage("count"); nullTable["a"] = 0; local gc3_2 = collectgarbage("count"); print( gc3_2 - gc3_1 );--一个kv是32B local gc4_1 = collectgarbage("count"); nullTable[1] = 0; local gc4_2 = collectgarbage("count"); print( gc4_2 - gc4_1 );--一个v是16B collectgarbage("restart");--重新唤起GC

    2.lua内存占用分析

    Lua中table类型,每条记录对外都是key-value的方式读写,底层是用array+hashtable的方式管理数据的,但对外是透明的。

    不论array还是hashtable都是连续的内存分布。在查找时:

    1. 如果key是整型, 并且 key > 1 and key < max_array_size, 直接取array[key]数据

    2. 其他情况,默认读取hashtable。Hashtable的管理方有些特别,当不同key hash到同一个node时,用链表来维护这些冲突节点。与stringtable 链表节点动态分配的方法不同, HashTable使用空闲链表来维护冲突节点。

    首先说一种典型的情况,调用table.insert 或者table[#table + 1] 按序插入列表的,就是存放在array里面。

     

    使用预填充方式创建table会省CPU消耗,否则每次动态扩容都会新建数据表,把原来的数据重新hash到新分配的内存中,并且每次扩容都是上一次的2倍。

    rehash数据重新分布

    上面只是描述节点不够用时触发内存扩容,数据重新进行hash分布。但如果既有Array数据,又有HashTable数据时,会怎么进行rehash呢?

      系统会把所有的key为整数的节点进行统计(包含在array和HashTable的),同时数组最大内存Max_Aarry_Size按2的指数倍增长,然后计算满足条件Key <=Max_Aarry_Size的整数节点数量,当数量 > Max_Aarry_Size/2 就认为array内存能充分利用,使用率超过50%,直到系统找到一个最大的符合条件的Max_Aarry_Size为止。剩下的节点数量进入HashTable, HashTable也是按2的指数倍增长,直到能够装下剩余节点数为止。

    因此,就可知道下方的表为什么使用的HashTable存储的原因了。

    for i=1, 10000 do

        tab[10000+i] = 10000+i

    end

    当Max_Aarry_Size = 2^13=8196时,没有Key落在这个[1, 8196]区间。

    当Max_Aarry_Size= 2^14=16392时,只有6392条数据落入区间[1, 16392], 区间利用率小于50%。

    当Max_Aarry_Size= 2^15=32784时,只有10000落入区间[1, 32784], 也不满足利用率的要求。

    二、Lua高级应用

    1.Lua协程

    ---唤起协程 function receive (prod)     local status, value = coroutine.resume(prod)     return value end ---挂起等待 function send (x)     coroutine.yield(x) end ---生产者:生产一个产品 function producer ()     return coroutine.create(function ()         while true do             local x = math.random( 1, 1000 ); -- produce new value             send(x)         end     end) end ---解释器:对产品进行包装操作 function filter (prod)     return coroutine.create(function ()         local line = 1         while true do             local x = receive(prod) -- get new value             x = string.format("Line: %5d Value: %s", line, x)             send(x)  -- send it to consumer             line = line + 1         end     end) end ---消费者:消费者有需求,则通知解释器,解释器先通知生产者生产,再进行包装,交给消费者 function consumer (prod)     return receive(prod) -- get new value end local product = filter( producer()); function TestPanel.secondUpdate()    print_green("-----",consumer( product )); end

    2.非抢占式多线程:LuaSocket库

    require "luasocket"; ---加载LuaSocket库  ---接收数据 function receive (connection)     connection:timeout(0) -- timeout(0)使得对连接的任何操作都不会阻塞     local s, status = connection:receive(2^10)     --当操作返回的状态为 timeout 时意味着操作未完成就返回了     if status == "timeout" then --timeout的时候挂起,挂起后s和status还是在被不断推送的。         coroutine.yield(connection)     end     return s, status end ---下载数据 function download (host, file)     local c = assert(socket.connect(host, 80)) --80主机端口     local count = 0  -- counts number of bytes read     c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")     while true do         local s, status = receive         count = count + string.len(s)         if status == "closed" then break end     end --上述方法只是计算接收到的文件大小(字节数)     c:close() --关闭连接     print(file, count) end ---获取文件 threads = {}  -- list of all live threads function get (host, file)  -- create coroutine     local co = coroutine.create(function ()         download(host, file)     end)     -- insert it in the list     table.insert(threads, co) end ---分配器( 对所有线程循环,移除掉已经完成任务的线程) function dispatcher ()     while true do         local n = #threads         if n == 0 then break end  -- no more threads to run         local connections = {}         for i=1,n do             local status, res = coroutine.resume(threads[i])             if not res then -- thread finished its task?                 table.remove(threads, i)                 break             else -- timeout                 table.insert(connections, res)             end         end         if #connections == n then  --当所有的连接都 timeout 分配器调用 select 等待任一连接状态的改变,此方法不会发生忙等待,否则可能会引起程序阻塞。             socket.select(connections)         end     end  end ---实例调用 function GetSocketData()     local host = "www.w3c.org";     get(host, "/TR/html401/html40.txt");     get(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf");     get(host, "/TR/REC-html32.html");     get(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt");     dispatcher() --main loop; end

    3.设置只读表

    function readOnly( t )     local proxy = {};     local mt = {        __index = t,        __newindex = function( t, k , v )           print_e( "attempt to update a read-only table"  );        end     }     setmetatable( proxy , mt );     return proxy; end

    4.使用动态名字访问全局变量

    --获取值 function getfield(f ) local v = _G; -- lua会把所有全局表存到_G里 for w in string.gmatch(f, "[%w_]+") do v = v[w];--层层查找 end return v;--返回最后一层找到的值 end --设置值 function setfield(f, v ) local t = _G for w,d in string.gmatch(f, "([%w_]+)(.?)") do if d == "." then t[w] = t[w] or {} t = t[w] else t[w] = v end end end setfield("TestManager.test.value", 10 ); print( tostring( getfield( "TestManager.test.value" ) ) );--10 local a = TestManager.test.value; --10

     

    Processed: 0.014, SQL: 9