Mailing List Archive

constant folding - why not more
Dear all,

I would like to learn about constant folding optimisation in Python. It seems
to be implemented in Python/ast_opt.c. In order to get impression I used
python3 and dis module:

$ python3 -V
Python 3.7.3

Arithmetics expression is folded as expected:

>>> dis.dis(compile('1 * 2', filename='<string>', mode='eval',
>>> optimize=2))
1 0 LOAD_CONST 0 (2)
2 RETURN_VALUE

On the contrary, comparison remains for runtime:
>>> dis.dis(compile('1 < 2', filename='<string>', mode='eval',
>>> optimize=2))
1 0 LOAD_CONST 0 (1)
2 LOAD_CONST 1 (2)
4 COMPARE_OP 0 (<)
6 RETURN_VALUE
In function fold_unaryop (though comparison is a binary operation) in
Python/ast_opt.c is remark: /* Fold not into comparison */

Is there a reason why comparison (== != < > <= >=) is not folded? I would
expect it handled in fold_binop.

Besides comparison there are other expressions that might be optimized out
due to constant expression. Ternary operator with constant condition True
has IF optimized out (just some dead code remains):
>>> dis.dis(compile('"a" if True else "b"', filename='<string>',
>>> mode='eval', optimize=2))
1 0 LOAD_CONST 1 ('a')
2 RETURN_VALUE
4 LOAD_CONST 2 ('b')
6 RETURN_VALUE

On the contrary, the same ternary operator with constant condition False
still loads the constat and contains POP_JUMP_IF_FALSE:
>>> dis.dis(compile('"a" if False else "b"', filename='<string>',
>>> mode='eval', optimize=2))
1 0 LOAD_CONST 0 (False)
2 POP_JUMP_IF_FALSE 8
4 LOAD_CONST 1 ('a')
6 RETURN_VALUE
>> 8 LOAD_CONST 2 ('b')
10 RETURN_VALUE

Any ideas or links, please?

Best regards,
Davis
--
https://mail.python.org/mailman/listinfo/python-list
Re: constant folding - why not more [ In reply to ]
> On 10 Nov 2020, at 14:45, David Kolovratník <david@kolovratnik.net> wrote:
>
> Dear all,
>
> I would like to learn about constant folding optimisation in Python. It seems
> to be implemented in Python/ast_opt.c. In order to get impression I used
> python3 and dis module:
>
> $ python3 -V
> Python 3.7.3

I do not have answers to your questions, but I would suggest that you look at 3.9
or even 3.10a2 to see if this is still the case.

Barry

>
> Arithmetics expression is folded as expected:
>
>>>> dis.dis(compile('1 * 2', filename='<string>', mode='eval',
>>>> optimize=2))
> 1 0 LOAD_CONST 0 (2)
> 2 RETURN_VALUE
>
> On the contrary, comparison remains for runtime:
>>>> dis.dis(compile('1 < 2', filename='<string>', mode='eval',
>>>> optimize=2))
> 1 0 LOAD_CONST 0 (1)
> 2 LOAD_CONST 1 (2)
> 4 COMPARE_OP 0 (<)
> 6 RETURN_VALUE
> In function fold_unaryop (though comparison is a binary operation) in
> Python/ast_opt.c is remark: /* Fold not into comparison */
>
> Is there a reason why comparison (== != < > <= >=) is not folded? I would
> expect it handled in fold_binop.
>
> Besides comparison there are other expressions that might be optimized out
> due to constant expression. Ternary operator with constant condition True
> has IF optimized out (just some dead code remains):
>>>> dis.dis(compile('"a" if True else "b"', filename='<string>',
>>>> mode='eval', optimize=2))
> 1 0 LOAD_CONST 1 ('a')
> 2 RETURN_VALUE
> 4 LOAD_CONST 2 ('b')
> 6 RETURN_VALUE
>
> On the contrary, the same ternary operator with constant condition False
> still loads the constat and contains POP_JUMP_IF_FALSE:
>>>> dis.dis(compile('"a" if False else "b"', filename='<string>',
>>>> mode='eval', optimize=2))
> 1 0 LOAD_CONST 0 (False)
> 2 POP_JUMP_IF_FALSE 8
> 4 LOAD_CONST 1 ('a')
> 6 RETURN_VALUE
>>> 8 LOAD_CONST 2 ('b')
> 10 RETURN_VALUE
>
> Any ideas or links, please?
>
> Best regards,
> Davis
> --
> https://mail.python.org/mailman/listinfo/python-list
>

--
https://mail.python.org/mailman/listinfo/python-list
Re: constant folding - why not more [ In reply to ]
On 11/10/2020 1:03 PM, Barry Scott wrote:
>
>
>> On 10 Nov 2020, at 14:45, David Kolovratník <david@kolovratnik.net> wrote:
>>
>> Dear all,
>>
>> I would like to learn about constant folding optimisation in Python. It seems
>> to be implemented in Python/ast_opt.c. In order to get impression I used
>> python3 and dis module:
>>
>> $ python3 -V
>> Python 3.7.3
>
> I do not have answers to your questions, but I would suggest that you look at 3.9
> or even 3.10a2 to see if this is still the case.

Checking with 3.10...

>> Arithmetics expression is folded as expected:
>>
>>>>> dis.dis(compile('1 * 2', filename='<string>', mode='eval',
>>>>> optimize=2))
>> 1 0 LOAD_CONST 0 (2)
>> 2 RETURN_VALUE
>>
>> On the contrary, comparison remains for runtime:
>>>>> dis.dis(compile('1 < 2', filename='<string>', mode='eval',
>>>>> optimize=2))
>> 1 0 LOAD_CONST 0 (1)
>> 2 LOAD_CONST 1 (2)
>> 4 COMPARE_OP 0 (<)
>> 6 RETURN_VALUE

