簡體   English   中英

Lua 是否優化了“..”運算符?

[英]Does Lua optimize the ".." operator?

我必須執行以下代碼:

local filename = dir .. "/" .. base

循環中數千次(這是打印目錄樹的遞歸)。

現在,我想知道 Lua 是否在一個 go 中連接 3 個字符串(dir、“/”、base)(即,通過分配一個足夠長的字符串來保存它們的總長度),或者它是否通過在兩個步驟:

local filename = (dir .. "/")              -- step1
                               .. base     -- step2

最后一種方式在內存方面效率低下,因為分配了兩個字符串而不是一個。

我不太關心 CPU 周期:我主要關心 memory 的消耗。

最后,讓我概括一下這個問題:

Lua 在執行以下代碼時是否只分配一個字符串,還是 4 個?

local result = str1 .. str2 .. str3 .. str4 .. str5

順便說一句,我知道我可以這樣做:

local filename = string.format("%s/%s", dir, base)

但我還沒有對其進行基准測試(內存和 CPU 方面)。

(順便說一句,我知道 table:concat()。這增加了創建表的開銷,所以我想它不會在所有用例中都有益。)

一個獎勵問題:

如果 Lua 沒有優化 ".." 運算符,那么定義一個 C function 用於連接字符串utils.concat(dir, "/", base, ".", extension) 、con、

雖然Lua對..使用進行了簡單的優化,但你仍然應該小心地在緊密循環中使用它,特別是在連接非常大的字符串時,因為這會產生大量垃圾,從而影響性能。

連接多個字符串的最佳方法是使用table.concat

table.concat允許您將表用作所有要連接的字符串的臨時緩沖區,並且只有在完成向緩沖區添加字符串后才執行連接,如下面的愚蠢示例所示:

local buf = {}
for i = 1, 10000 do
    buf[#buf+1] = get_a_string_from_somewhere()
end
local final_string = table.concat( buf )

可以看到..的簡單優化分析以下腳本的反匯編字節碼:

-- file "lua_06.lua"

local a = "hello"
local b = "cruel"
local c = "world"

local z = a .. " " .. b .. " " .. c

print(z)

luac -l -p lua_06.lua的輸出如下(對於Lua 5.2.2):

main  (13 instructions at 003E40A0)
0+ params, 8 slots, 1 upvalue, 4 locals, 5 constants, 0 functions
    1   [3] LOADK       0 -1    ; "hello"
    2   [4] LOADK       1 -2    ; "cruel"
    3   [5] LOADK       2 -3    ; "world"
    4   [7] MOVE        3 0
    5   [7] LOADK       4 -4    ; " "
    6   [7] MOVE        5 1
    7   [7] LOADK       6 -4    ; " "
    8   [7] MOVE        7 2
    9   [7] CONCAT      3 3 7
    10  [9] GETTABUP    4 0 -5  ; _ENV "print"
    11  [9] MOVE        5 3
    12  [9] CALL        4 2 1
    13  [9] RETURN      0 1

您可以看到只生成了一個CONCAT操作碼,盡管腳本中使用了許多..運算符。


要完全了解何時使用table.concat您必須知道Lua字符串是不可變的 這意味着每當您嘗試連接兩個字符串時,您確實創建了一個新字符串(除非結果字符串已被解釋器實例化,但這通常不太可能)。 例如,請考慮以下片段:

local s = s .. "hello"

並假設s已經包含一個巨大的字符串(比方說,10MB)。 執行該語句會創建一個新字符串(10MB + 5個字符)並丟棄舊字符串。 所以你剛剛為垃圾收集器創建了一個10MB的死對象來應對。 如果你反復這樣做,你最終會占用垃圾收集器。 這是真正的問題..這是典型的使用情況下,有必要收集,最后一個字符串的所有片段在表中,並使用table.concat就可以了:這將無法避免垃圾的產生(調用table.concat后所有的部分都將是垃圾,但是你會大大減少不必要的垃圾。


結論

  • 每當你連接幾個,可能很短的字符串,或者你沒有緊密循環時,請使用.. 在這種情況下, table.concat會給你帶來更差的性能,因為:
    • 你必須創建一個表(通常你會扔掉);
    • 你必須調用函數table.concat (函數調用開銷比使用內置的..運算符多次影響性能)。
  • 如果需要連接多個字符串,請使用table.concat ,尤其是在滿足以下一個或多個條件時:
    • 你必須在后續步驟中執行它( ..優化只在同一個表達式中起作用);
    • 你處於緊張的環境中;
    • 字符串很大(比如幾個或更多)。

請注意,這些只是經驗法則。 如果性能真的至關重要,那么您應該對代碼進行分析。

無論如何Lua在處理字符串時與其他腳本語言相比要快得多,所以通常你不需要太在意。

在您的示例中, ..運算符是否進行優化對於性能來說幾乎不是問題,您不必擔心內存或CPU。 還有用於連接多個字符串的table.concat (請參閱Lua中的編程 )以了解table.concat

回到你的問題,在這段代碼中

local result = str1 .. str2 .. str3 .. str4 .. str5

Lua只分配一個新字符串,從luaV_concat Lua相關源檢查這個循環:

do {  /* concat all strings */
    size_t l = tsvalue(top-i)->len;
    memcpy(buffer+tl, svalue(top-i), l * sizeof(char));
    tl += l;
} while (--i > 0);
setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl));
total -= n-1;  /* got 'n' strings to create 1 new */
L->top -= n-1;  /* popped 'n' strings and pushed one */

你可以看到Lua在這個循環中連接了n字符串,但是最后只將一個字符串推回到堆棧,這是結果字符串。

順便說一句,我知道 table:concat()。 這增加了創建表的開銷,所以我想它不會在所有用例中都有益。

在這個特定的用例(和類似的用例)中,如果您擔心創建大量垃圾表,則可以考慮重用表:

local path = {}
...
-- someplace else, in a loop or function:
path[1], path[2] = dir, base
local filename = table.concat(path, "/")
path[1], path[2] = nil
...

您甚至可以將其概括為“concat”實用程序:

local rope = {}
function string_concat(...)
    rope = {...} -- prepare rope
    local res = table.concat(rope)
    for i = 1, select("#", ...) do rope[i] = nil end -- clear rope
    return res
end

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM