I am wanting to make a function that acts like a NAND.
It takes in two inputs, A, B and outputs a C
Which is more efficient in lua
- not A or not B
- not (A and B)
thanks for the replies.
I am wanting to make a function that acts like a NAND.
It takes in two inputs, A, B and outputs a C
Which is more efficient in lua
thanks for the replies.
You should benchmark it chars chars
Both expressions give equivalent output and theyâre both SO fast I donât think you should be bothering with performance of either option. The difference depends on what A
and B
are, but you can still run multiple hundred million of these if-statements in under a second before reaching stack overflow. There are many other factors and circumstances magnitudes more crucial than this, affecting execution time.
Anyways, letâs break down how lua (to my knowledge) interprets the expressions.
IMPORTANT: Luaâs short-circuit evaluation will not evaluate the second operand if the first one is sufficient.
not false or true/false
Wonât even look at the second option because the first operand is true
and the condition is true
regardless of the second operand.
(not true) and true/false
Same applies, but with the opposite values. First operand false
, whole condition is false.
The execution speed will theoretically be faster for the expression that doesnât evaluate the unnecessary part.
A
is true
, not A or not B
will execute faster because it will conclude before B is even evaluated. â not (A and B)
will proceed to check the value of B.
A
is false
, not (A and B)
will execute faster because B wonât be evaluated. â not A or not B
will proceed to check the value of B.
So Iâd say, choose the one you prefer in terms of readability.
Less theorizing, more experimenting! >:3
Two sets of 10k bools, randomly chosen with an even distribution, NANDâed together. First with one method, then with the other.
local nbs = 1e5
local a, b = {}, {}
for i = 1, nbs do
a[i] = math.random(0, 1) == 1
b[i] = math.random(0, 1) == 1
end
local ca, cb = 0, 0
for i = 1, nbs do
if a[i] then ca += 1 end
if b[i] then cb += 1 end
end
print(ca/nbs, cb/nbs) --Just checking that the distribution seems reasonable
local repeats = 1e3
for _ = 1, 1 do
local nts = 1e5
local ts = {}
for i = 1, nts do
local t0 = tick()
local aNandB
for _ = 1, repeats do --Do the calculation lots of times because it's too damn fast
aNandB = not a[i] or not b[i] --1.2e-8, 26% slower
--aNandB = not (a[i] and b[i]) --9.5e-9
end
local t1 = tick()
ts[i] = t1 - t0
end
local avg = 0
for i = 1, nts do
avg += ts[i]/nts
end
print(avg/repeats)
end
Change which version is commented out to switch between testing one or the other.
Code | Average time | Times slower than fastest | Times faster than slowest |
---|---|---|---|
not a[i] or not b[i] |
1.20E-08 | 1.26 | 1 |
not (a[i] and b[i]) |
9.51E-09 | 1 | 0.79 |
not (a[i] and b[i])
is on average 26% faster than not a[i] or not b[i]
for this type of input, and experiments are coo!!!
Hey, cool test!
Iâve done my experiment a bit differently, so my results differ. Iâll also consider why that is so.
Both expressions are almost EQUALLY EFFICIENT.
The difference is usually no higher than 0.8 %!
In attempts to measure the performance as perfectly as possible, I compared the same condition in a number of repetitions (100 million
, to be exact). I ran the repetitions 10 times separately but during the same run-time for both expressions, not A or not B
and not (A and B)
. The experiment was repeated four times, that is for all four combinations of A
and B
booleans. (true, true
, false, false
, true, false
and false, true
).
Described in my post above: Which is the better NAND - #4 by MeerkatSpirit. I expected the performance to be about equal. Both expressions had their advantages depending on the two conditions.
â Solved this with task.wait(10)
before starting.
os.clock()
is the most accurate function for benchmarking. tick()
can potentially cause slight inconsistencies.
Floating point error. Because of the certain limitation in storing numbers, calculating average is not always perfect.
Because both expressions are so damn fast, there are other factors involved in the execution time. These tests arguably cannot be perfect.
Sometimes your results will differ! Studio events, resource like CPU use, background tasks on your computer, CPU and GPU temperature, all this can affect the outcome. Sometimes the first expression is faster than the second, although it theoretically shouldnât be, and vice versa.
local repetitions = 1e8
local times1, times2 = {}, {}
local clock = os.clock
local tstart: number
local tfinish: number
local A = false
local B = true
local avg1: number
local avg2: number
task.wait(10)
print("Began")
for i=1, 10 do
tstart = clock()
local aNandB
for i=1, repetitions do
aNandB = not A or not B
end
tfinish = clock() - tstart
times1[#times1+1] = tfinish
task.wait()
end
for i=1, 10 do
tstart = clock()
local aNandB
for i=1, repetitions do
aNandB = not (A and B)
end
tfinish = clock() - tstart
times2[#times2+1] = tfinish
task.wait()
end
local function FindAverage(array)
local count = 0
for _,v in array do
count += v
end
return count/#array
end
avg1 = FindAverage(times1)
avg2 = FindAverage(times2)
print("Average time | not A or not B: "..avg1)
print("Average time | not (A and B): "..avg2)
if avg1 > avg2 then
print("not (A and B) was faster")
print(
"Relative difference (%): "..string.format("%.3f", tostring(((avg1-avg2)/avg1)*100))
)
else
print("not A or not B was faster")
print(
"Relative difference (%): "..string.format("%.3f", tostring(((avg2-avg1)/avg2)*100))
)
end
â not (A and B)
was negligibly faster.
not (A and B) | 0.6796392300006119 s | not A or not B | 0.6804587700011325 s |
---|
Relative difference (%): 0.120.
â not A or not B
was negligibly faster.
â Interestingly, sometimes the difference was a lot bigger (2.5% or more). Iâm not sure what causes that.
not (A and B) | 0.6842400899971836 s | not A or not B | 0.6828325800059247 s |
---|
Relative difference (%): 0.206.
â not (A and B)
was negligibly faster.
not (A and B) | 0.6969810900001903 s | not A or not B | 0.7017967100007809 s |
---|
Relative difference (%): 0.686.
â not A or not B
was negligibly faster.
not (A and B) | 0.7116225999998278 s | not A or not B | 0.7100575700038462 s |
---|
Relative difference (%): 0.220.
Why did the results differ from the test by @ThanksRoBama ?
ThanksRoBama, nice username
I quite like their test, itâs interesting as a practical example. Fun approach. However, even though the distribution is approximately equal, the combinations of values A and B are not, which causes variations.
After switching to os.clock()
, adding task.wait(10)
at the top, and performing multiple tests, both expressions appeared to be similarly efficient.
I was asking for my son, he does a lot of hardware type circuitry using different gates, and he was wondering the difference depending on the underlying hardware. Such as on the computers boards, and circuitry, which is using the least amount of âhardwareâ energy.
not (A and B)
is formed as a NAND gate.
NAND gate diagram:
A ---| |
| NAND |---C
B ---| |
Mathematically thatâs .
The computer evaluates both A and B before negating to produce the output.
But it gets slightly more complicated than this because Lua wonât even touch B if A is false. It will conclude the whole condition is false before negating. So it doesnât seem to truly fit NAND. Well, actually, itâs underlying C code doing this.
On the other hand, similar applies to not (A or B)
and not A or not B
(which are different expressions producing different outcomes). I guess that would mean it would be inaccurate to associate them with a single particular gate.
Considering the tests above, we could say the required hardware resources depend on the values of A and B.
not (A and B)
A | true | B | true |
â not (true and true) â not true â false
A | true | B | false |
â not (true and false) â not falseâ> true
A | false | B | false or true |
â (directly to) not false â true
Doesnât matter what B is. It wonât be considered.
=========================================
Looks like inverter gate (NOT gate) at this point.
What supports this is the almost identical execution time for not false
and not (false and true)
.
Short-circuit evaluation is just a performance optimization though, it doesnât change the logic, so itâs still always going to match the NAND truth table. In fact, in the actual transistor implementation of a PMOS NAND gate, the MOSFETs are in parallel, so either one can literally short-circuit the other, making the state of the other transistor irrelevant.
Hey, thanks for the reply, Emily, you really bend space (seen your many contributions before).
I am by no means on a very familiar ground with these hardware details. So if I understand correctly, you mean that not (A and B)
is always going to match the NAND truth table regardless of the lua implementation.
Just like not (A or B)
evaluation matches the NOR truth table, even though lua allows the evaluation to halt early when the result can be determined without fully evaluating both operands.
After consulting the Wiki, âPMOS or pMOS logic [âŚ] is a family of digital circuits based on p-channel, enhancement mode metalâoxideâsemiconductor field-effect transistors (MOSFETs)â (PMOS logic - Wikipedia). And PMOS NAND is responsible for not-and operations.
So the transistors are arranged in parallel in a way that allows short-circuit output because one transistor renders other transistors irrelevant regardless of their state.
Awkward and completely simplified picture:
@SelDraken this is amazing info. Itâs the hardware configuration itself allowing Lua and C to do this.
Thanks for the posts.
This is all a bit beyond me.
My son has been building circuits, and I am trying to introduce him to some programming.
So he has had questions that I never thought to even ask about how things are working behind the scenes with the language and the logic.
He said the post marked as solution is what most answered his questions about this.
He is in high school and already knows more than I have learned in the last 30 years of coding o.o
I appreciate all the answers, this has been very educational for me too, and given us a lot of things to research.
Thanks again everyone, I would mark you all as solved if I could.
Iâm glad we could help and that you guys found some use of my posts too.
Emily probably knows much more than me about hardware. Lua/C can fully evaluate the expression in some cases with only the first variable. NAND can be viewed as a small box, but the âparallel transistorsâ in those mentioned cases make NAND resemble the functionality of a NOT gate(?). So the operation is still is performed by PMOS NANDs, and what I said at the end of the reply marked as a solution is not entirely true. Sorry. Of course, the execution speed is still the same, but it doesnât involve different gates.
I learned a lot today too. Good luck with new discovery!
Itâs expected that you wonât be able to measure a real difference here. Just overhead of the for loop, which includes an integer comparison, is going to dominate over a few boolean operations. Both expressions also short-circuit when the first âAâ operand is False. In a totally naive, unoptimized implementation, not (A and B) will have fewer operations in the case where A is true (one AND, one invert), whereas (not A) or (not B) would have two inversions and one OR.
Personally, if youâre going to implement NAND, thereâs no reason not to just use not(A and B) since itâs immediately readable as NAND right from the operands, and itâs going to be at least as fast as the alternative.
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.