Same.

>> In function fold_unaryop (though comparison is a binary operation) in
>> Python/ast_opt.c is remark: /* Fold not into comparison */

Use git blame to find out who and when made that remark.

>> Is there a reason why comparison (== != < > <= >=) is not folded? I would
>> expect it handled in fold_binop.
>>
>> Besides comparison there are other expressions that might be optimized out
>> due to constant expression. Ternary operator with constant condition True
>> has IF optimized out (just some dead code remains):
>>>>> dis.dis(compile('"a" if True else "b"', filename='<string>',
>>>>> mode='eval', optimize=2))
>> 1 0 LOAD_CONST 1 ('a')
>> 2 RETURN_VALUE

Remains.

>> 4 LOAD_CONST 2 ('b')
>> 6 RETURN_VALUE

Gone.

>> On the contrary, the same ternary operator with constant condition False
>> still loads the constat and contains POP_JUMP_IF_FALSE:
>>>>> dis.dis(compile('"a" if False else "b"', filename='<string>',
>>>>> mode='eval', optimize=2))
>> 1 0 LOAD_CONST 0 (False)
>> 2 POP_JUMP_IF_FALSE 8
>> 4 LOAD_CONST 1 ('a')
>> 6 RETURN_VALUE
>> >> 8 LOAD_CONST 2 ('b')
>> 10 RETURN_VALUE

Same. You could file issue for this, but one guess is that else clause
might spill over to another line, and current decision is that for
tracing, every line of code that naively should be executed is executed
rather than optimized away. But I would first try to find the file that
produces bytecode and then use git blame to find issue where the True
else part was removed.

--
Terry Jan Reedy


--
https://mail.python.org/mailman/listinfo/python-list
Re: constant folding - why not more [ In reply to ]
On Wed, Nov 11, 2020 at 4:01 AM David Kolovratník <david@kolovratnik.net> wrote:
> On the contrary, comparison remains for runtime:
> >>> dis.dis(compile('1 < 2', filename='<string>', mode='eval',
> >>> optimize=2))
> 1 0 LOAD_CONST 0 (1)
> 2 LOAD_CONST 1 (2)
> 4 COMPARE_OP 0 (<)
> 6 RETURN_VALUE
> In function fold_unaryop (though comparison is a binary operation) in
> Python/ast_opt.c is remark: /* Fold not into comparison */
>
> Is there a reason why comparison (== != < > <= >=) is not folded? I would
> expect it handled in fold_binop.

I think that comment is unrelated. It's more about this:

>>> dis.dis(lambda x, y: x is y)
1 0 LOAD_FAST 0 (x)
2 LOAD_FAST 1 (y)
4 IS_OP 0
6 RETURN_VALUE
>>> dis.dis(lambda x, y: not (x is y))
1 0 LOAD_FAST 0 (x)
2 LOAD_FAST 1 (y)
4 IS_OP 1
6 RETURN_VALUE
>>> dis.dis(lambda x, y: x is not y)
1 0 LOAD_FAST 0 (x)
2 LOAD_FAST 1 (y)
4 IS_OP 1
6 RETURN_VALUE

The leading "not" gets folded into the operator, since there's
absolutely no way for "x is not y" and "not (x is y)" to return
different results. Same is true for "not (x in y)".

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: constant folding - why not more [ In reply to ]
>
> On the contrary, comparison remains for runtime:
> >>> dis.dis(compile('1 < 2', filename='<string>', mode='eval',
> >>> optimize=2))
> 1 0 LOAD_CONST 0 (1)
> 2 LOAD_CONST 1 (2)
> 4 COMPARE_OP 0 (<)
> 6 RETURN_VALUE
> In function fold_unaryop (though comparison is a binary operation) in
> Python/ast_opt.c is remark: /* Fold not into comparison */
>
> Is there a reason why comparison (== != < > <= >=) is not folded?
>

I can think of two reasons. One, this kind of comparison will almost never
appear in production code (maybe in unit tests?). Unlike the C family of
languages, Python doesn't have a macro processor which would give symbolic
names to numeric constants or string literals. Code generators might
conceivably generate constant comparisons, but they might be able to easily
do constant folding of comparisons themselves.

Two, given that this sort of construct will almost never be found in the
wild, folding constant comparisons in the compiler would increase the
maintenance burden of the compiler (just slightly, but still...) with no
clear benefit.

Skip

>
--
https://mail.python.org/mailman/listinfo/python-list
Re: constant folding - why not more [ In reply to ]
11.11.20 02:46, Skip Montanaro ????:
> I can think of two reasons. One, this kind of comparison will almost never
> appear in production code (maybe in unit tests?). Unlike the C family of
> languages, Python doesn't have a macro processor which would give symbolic
> names to numeric constants or string literals. Code generators might
> conceivably generate constant comparisons, but they might be able to easily
> do constant folding of comparisons themselves.
>
> Two, given that this sort of construct will almost never be found in the
> wild, folding constant comparisons in the compiler would increase the
> maintenance burden of the compiler (just slightly, but still...) with no
> clear benefit.

I concur with Skip. Expressions like 2**32-1 or 1/3 or b'A'[0] are
pretty common, so it makes sense to evaluate them at compile time. But
comparison and boolean operators are newer used with constants in
production code.

--
https://mail.python.org/mailman/listinfo/python-list