c - ¿Lua optimiza el operador ".."?
string performance (2)
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. (Ver Programació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 cadena nueva, 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 concatena n
cadenas en este bucle, pero solo empuja hacia atrás a la pila una cadena al final, que es la cadena resultante.
Tengo que ejecutar el siguiente código:
local filename = dir .. "/" .. base
miles de veces en un bucle (es una recursividad que imprime un árbol de directorios).
Ahora, me pregunto si Lua concatena las 3 cuerdas (dir, "/", base) de una vez (es decir, asignando una cuerda lo suficientemente larga 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 ineficiente en cuanto a la memoria porque se asignan dos cadenas en lugar de solo una.
No me preocupan demasiado los ciclos de 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, sé acerca de la tabla: concat (). Esto tiene la carga adicional de crear una tabla, así que supongo que no será beneficioso en todos los casos de uso).
Una pregunta adicional:
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, debe tener cuidado de usarlo en un circuito cerrado, especialmente cuando se unen cadenas muy grandes, ya que esto creará una gran cantidad de basura y, por lo tanto, afectará el rendimiento.
La mejor forma de concatenar muchas cadenas es con table.concat
.
table.concat
permite usar una tabla como un búfer temporal para todas las cadenas que se concatenarán 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 bytecode 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):
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
Puede ver que solo se genera un único CONCAT
operación CONCAT
, aunque muchos operadores se utilizan en el script.
Para entender completamente cuándo usar table.concat
, debe saber que las cadenas Lua son inmutables . Esto significa que cada vez que intenta concatenar dos cadenas, de hecho está creando una nueva cadena (a menos que la cadena resultante ya esté intervenida por el intérprete, pero esto generalmente no es probable). Por ejemplo, considere el siguiente fragmento:
local s = s .. "hello"
y supongamos que s
ya contiene una cadena enorme (digamos, 10MB). La ejecución de esa declaración crea una nueva cadena (10 MB + 5 caracteres) y descarta la anterior. Así que acabas de crear un objeto muerto de 10 MB para que el recolector de basura pueda arreglárselas. 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á enormemente la basura innecesaria .
Conclusiones
- Use
..
siempre que concatene pocas cadenas, posiblemente cortas, o no esté en un círculo cerrado. En este caso,table.concat
podría darle peor rendimiento porque:- debes crear una tabla (que generalmente tirarías);
- debe llamar a la función
table.concat
(latable.concat
llamada a la función afecta el rendimiento más que usar el operador incorporado..
algunas veces).
- Use
table.concat
, si necesita concatenar muchas cadenas, especialmente si se cumplen una o más de las siguientes condiciones:- debes hacerlo en pasos posteriores (la optimización
..
funciona solo dentro de la misma expresión); - estás en un círculo cerrado;
- las cadenas son grandes (digamos, varios kBs o más).
- debes hacerlo en pasos posteriores (la optimización
Tenga en cuenta que estas son solo reglas generales. 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 scripting cuando se trata de cadenas de caracteres, por lo que normalmente no es necesario que te importe tanto.