Branchless method to convert false/true to -1/+1? [closed] - python

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
What is a branchless way to do the following mapping?
true -> +1
false -> -1
An easy way would be if b then 1 else -1 but I'm looking for a method to avoid the branch, i.e. if.
If it is relevant, I'm using Python.

Here's a comparison of the solutions posted in comments and answers so far.
We can use the dis module to see the generated bytecode in each case; this confirms that there are no conditional jump instructions (in the Python code itself, at least), and also tells us something about the expected performance, since the number of opcodes executed has a direct impact on that (though they are not perfectly correlated). The number of function calls is also relevant for performance, since these have a particularly high overhead.
#Glannis Clipper and #kaya3: (-1, 1)[b] (3 opcodes)
1 0 LOAD_CONST 2 ((-1, 1))
3 LOAD_NAME 0 (b)
6 BINARY_SUBSCR
#HeapOverflow: -(-1)**b (4 opcodes)
1 0 LOAD_CONST 0 (-1)
2 LOAD_NAME 0 (b)
4 BINARY_POWER
6 UNARY_NEGATIVE
#HeapOverflow: b - (not b) (4 opcodes)
1 0 LOAD_NAME 0 (b)
2 LOAD_NAME 0 (b)
4 UNARY_NOT
6 BINARY_SUBTRACT
#kaya3: 2 * b - 1 (5 opcodes)
1 0 LOAD_CONST 0 (2)
3 LOAD_NAME 0 (b)
6 BINARY_MULTIPLY
7 LOAD_CONST 1 (1)
10 BINARY_SUBTRACT
#HeapOverflow: ~b ^ -b (5 opcodes)
1 0 LOAD_NAME 0 (b)
2 UNARY_INVERT
4 LOAD_NAME 0 (b)
6 UNARY_NEGATIVE
8 BINARY_XOR
#Mark Meyer: b - (b - 1) * -1 (7 opcodes)
1 0 LOAD_NAME 0 (b)
3 LOAD_NAME 0 (b)
6 LOAD_CONST 0 (1)
9 BINARY_SUBTRACT
10 LOAD_CONST 1 (-1)
13 BINARY_MULTIPLY
14 BINARY_SUBTRACT
#Sayse: {True: 1, False: -1}[b] (7 opcodes)
1 0 LOAD_CONST 0 (True)
3 LOAD_CONST 1 (1)
6 LOAD_CONST 2 (False)
9 LOAD_CONST 3 (-1)
12 BUILD_MAP 2
15 LOAD_NAME 0 (b)
18 BINARY_SUBSCR
#deceze: {True: 1}.get(b, -1) (7 opcodes, 1 function call)
1 0 LOAD_CONST 0 (True)
3 LOAD_CONST 1 (1)
6 BUILD_MAP 1
9 LOAD_ATTR 0 (get)
12 LOAD_NAME 1 (b)
15 LOAD_CONST 2 (-1)
18 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
#Glannis Clipper: [-1, 1][int(b)] (7 opcodes, 1 function call)
1 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 0 (1)
6 BUILD_LIST 2
9 LOAD_NAME 0 (int)
12 LOAD_NAME 1 (b)
15 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
18 BINARY_SUBSCR
#divyang4481: 2 * int(b) - 1 (7 opcodes, 1 function call)
1 0 LOAD_CONST 0 (2)
3 LOAD_NAME 0 (int)
6 LOAD_NAME 1 (b)
9 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
12 BINARY_MULTIPLY
13 LOAD_CONST 1 (1)
16 BINARY_SUBTRACT

You can exploit the fact that in Python, the type bool is numeric:
>>> True == 1
True
>>> False == 0
True
So the expression 2 * b - 1 gives the desired results:
>>> def without_branching(b):
... return 2 * b - 1
...
>>> without_branching(True)
1
>>> without_branching(False)
-1
However, it's arguable whether even this is really "branchless". It will be compiled to Python bytecode with no conditional jumps, but the bytecode interpreter will certainly do some conditional jumps in order to execute it: at the very least, it has to check which opcodes to execute, what types the operands of * and - have, and so on.

Maybe we can use a list in a way like this:
[None, True, False][1]
# output True
[None, True, False][-1]
# output False
UPDATE: And the opposite way as mentioned in comments:
[-1, 1][int(False)]
# output -1
[-1, 1][int(True)]
# output 1
UPDATE: Or even simpler with the use of a tuple and without the need of int() conversion (as mentioned in comments too):
(-1, 1)[False]
# output -1
(-1, 1)[True]
# output 1

Related

Line number of python Disassembler(dis)

