Lua源碼簡(jiǎn)析及使用技巧

lua是一款非常小巧的開源的腳本語言,5.1版本的的壓縮包僅208KB,代碼僅有17000多行。
使用標(biāo)準(zhǔn)c編寫的它可以很方便的嵌入到各個(gè)平臺(tái)系統(tǒng)中,深得游戲開發(fā)者的青睞。
你可以很方便的在github中查看它的源代碼:https://github.com/lua/lua

數(shù)據(jù)類型

lua的數(shù)據(jù)類型定義在lua.h

// lua.h
#define LUA_TNONE (-1)          // 無類型
#define LUA_TNIL 0              // 空類型
#define LUA_TBOOLEAN 1          // 布爾
#define LUA_TLIGHTUSERDATA 2    // 指針 (void *)
#define LUA_TNUMBER 3           // 數(shù)字 (lua_Number)
#define LUA_TSTRING 4           // 字符串 (TString)
#define LUA_TTABLE 5            // 表 (Table)
#define LUA_TFUNCTION 6         // 函數(shù) (CClosure)
#define LUA_TUSERDATA 7         // 指針 (void *)
#define LUA_TTHREAD 8           // LUA虛擬機(jī) (lua_State)

我們可以看到LUA_TLIGHTUSERDATALUA_TUSERDATA都是void類型,但LUA_TLIGHTUSERDATA由lua外部使用者自行管理,不需要GC。

上述定義中,LUA_TTABLE及之后的類型均由GC進(jìn)行管理。

字符串

Lua的字符串與其他語言不同,在Lua虛擬機(jī)中有一個(gè)哈希數(shù)組變量,用來存儲(chǔ)所有字符串
同一個(gè)字符串只會(huì)有一份,一旦創(chuàng)建不可更改,Lua中使用的均是該字符串的引用

這個(gè)哈希數(shù)組存放在global_state.strt

每當(dāng)使用一個(gè)字符串時(shí),會(huì)進(jìn)行以下操作:

  • 計(jì)算該字符串的哈希值
  • 找到對(duì)應(yīng)的哈希桶,遍歷該哈希桶,如果找到同樣的字符串,則返回
  • 否則,在該哈希桶中新插入該字符串
  • 當(dāng)一個(gè)哈希桶中數(shù)量過多時(shí),執(zhí)行一次重新哈希
  • GC階段時(shí)會(huì)找到?jīng)]有使用的字符串進(jìn)行回收

使用技巧

我們知道每次設(shè)置字符串時(shí),都會(huì)有一次哈希和查找操作,如果是新的字符串,還會(huì)新增到全局?jǐn)?shù)組中,所以我們應(yīng)該盡可能減少臨時(shí)字符串以及字符串連接操作

-- 低性能
local str = ""
for i = 1, 99999 do
    str = str .. "."
