I was making a 2D physics engine in python for fun. I use an object's velocity to determine if I should run the collision detection on it. I.e if it's velocity would cause it to be inside another object on the next cycle, then I run the collision detection on it.
Note: x and y velocities are always integers
So I realised that I would have to check that the object's x or y velocities are either less than or greater than (not) 0.
There are a lot of ways to do this. I came up with 4 different methods:
def cmp1 (a,b):
return a > 0 or a < 0 or b > 0 or b < 0
def cmp2 (a, b):
return a != 0 or b != 0
def cmp3 (a, b):
return abs(a | b) > 0
def cmp4 (a, b):
return a | b != 0
I like the 4th method because it has the fewest operations and reads well in English
a or b does not equal 0
But of the 4 methods, and of the benchmarking method I used, it has the 2nd slowest average time which surprised me. In fact, the first method is the fastest (again, using the benchmarking method I used).
Here is the method I used to work out the timings:
def randints(min, max, amount):
return tuple([random.randint(min, max) for i in range(amount)])
times = []
for i in range(1000):
n = randints(-9999, 9999, 2)
t = time.time() * 1000
cmp4(*n)
t2 = time.time() * 1000
times.append(t2-t)
print('Average Time: ', sum(times)/len(times))
If I run this multiple times for each method, I get very consistent results:
cmp1 - 0.00027s
cmp2 - 0.00032s
cmp3 - 0.00037s
cmp4 - 0.00035s
So my question is why is the 4th method so slow compared to the first? surely 2 operations (one bitwise, one comparison) should be faster than 4.
And, yes, I know its a difference of only a couple of microseconds.
There are better testing methods
y = [(random.randint(0,1), random.randint(0,1)) for _ in range(100_000)]
def test(cmp):
[cmp(a, b) for a, b in y]
%timeit test(cmp1)
20.1 ms ± 315 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit test(cmp2)
17.3 ms ± 212 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit test(cmp3)
29.3 ms ± 588 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit test(cmp4)
21.9 ms ± 289 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
The main advantage of or
is that it is short circuiting, effectively equivalent to:
def or(a, b):
if a: return a
return b
Whereas |
requires an actual (very low level) operation to be performed, then a new python integer object to be created and then another operation to be performed.
My test uses an array which is 50% zeros. If there was a larger or smaller percentage of of zeros the results may be quite different.
In any case, you forgot the fastest method of all (since all non-zero numbers are True
):
def cmp0(a, b):
return a or b
bool(cmp0(0, 0)), bool(cmp0(-12, 0)), bool(cmp0(3.14, 0))
# --> (False, True, True)
%timeit test(cmp0)
12.8 ms ± 191 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
However if you wanted to be as clear as possible (something at the heart of programming in python), I'd use a != 0 or b != 0
.