I recently learn python.
I am wondering what is the line number for dis function.
import dis
def add(a, b):
a += 1
return a+b
dis.dis(add)
.
3 0 LOAD_FAST 0 (a)
2 LOAD_CONST 1 (1)
4 INPLACE_ADD
6 STORE_FAST 0 (a)
4 8 LOAD_FAST 0 (a)
10 LOAD_FAST 1 (b)
12 BINARY_ADD
14 RETURN_VALUE
There are 3 and 4.
Where is the line 1 and line 2 ?
With dis.dis(add) you disassembly only your function add. So there a only two lines. The function add have only two lines too.

Which is faster? if not x in OR if x not in [duplicate]

This question already has answers here:
"x not in" vs. "not x in" [duplicate]
(5 answers)
Closed 4 years ago.
if not 7 in [5, 6, 7]:
# something
if 7 not in [5, 6, 7]:
# something
Which is faster?
They are exactly the same, and thus take the same amount of time. not in is just syntactic sugar. Using the dis module, we can see that both result in the same bytecode:
>>> dis.dis("not x in y")
1 0 LOAD_NAME 0 (x)
2 LOAD_NAME 1 (y)
4 COMPARE_OP 7 (not in)
6 RETURN_VALUE
>>> dis.dis("x not in y")
1 0 LOAD_NAME 0 (x)
2 LOAD_NAME 1 (y)
4 COMPARE_OP 7 (not in)
6 RETURN_VALUE
Even adding parentheses as not (x in y) does not change that, unless, of course, you add more to the parentheses:
>>> dis.dis("not (x in y)")
1 0 LOAD_NAME 0 (x)
2 LOAD_NAME 1 (y)
4 COMPARE_OP 7 (not in)
6 RETURN_VALUE
>>> dis.dis("not (x in y or z)")
1 0 LOAD_NAME 0 (x)
2 LOAD_NAME 1 (y)
4 COMPARE_OP 6 (in)
6 JUMP_IF_TRUE_OR_POP 10
8 LOAD_NAME 2 (z)
>> 10 UNARY_NOT
12 RETURN_VALUE
Tested with both, Python 3.6.7 and 2.7.15.
It's exactly the same, there is no difference at all. The standard operator is in fact not in (see the docs), the not 7 in form will get automatically transformed to 7 not in.
So the recommended way is if 7 not in [5, 6, 7], that's a straight use of the operator and also has improved readability.

Can you simplify chained comparisons with not equal or equal in Python? [duplicate]

This question already has answers here:
Simplify Chained Comparison
(2 answers)
How do chained comparisons in Python actually work?
(1 answer)
Closed 4 years ago.
Probably some of you might think this is duplicate, and yes, I found a lot of examples similar to this:
But Pycharm says that I could simply this:
if y > x and x != -1:
# do something
And I did some searching and could not find something similar.
My question is it correct to simplify this function like this:
if y > x != -1:
# do something
And if so, is it safe and is there is any difference at all between this version and not simplified version except that it's shorter?
If it is not correct way to simplify it, what is then?
this is functionnaly equivalent, but when this:
10 < x < 40
is nice to read, mixing different operators to use chained comparisons isn't the best choice.
Is that really the same? let's disassemble to find out:
def f1(x,y):
if y > x and x != -1:
return 0
def f2(x,y):
if y > x != -1:
return 0
import dis
print("function 1")
dis.dis(f1)
print("function 2")
dis.dis(f2)
result:
function 1
2 0 LOAD_FAST 1 (y)
3 LOAD_FAST 0 (x)
6 COMPARE_OP 4 (>)
9 POP_JUMP_IF_FALSE 28
12 LOAD_FAST 0 (x)
15 LOAD_CONST 3 (-1)
18 COMPARE_OP 3 (!=)
21 POP_JUMP_IF_FALSE 28
3 24 LOAD_CONST 2 (0)
27 RETURN_VALUE
>> 28 LOAD_CONST 0 (None)
31 RETURN_VALUE
function 2
6 0 LOAD_FAST 1 (y)
3 LOAD_FAST 0 (x)
6 DUP_TOP
7 ROT_THREE
8 COMPARE_OP 4 (>)
11 JUMP_IF_FALSE_OR_POP 23
14 LOAD_CONST 3 (-1)
17 COMPARE_OP 3 (!=)
20 JUMP_FORWARD 2 (to 25)
>> 23 ROT_TWO
24 POP_TOP
>> 25 POP_JUMP_IF_FALSE 32
7 28 LOAD_CONST 2 (0)
31 RETURN_VALUE
>> 32 LOAD_CONST 0 (None)
35 RETURN_VALUE
>>>
Surprisingly they aren't the same, and the chained version has more instructions.
Not sure what's going on here (some took some more time to explain it better: How do chained comparisons in Python actually work?), but really I'd stick to the and version which shortcuts and is so much readable (think of future maintainers too...).
That said, one interesting thing about chained comparisons would be if the central argument is computed/takes a long time to compute/has a side effect in the computation and you don't want to store it in a variable:
if y > super_long_computation(x) != -1:
In that case, the central argument is only evaluated once. In the case of and, you'd have to store it beforehand.