end
-- 高性能 大約提升10倍
local t = {}
for i = 1, 99999 do
    t[#t + 1] = "."
end
local str = table.concat(t, "")

Table

Lua的table是一個(gè)非常靈活的數(shù)據(jù)類型,它定義在lobject.h中,實(shí)現(xiàn)在ltable.c中。

typedef struct Table {
  CommonHeader;
  lu_byte flags;
  lu_byte lsizenode;  // 以2的lsizenode次方作為哈希表長(zhǎng)度
  unsigned int sizearray;  // 數(shù)組部分大小
  TValue *array;  // 數(shù)組部分
  Node *node; // 哈希表部分
  Node *lastfree;  // 指向最后一個(gè)閑置的鏈表空間
  struct Table *metatable; // 元表
  GCObject *gclist;
} Table;

Lua中的table分為數(shù)組和哈希表兩個(gè)部分

  • 當(dāng)key為正整數(shù)時(shí),在數(shù)組空間滿足時(shí)會(huì)優(yōu)先使用數(shù)組部分
  • 哈希表部分則可以存放任意key和非nil的所有類型value

訪問流程

  • 當(dāng)輸入key為正整數(shù)且小于等于數(shù)組部分大小時(shí),嘗試在數(shù)組部分查找
  • 否則計(jì)算哈希值,找到對(duì)應(yīng)哈希桶,遍歷該哈希桶直到找到該key

這里可以看出,如果key大于數(shù)組部分大小時(shí),即使是正整數(shù),也會(huì)存放在哈希表中

local t = {1}
t[1] = 0 -- 數(shù)組部分
t[100] = 0 -- 哈希表部分

Table空間動(dòng)態(tài)擴(kuò)展

  • local t = {}數(shù)組部分和哈希部分沒有分配空間
  • 在添加元素會(huì)優(yōu)先往哈希表部分添加
  • 哈希表空間不足時(shí)執(zhí)行rehashresize 操作,重新分配數(shù)組部分和哈希部分大小
  • Lua申請(qǐng)內(nèi)存時(shí)以 2 的整數(shù)次冪邊界對(duì)齊
static void rehash (lua_State *L, Table *t, const TValue *ek)

table重新分配內(nèi)存有個(gè)原則:數(shù)組段的利用率不低于 50%

為此在rehash函數(shù)中調(diào)用了numusehash函數(shù)

目的:計(jì)算正整數(shù)key在(2^(i - 1), 2^i]之間的元素?cái)?shù)量,存放在num[i]

static int numusehash (const Table *t, unsigned int *nums, unsigned int *pna)

舉個(gè)例子:

tb = {1,2,3, 20}
num[0] = 1   -- 1在此范圍 2^0,范圍內(nèi)元素個(gè)數(shù)1個(gè)
num[1] = 1   -- 2在此范圍 2^1,范圍內(nèi)元素個(gè)數(shù)1個(gè)
num[2] = 1   -- 3在此范圍 2^2,范圍內(nèi)元素個(gè)數(shù)1個(gè)
num[3] = 0
num[4] = 0
num[5] = 1   -- 20在此范圍 2^5,范圍內(nèi)元素個(gè)數(shù)1個(gè)

而后rehash函數(shù)調(diào)用了computesizes

目的:判斷每個(gè)2次方位置num[i]的總元素個(gè)數(shù)都超過該范圍的50%

static unsigned int computesizes (unsigned int nums[], unsigned int *pna)

舉例例子:

-- 在之前的例子中我們已經(jīng)統(tǒng)計(jì)出了各二次冪分段的元素?cái)?shù)量
-- 接著我們判斷是否每個(gè)二次冪分段使用率超過50%
num[0]分段:元素個(gè)數(shù) 1 > 0.5 * 2^0 滿足利用率
num[1]分段:元素個(gè)數(shù) 1+1 = 2 > 0.5 * 2^1 滿足
num[2]分段:元素個(gè)數(shù) 1+1+1 = 3 > 0.5 * 2^2 滿足利用率
num[3],num[4]分段無元素,跳過
num[5]分段:元素個(gè)數(shù) 1+1+1 = 3 < 0.5 * 2^5 不滿足,舍棄

最終數(shù)組部分分配的大小為2^2,也就是1,2,3均存在數(shù)組部分,20存放在哈希表部分

使用技巧

  • 預(yù)分配策略

對(duì)于空表而言,前三個(gè)元素會(huì)執(zhí)行三次rehash操作,而100萬個(gè)元素僅執(zhí)行了20次(2^20 > 100萬),對(duì)于已知元素大于3個(gè)的表來說,建議采用預(yù)分配策略

-- 低性能
local a = {}
for i = 1, 3 do
    a[i] = true
end

-- 高性能,大約提升一倍
local a= {1,1,1}
for i = 1, 3 do
    a[i] = true
end
  • 盡量復(fù)用臨時(shí)table

Table表的取長(zhǎng)度操作

我們都知道lua可以使用#符號(hào),來取table的長(zhǎng)度,但是其中有很多的限制
舉個(gè)例子,猜猜看下面這段代碼的輸出結(jié)果

