Lua高级应用

一、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

5.使用二进制节省内存

一个空的table ({})就会占用56字节,另外每个键占用16字节,每个值占用16字节,加起来32字节,如果是连续的键(1,2,3...) , 那样就只有值占用16字节。 所以应该尽量避免频繁的创建table,尤其是在update里。另外假如想弄一个字段,但是一个键对应两个值,一般写法是: table = { [key] = { value1 = 1 , value2 = 2 } }; 可以看出每个key都要创建个table但是里面就存了2个值,性价比太低,可以优化成 table = { [key] = ( 1 << 16 ) + 2 };(因为位运算优先级较低,所以需要加个括号)这样每个key就对应一个数字,内存比table小得多。
值的取法:取 value里的1 : value >> 16 (因为1左移了16位,取的时候只要右移16位就行了) 。 取value里的2 : value & 0xFFFF。 ( 0xFFFF 是16进制写法,0x是固定格式,FFFF对应二进制的 1111 1111 1111 1111,当value和 0xFFFF进行按位与运算后,得出的就是后16位的值,即为2,(因为1左移了16位,与不到) )
(当然16位不够也可以32 , 但是不要超过32,lua的number使用double类型,故有53个有效位,外加1个隐含位和1个符号位 , 左移的时候注意位数 )

简单说一下二进制与16进制转换:以二进制的每4位为一个单位,1111 , 分别对应 8 4 2 1 ,即2^3  2^2  2^1  2^0   。转化为十进制就是 1+2+4+8 = 15 , 即十六进制的F。 1111 1111 1111 1111就是十六进制的FFFF。同理 二进制1100 => 8 + 4 + 0 + 0 = 十进制12 =》 十六进制C   0101 => 0+4+0+1 =》 十进制5 =》十六进制5 。 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值