This is really good to know I frequently use assert() and was unaware of this.
It’s completely fine to use assert, however string formatting the error message may be slower since formatting a string includes checking the string types and goes through another work.
Why does roblox dev forum say someone mentioned me here? there’s no mention here
Sorry for the mention, I tagged you for the image credit.
Sorry to bump, but for this example, is it necessary to do == true
?
Or can I simply write return v or error(errorMessage)
?
Thanks in advance.
I have also benchmarked this and these are my results
local function Assert(value)
assert(type(value) == "number", "Bad Argument" .. value)
end
local function If(value)
if type(value) ~= "number" then error("Bad Argument" .. value) end
end
local amount = 1000
local assertTotal = 0
for i = 1, amount do
local t = os.clock()
local a, b = 1, true
for i = 1, 1000 do
pcall(Assert, a)
a, b = b, a
end
assertTotal += os.clock() - t
end
assertTotal /= amount
task.wait()
local ifTotal = 0
for i = 1, amount do
local t = os.clock()
local a, b = 1, true
for i = 1, 1000 do
pcall(If, a)
a, b = b, a
end
ifTotal += os.clock() - t
end
ifTotal /= amount
print("Assert:", string.format("%.9f", assertTotal))
print("If ", string.format("%.9f", ifTotal))
Results:
-- Assert:
0.004660541
0.004596083
0.005090250
0.004749107
0.004602356
0.004767745
0.004852163
-- If
0.004702491
0.004715973
0.004451382
0.004726072
0.004471088
0.004481933
0.004517734
so I would say if is around 0.0002 seconds faster
but if I change the a and b values to always be numbers and change the error message to
"Bad Argument" .. value .. string.format("%.3f", value)
then I get results
-- Assert:
0.000970206
0.001223040
-- If
0.000116331
0.000109242
now we can see that if is around 0.0009 seconds faster
so we should not blame assert for this but blame the string functions because this performance impact will effect any function if your passing string that has had any work done to it not only assert
so the best way to use assert is to pass a simple string into it like this
assert(type(value) == "number", "Bad Argument")
and now the performance should be very close to if
assert
checks if the first parameter is true (or a non-nil value that isn’t false) and errors if it isn’t. In this case, we want to be sure the type is number.
Let’s take the example of:
fasterAssert(num == 123 or "Wrong number!")
When num
is equal to 123, it results in true
. This causes the or
operator to short circuit and skip "Wrong number!"
and instead pass true
into fasterAssert.
But what if num
is not equal to 123? Well in that case or
looks at its second argument "Wrong number!"
and because it is truthy (close enough to true), it accepts it and passes it into fasterAssert
.
So there are two possibilities:
true
-
string
which is truthy.
When something is truthy, it means that the if-statement will see it as true and evaluate its contents, but when comparing with == true
, it will result in false as it isn’t exactly true. In both of these cases, the argument is always truthy. So in the fasterAssert
function, the or
operator will see "Wrong number!"
as truthy, and return that instead always. It will in fact never error either.
Just use warn() so the code doesn’t stop. Error stops the code.
The whole point of erroring is to stop the code from continuing in a broken state. If a function expected a number but got a boolean, then you’re going to want it to stop and tell you that before it tries to evaluate with it.
Warning is more for informing the user of a possible misuse that isn’t severe enough to cause issues, i.e. a Disable
method that warns when the object is already disabled but continues because it won’t do anything.
This is interesting. Although I have been getting nearer to 90% improvements with more calculations.
Maybe team behind Luau can work on optimizing assert? I hope they do it at some point.
The problem doesn’t lie with assert.
The problem is that the string.format is slow which is also calculated even though it’s not needed
Here is a one which shouldn’t lag that much
local function assertf(c, format, ...)
if c then
return c
else
error(string.format(format, ...), 2)
end
end
example
assertf(type(test) == "string", "Expected string got %s", type(test))
how about with the new `` string?
assert(1+1 == 2, `the correct answer is {1+1}`)
The formatting is still calculated where you call the function, so yes.
you can remake the function tho
function assert(condition, message)
if not condition then
error(message or "assertion failed!", 2)
end
return condition
end
that does nothing, the problem is with computing the formatting of the message
Assert itself isn’t really the problem, generating the message is the costly part. If you are doing thousands of assert calls with dynamic messages, you are generating the message every time. So if you use assert in a hot code path, you will get better performance by not generating a message dynamically and instead just passing a static string. You can also use string interpolation and you’ll get slightly better performance from that compared to concatenation.
This all only matters in really hot code paths though. Manual string concatenation has some overhead simply because you’re generating a brand new string, and this adds up over thousands.
That is why using table.concat
to join lots of strings together is so fast compared to concatenation, because you are taking a table and building a string from it once vs building a new string every iteration.
Just to be 100% sure, assert is fine to use as long as you don’t concatenate a string in the second argument?
Good:
assert(false, "Something went wrong!")
Bad:
assert(false, "Something went wrong for"..player.Name.."!")
?
yes, you are correct. You understood it very well.