print(#{1,2,nil,4,5,6,7,nil}) -- 輸出7
print(#{1,2,3,nil,5,6,7,nil}) -- 輸出3

為什么會(huì)出現(xiàn)這種結(jié)果呢,我們就要看看lua的取長(zhǎng)度函數(shù)了

lua_Unsigned luaH_getn (Table *t) {
  unsigned int j = t->sizearray;
  if (j > 0 && isempty(&t->array[j - 1])) {
    unsigned int i = 0;
    while (j - i > 1u) {  /* binary search */
      unsigned int m = (i + j) / 2;
      if (isempty(&t->array[m - 1])) j = m;
      else i = m;
    }
    return i;
  }
  else {  /* 'j' is zero or present in table */
    if (isdummy(t) || isempty(luaH_getint(t, l_castU2S(j + 1))))
      return j;  /* 'j + 1' is absent... */
    else  /* 'j + 1' is also present */
      return hash_search(t, j);
  }
}

取長(zhǎng)度一般經(jīng)過以下幾個(gè)步驟:

  • 數(shù)組部分末位為空,二分法找空值
print(#{1,2,nil,4,5,6,7,nil}) -- 輸出7
數(shù)組大小為8,二分中值4,key=4存在的,會(huì)向右邊二分查找,返回長(zhǎng)度7

print(#{1,2,3,nil,5,6,7,nil}) -- 輸出3
數(shù)組大小為8,二分中值4,key=4不存在的,向左二分,返回長(zhǎng)度3
  • 哈希部分為空,或者數(shù)組末位+1不存在,直接返回?cái)?shù)組部分大小
-- 哈希部分為空
print(#{nil,nil,nil,nil,nil,nil,nil,8}) -- 輸出8

-- 數(shù)組末位+1不存在
-- 數(shù)組大小4,哈希部分key=5不存在,所以直接返回4
local t = {nil, 2, 3, 4}
t[6] = 6 -- 若t[1]不為nil時(shí),根據(jù)利用率大于50%原則,t[6]會(huì)被分到數(shù)組部分
print(#t) -- 輸出4

-- 另一個(gè)故事
local t = {1,2,3,4}
t[6] = 6
print(#t) -- 輸出6
-- 根據(jù)利用率大于50%原則,2^3次方區(qū)間總個(gè)數(shù)為5,滿足條件,t[6]會(huì)被分到數(shù)組部分
  • 數(shù)組末位+1存在時(shí),數(shù)組size翻倍找空值,再二分查找最大key
    這一部分的代碼是在hash_search函數(shù)中
local t = {nil, 2, 3, 4} -- 數(shù)組部分
t[5] = 5 -- 哈希部分
print(#t) -- 輸出5
-- key = 5存在時(shí),數(shù)組大小=4,翻倍找空值,key = 8不存在,找到空值
則大小落在4-8之間,二分...
最終找到key = 5

原創(chuàng)文章,僅發(fā)布于我的簡(jiǎn)書我的博客中,禁止轉(zhuǎn)載!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Lua 5.1 參考手冊(cè) by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 14,259評(píng)論 0 38
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 7,377評(píng)論 0 17
  • CATextLayer 摘自- ios核心動(dòng)畫高級(jí)技巧 一書 純屬分享 用戶界面是無法從一個(gè)單獨(dú)的圖片里面構(gòu)...
    lumic000閱讀 14,204評(píng)論 2 20
  • 你的聲音, 讓歲月懷念。 我也還在聽。 那曾經(jīng)駐足掌心 是蝴蝶, 它生命未盡卻離去。 它說,不愿見到你 心也枯老的...
    詩間行客閱讀 141評(píng)論 0 2
  • 樸素的唯物主義(泰勒斯、德謨克利特和伊壁鳩魯) 機(jī)械的唯物主義(拉美特利) 直觀的唯物主義(費(fèi)爾巴哈) 辯證的唯物...
    8a5bc35c564a閱讀 312評(píng)論 0 0

友情鏈接更多精彩內(nèi)容