i was looking over old code i had and as context, the game this is used for has certain “devices” where, depending on its setting, it would do arithmetic operations on two values inserted, hence this dictionary of “ArithmeticOperators” that did whatever arithmetic operation on an x and y;
so that later in my code, i could do something like:
local operator = "+"
local x, y = 9, 10
local Result = ArithmeticOperators[operator](x, y)
i knew back then and definitely know now that this is most likely a pretty ugly and inefficient way of going about doing this, and, after learning about loadstring’s recently, immediately thought about possibly using it to replace this mess
my question is, because im not actually sure, is it more resource intensive to hold these functions within a dictionary to be called later for each operator, or do something like this?:
local operator = "+"
local x, y = 9, 10
local Result = loadstring(`return {x} {operator} {y}`)()
the thing is, loadstring also feels a bit inefficient, as im creating a whole new anonymous function for a single calculation over and over at runtime… is there any other way to go about doing this?
i just realised after creating this post that it would also probably be a good idea to include the rest of the code i was using, and what “Operators” was doing;
its extremely embarrassing to show now that im looking at it but at the same time it also shows that i could just redo practically all of these with the exact same loadstring method i showed in the original post, however still definitely shows that all of these calculations that may be ran at runtime would definitely pile up if done in a resource heavy way
Would you mind elaborating on why you are using the dictionary in the first place? Why not write what the functions return? The table adds additional layer of abstraction, definitely making the process slower.
loadstring is probably even slower, given the fact that it deoptimises the environment. It is also a global keyword (global access, although optimised) and string loading is disabled by default, unless enabled manually in ServerScriptService properties.
i kinda guessed that loadstring may be slower even if looking more cleaner, however im not sure what you mean when you said simply using loadstring “deoptimises the environment”. can you clarify? does it cost more memory/resources to use loadstring within a script/function, due to/regardless of what is actually being loaded?
i was aware of having to enable loadstring’s before, but i might as well just ask another question here while i have your attention lol, is it generally malpractice to use loadstrings within your scripts, or leave them enabled in ServerScriptStorage? i definitely understand vulnerabilities when using user-generated text within a loadstring, but if i were to simply restrict what “operators” could be used within the devices interface (by doing something like checking if the inputted string is found within a preset table of known boolean and arithmetic operators) would there be any other downsides to using loadstring? (not nessecarily talking about this case anymore as i understand now that loadstring is a bit unoptimised to be using just for returning 1 + 9 )
i’ll keep the dictionaries then, but would you reccommend another way to rewrite them or optimise each function? or do you think they’re as optimised as possible as is?
Loadstring isn’t particularly slower. It doesn’t work on client-side for whatever reason and is just generally discouraged.
In your situation, it wouldn’t make any sense. Loading a script using loadstring is way slower than accessing a dictionary, while calling both is about the same. There are few reasons for using loadstring I can think of, and most have better alternatives.
Roblox does some serious compiler optimisations for quicker table access and function calling. Among them there is this ‘importing of global access chains’, as they call it, which means globals like string.find or math.sin are imported and resolved when the script loads and not during execution (in the line its actually used).
For example, in vanilla Lua we can get about 30% cut on execution time if we localise math functions, such as local sin = math.sin. Luau already takes care of this internally, hence localising makes almost no difference.
loadstring, getfenv, and setfenv prevent any optimisations because they can reach out of their environment and take away the predictability enabling compile-time optimisations. As far as I know the fenv functions are also softly deprecated.
local OPERATOR = "+"
local ITERATIONS = 1e5
local RUNS = 5
local ArithmeticOperators = {
["+"] = function(x, y) return x + y end;
}
local loadstr = loadstring
local results = {table.create(RUNS), table.create(RUNS)}
task.wait(8); print("Began")
for i=1,RUNS do
task.wait()
local t = os.clock()
for i=1,ITERATIONS do
local n = ArithmeticOperators[OPERATOR](i, i)
end
table.insert(results[1], os.clock() - t)
task.wait()
local t = os.clock()
for i=1,ITERATIONS do
local n = loadstr(`return {i} {OPERATOR} {i}`)()
end
table.insert(results[2], os.clock() - t)
end
----------
local sum1, sum2 = 0, 0
for i=1,RUNS do
sum1 += results[1][i]
sum2 += results[2][i]
end
print("Average dictionary: "..(sum1/RUNS))
print("Average loadstring: "..(sum2/RUNS))
print("Ratio: "..(sum2/RUNS)/(sum1/RUNS))
Moderation is one reason. Loadstring is often used in malicious free models, so any Roblox moderator will see loadstring as a yellow flag, thus there is a higher chance for a mistake to happen. Another reason is simply that there is not much reason to use loadstring to begin with. It’s difficult to sandbox the loaded code, you cannot create MeshParts using code (not talking loading assets) and you cannot run it on the client. And for every case I mentioned there is an alternative.
As for performance, loadstring still treats your code with the same compiler used for any other code. From my testing, the difference was negligible, but that was also good 4 months ago.
im thankful for all this research you did lol, and after that speed comparison i’ll definitely be using dictionary, however i have a small question after looking over your code;
from what i understood, you said here that localising the global functions practically makes no difference on performance, however in your code i saw you localise loadstring under a different name; does it actually create a significant performance difference when comparing speeds to localise the functions in the dictionaries + global function loadstring? or did you just declare it under a different name for preference?