¿Lua optimiza el operador “..”?

Tengo que ejecutar el siguiente código:

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

miles de veces en un bucle (es una recursión que imprime un árbol de directorios).

Ahora, me pregunto si Lua concatena las 3 cadenas (dir, “/”, base) de una sola vez (es decir, asignando una cadena lo suficientemente larga como para mantener sus longitudes totales) o si lo hace de manera ineficiente al hacerlo internamente en dos pasos:

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

Esta última forma sería ineficaz en cuanto a memoria porque se asignan dos cadenas en lugar de solo una.

No me importan los ciclos de la CPU: me preocupa principalmente el consumo de memoria.

Finalmente, permítanme generalizar la pregunta:

¿Lua asigna solo una cadena, o 4, cuando ejecuta el siguiente código?

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

Por cierto, sé que podría hacer:

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

Pero todavía tengo que compararlo (memoria y CPU).

(Por cierto, conozco la tabla: concat (). Esto tiene la sobrecarga adicional de crear una tabla, así que supongo que no será beneficioso en todos los casos de uso).

Una pregunta extra:

En caso de que Lua no optimice el operador “..”, ¿sería una buena idea definir una función C para concatenar cadenas, por ejemplo, utils.concat(dir, "/", base, ".", extension) ?

Aunque Lua realiza una optimización simple en el uso, aún así, debe tener cuidado de usarlo en un circuito cerrado, especialmente cuando se unen cadenas muy grandes, ya que esto creará mucha basura y, por lo tanto, afectará el rendimiento.

La mejor manera de concatenar muchas cadenas es con table.concat .

table.concat permite usar una tabla como un búfer temporal para concatenar todas las cadenas y realizar la concatenación solo cuando haya terminado de agregar cadenas al búfer, como en el siguiente ejemplo tonto:

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

La optimización simple para .. se puede ver analizando el código de bytes desensamblado del siguiente script:

 -- file "lua_06.lua" local a = "hello" local b = "cruel" local c = "world" local z = a .. " " .. b .. " " .. c print(z) 

la salida de luac -l -p lua_06.lua es la siguiente (para Lua 5.2.2):

 principal (13 instrucciones en 003E40A0)
 0+ params, 8 slots, 1 upvalue, 4 locals, 5 constantes, 0 funciones
     1 [3] LOADK 0 -1;  "Hola"
     2 [4] LOADK 1 -2;  "cruel"
     3 [5] LOADK 2 -3;  "mundo"
     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 "imprimir"
     11 [9] MOVE 5 3
     12 [9] LLAMADA 4 2 1
     13 [9] VOLVER 0 1

Puede ver que solo se genera un único CONCAT operación CONCAT , aunque se utilizan muchos operadores en el script.


Para comprender completamente cuándo usar table.concat , debe saber que las cadenas Lua son inmutables . Esto significa que siempre que intente concatenar dos cadenas, está creando una nueva cadena (a menos que la cadena resultante ya esté internada por el intérprete, pero esto suele ser poco probable). Por ejemplo, considere el siguiente fragmento:

 local s = s .. "hello" 

y suponga que s ya contiene una cadena enorme (por ejemplo, 10 MB). La ejecución de esa statement crea una nueva cadena (10MB + 5 caracteres) y descarta la anterior. Así que acaba de crear un objeto muerto de 10 MB para que el recolector de basura pueda hacer frente. Si haces esto repetidamente, terminas acaparando al recolector de basura. Este es el verdadero problema con .. y este es el caso de uso típico donde es necesario recolectar todas las piezas de la cadena final en una tabla y usar table.concat en ella: esto no evitará la generación de basura ( Todas las piezas serán basura después de la llamada a table.concat ), pero reducirá en gran medida la basura innecesaria .


Conclusiones

  • Use .. siempre que concatene pocas, posiblemente cortas, cadenas, o no esté en un circuito cerrado. En este caso, table.concat podría darte un peor rendimiento porque:
    • debes crear una tabla (que normalmente tirarías);
    • tiene que llamar a la función table.concat (la sobrecarga de llamadas a la función afecta el rendimiento más que si se utiliza el operador incorporado .. varias veces).
  • Use table.concat , si necesita concatenar muchas cadenas, especialmente si se cumple una o más de las siguientes condiciones:
    • debe hacerlo en los pasos siguientes (la optimización .. funciona solo dentro de la misma expresión);
    • estás en un bucle estrecho;
    • las cadenas son grandes (por ejemplo, varios kBs o más).

Tenga en cuenta que estas son sólo reglas de oro. Donde el rendimiento es realmente primordial, debe perfilar su código.

De todos modos, Lua es bastante rápido en comparación con otros lenguajes de secuencias de comandos cuando se trata de cadenas, por lo que generalmente no es necesario preocuparse tanto.

En su ejemplo, si el operador realiza la optimización no es un problema para el rendimiento, no tiene que preocuparse por la memoria o la CPU. Y hay table.concat para concatenar muchas cadenas. (Consulte Progtwigción en Lua ) para el uso de table.concat .

De vuelta a tu pregunta, en este fragmento de código.

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

Lua asigna solo una nueva cadena, revisa este bucle de la fuente relevante de Lua en luaV_concat :

 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 */ 

Puede ver que Lua concatenar n cadenas en este bucle, pero solo empuja de regreso a la stack una cadena al final, que es la cadena de resultados.