Каковы конкретные правила сворачивания констант?

Я только что понял, что CPython, кажется, обрабатывает константные выражения, которые представляют одно и то же значение, по-разному в отношении свертывания констант. Например:

>>> import dis
>>> dis.dis('2**66')
  1           0 LOAD_CONST               0 (2)
              2 LOAD_CONST               1 (66)
              4 BINARY_POWER
              6 RETURN_VALUE
>>> dis.dis('4**33')
  1           0 LOAD_CONST               2 (73786976294838206464)
              2 RETURN_VALUE

Во втором примере применяется постоянное свертывание, а в первом нет, хотя оба представляют одно и то же значение. Кажется, это не связано ни со значением показателя степени, ни с величиной результата, поскольку следующие выражения также складываются:

>>> dis.dis('2.0**66')
  1           0 LOAD_CONST               2 (7.378697629483821e+19)
              2 RETURN_VALUE
>>> dis.dis('4**42')
  1           0 LOAD_CONST               2 (19342813113834066795298816)
              2 RETURN_VALUE

Почему первые два выражения обрабатываются по-разному и, в более общем смысле, каковы конкретные правила, которым следует CPython для свертывания констант?


Протестировано на:

$ python3.6 --version
Python 3.6.5 :: Anaconda, Inc.
$ python3.7 --version
Python 3.7.1

person a_guest    schedule 02.05.2019    source источник
comment
Правил нет. Есть только детали реализации. Они менялись раньше, и они изменятся снова.   -  person user2357112 supports Monica    schedule 02.05.2019
comment
Кроме того, я думаю, вы, возможно, забыли протестировать все свои примеры как на Python 3.6, так и на Python 3.7, потому что dis.dis('2**66') показывает свертывание констант в 3.6.   -  person user2357112 supports Monica    schedule 02.05.2019
comment
Подождите, нет, на самом деле это разница между различными уровнями исправления 3.6. Поведение изменилось между 3.6.4 и 3.6.5.   -  person user2357112 supports Monica    schedule 02.05.2019
comment
Я использую 3.7 в 64-разрядной версии Windows, а Python 3.7.0 не сворачивает dis.dis('2**66') (Python 3.7.0 (v3.7.0:1bf9cc5093, 27 июня 2018 г., 04:59:51) [MSC v.1914 64 бит (AMD64)] на win32) - в частности, кажется, что он складывает любую степень двойки с показателем степени 64 или меньше, но переключается на использование BINARY_POWER для любого показателя степени выше 64.   -  person Grismar    schedule 02.05.2019


Ответы (1)


Для свертывания констант нет правил. Есть только детали реализации. Они менялись раньше, и они изменятся снова.

Черт возьми, вы даже не можете говорить о «поведении Python 3» или «поведении Python 3.6», потому что эти детали реализации изменились между 3.6.4 и 3.6.5. В версии 3.6.4 пример 2**66 свёрнут с константами.

На данный момент, и никто не знает, как долго «пока» продлится, детали реализации заключаются в том, что оптимизатор AST включает меры безопасности, чтобы не тратить слишком много времени или памяти на свертывание констант. защита для 2**66 или 4**33 основана на количество битов в LHS и значение RHS:

if (PyLong_Check(v) && PyLong_Check(w) && Py_SIZE(v) && Py_SIZE(w) > 0) {
    size_t vbits = _PyLong_NumBits(v);
    size_t wbits = PyLong_AsSize_t(w);
    if (vbits == (size_t)-1 || wbits == (size_t)-1) {
        return NULL;
    }
    if (vbits > MAX_INT_SIZE / wbits) {
        return NULL;
    }
}

MAX_INT_SIZE — это #defined раньше, чем 128. Поскольку 2 — это 2-битное число, а 4 — 3-битное число, предполагаемый размер результата меньше для 4**33, поэтому он проходит проверку и складывается в константу.

В Python 3.6.5 детали реализации в основном схожи, но свертывание констант происходит в оптимизатор байт-кода вместо оптимизатора AST, которого нет в 3.6.5.

В Python 3.6.4 защита предварительной проверки не существует. Оптимизатор глазка отбрасывает слишком большую константу -свертывание результатов после их вычисления, что приводит к другим пороговым значениям, чем предварительные проверки.

person user2357112 supports Monica    schedule 02.05.2019
comment
Спасибо за ответ. Для всех, кто заинтересован, это соответствующая проблема, связанная с журнал изменений. Также стоит отметить, что аналогичные проверки существуют для умножения. , сдвиги влево и интерполяция строк. т.е. в основном все, что потенциально может неограниченно увеличиваться в размере (памяти). - person a_guest; 02.05.2019