Performance of assigning multiple return values from function call [duplicate]

This question already has answers here:
How can I return two values from a function in Python?
(8 answers)
Closed 4 years ago.
I'm wondering if there's a way to assign the outputs of a function to variables, while only having to run the function once. For example, say I have a function like this:
def my_func(x):
return x+1, x**2, x**3
And then I want to store each output as a variable, so normally I would code this afterwards:
var1 = my_func(2)[0]
var2 = my_func(2)[1]
var3 = my_func(2)[2]
But from my understanding, each line above requires the function to run again. If I have a longer function, it would be more convenient to assign them all at the same time, only running the function once. Is there a way to do this?
Two possibilities:
assign the function's return value (which is a tuple) to a variable, and access its elements:
var = my_func(2)
var1 = var[0]
var2 = var[1]
var3 = var[2]
or use unpacking:
var1, var2, var3 = my_func(2)
Re: Which method runs faster in most cases?
Let's compare the disassemble (I hilighted with > the differences):
dis.dis('t=my_func(2);d=t[0]+t[1]+t[2]')
1 0 LOAD_NAME 0 (my_func)
2 LOAD_CONST 0 (2)
4 CALL_FUNCTION 1
> 6 STORE_NAME 1 (t)
8 LOAD_NAME 1 (t)
> 10 LOAD_CONST 1 (0)
> 12 BINARY_SUBSCR
14 LOAD_NAME 1 (t)
> 16 LOAD_CONST 2 (1)
> 18 BINARY_SUBSCR
20 BINARY_ADD
22 LOAD_NAME 1 (t)
> 24 LOAD_CONST 0 (2)
> 26 BINARY_SUBSCR
28 BINARY_ADD
30 STORE_NAME 2 (d)
32 LOAD_CONST 3 (None)
34 RETURN_VALUE
dis.dis('a,b,c=my_func(2);d=a+b+c')
1 0 LOAD_NAME 0 (my_func)
2 LOAD_CONST 0 (2)
4 CALL_FUNCTION 1
> 6 UNPACK_SEQUENCE 3
> 8 STORE_NAME 1 (a)
> 10 STORE_NAME 2 (b)
> 12 STORE_NAME 3 (c)
14 LOAD_NAME 1 (a)
16 LOAD_NAME 2 (b)
18 BINARY_ADD
20 LOAD_NAME 3 (c)
22 BINARY_ADD
24 STORE_NAME 4 (d)
26 LOAD_CONST 1 (None)
28 RETURN_VALUE
So, the first approach takes two more instructions each time a variable is used (LOAD_CONST, BINARY_SUBSCR), while the second approach takes one more instruction when the variable is created.
The difference is practically meaningless.
Yes, you can do this via sequence unpacking:
def my_func(x):
return x+1, x**2, x**3
var1, var2, var3 = my_func(2)
This feature is described in the docs: Tuples and Sequences

using aliasing to iterate through list once (python)

Say I have a list called list, which is comprised of boolean values. Also say that I have some (valid) index i which is the index of list where I want to switch the value.
Currently, I have: list[i] = not list[i].
But my question is, doesn't this iterate through list twice? If so is there are way to setup a temp value through aliasing to only iterate through the list once?
I tried the following:
temp = list[i]
temp = not temp
But this has not worked for me, it has only switched the value of temp, and not the value of list[i].
you can look a little ways 'under the hood' using the dis module https://docs.python.org/3/library/dis.html
import dis
boolst = [True, True, True, True, True]
dis.dis('boolst[2] = not boolst[2]')
1 0 LOAD_NAME 0 (boolst)
2 LOAD_CONST 0 (2)
4 BINARY_SUBSCR
6 UNARY_NOT
8 LOAD_NAME 0 (boolst)
10 LOAD_CONST 0 (2)
12 STORE_SUBSCR
14 LOAD_CONST 1 (None)
16 RETURN_VALUE
dis.dis('boolst[2] ^= True')
1 0 LOAD_NAME 0 (boolst)
2 LOAD_CONST 0 (2)
4 DUP_TOP_TWO
6 BINARY_SUBSCR
8 LOAD_CONST 1 (True)
10 INPLACE_XOR
12 ROT_THREE
14 STORE_SUBSCR
16 LOAD_CONST 2 (None)
18 RETURN_VALUE

Categories

Resources