按位或“ |”与加号“ +”在Python中为2的正幂

时间:2019-04-03 14:58:51

标签: python bitwise-operators

让我们考虑这种具体情况,在这种情况下,我想传递一组某些对象的状态。为了方便和灵活(或任意),我选择使用二进制状态,然后将这些状态使用按位或“ |”连接在我绕过它们之前:

status_a | status_c == status_a + status_c  
#             0b101 == 0b101  -->  True

然后我意识到在这种情况下,我也可以使用加法算术运算符“ +”:

status_a | status_c | status_c == status_a + status_c + status_c  
#                        0b101 == 0b1001  -->  False

当然,当状态为2的正幂时,这是正确的。还有其他一些警告,例如:

{{1}}

但是让我们假设我处于限制范围之内-是否有任何理由为什么按位运算符会比算术运算符更好? Python背后有东西吗?哪一个更快?也许还有其他我没有想到的副作用?

4 个答案:

答案 0 :(得分:4)

当我们进一步查看Python源代码时,我们注意到运算符调用了不同的函数。加法运算符调用binary_op1(),而OR运算符调用binary_op()

Python加法运算符(第955行)

PyObject *
PyNumber_Add(PyObject *v, PyObject *w)
{
    PyObject *result = binary_op1(v, w, NB_SLOT(nb_add));
    if (result == Py_NotImplemented) {
        PySequenceMethods *m = v->ob_type->tp_as_sequence;
        Py_DECREF(result);
        if (m && m->sq_concat) {
            return (*m->sq_concat)(v, w);
        }
        result = binop_type_error(v, w, "+");
    }
    return result;
}

Python OR运算符(第941行)

#define BINARY_FUNC(func, op, op_name) \
    PyObject * \
    func(PyObject *v, PyObject *w) { \
        return binary_op(v, w, NB_SLOT(op), op_name); \
    }

BINARY_FUNC(PyNumber_Or, nb_or, "|")

我们可能认为OR运算符会比加法运算符快,但是OR运算符有更多的代码要执行。在Python中,OR运算符比较慢,因为binary_op()调用binary_op1()

binary_op (第834行)

static PyObject *
binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
{
    PyObject *result = binary_op1(v, w, op_slot);
    if (result == Py_NotImplemented) {
        Py_DECREF(result);

        if (op_slot == NB_SLOT(nb_rshift) &&
            PyCFunction_Check(v) &&
            strcmp(((PyCFunctionObject *)v)->m_ml->ml_name, "print") == 0)
        {
            PyErr_Format(PyExc_TypeError,
                "unsupported operand type(s) for %.100s: "
                "'%.100s' and '%.100s'. Did you mean \"print(<message>, "
                "file=<output_stream>)\"?",
                op_name,
                v->ob_type->tp_name,
                w->ob_type->tp_name);
            return NULL;
        }

        return binop_type_error(v, w, op_name);
    }
    return result;
}

binary_op1 (第785行)

static PyObject *
binary_op1(PyObject *v, PyObject *w, const int op_slot)
{
    PyObject *x;
    binaryfunc slotv = NULL;
    binaryfunc slotw = NULL;

    if (v->ob_type->tp_as_number != NULL)
        slotv = NB_BINOP(v->ob_type->tp_as_number, op_slot);
    if (w->ob_type != v->ob_type &&
        w->ob_type->tp_as_number != NULL) {
        slotw = NB_BINOP(w->ob_type->tp_as_number, op_slot);
        if (slotw == slotv)
            slotw = NULL;
    }
    if (slotv) {
        if (slotw && PyType_IsSubtype(w->ob_type, v->ob_type)) {
            x = slotw(v, w);
            if (x != Py_NotImplemented)
                return x;
            Py_DECREF(x); /* can't do it */
            slotw = NULL;
        }
        x = slotv(v, w);
        if (x != Py_NotImplemented)
            return x;
        Py_DECREF(x); /* can't do it */
    }
    if (slotw) {
        x = slotw(v, w);
        if (x != Py_NotImplemented)
            return x;
        Py_DECREF(x); /* can't do it */
    }
    Py_RETURN_NOTIMPLEMENTED;
}

这些摘录属于abstract.c中的CPython project on GitHub

答案 1 :(得分:2)

从Python 3.6开始,您要使用Flag枚举:

from enum import Flag, auto

class Status(Flag):
    R = auto()
    W = auto()
    X = auto()
    FULL = R | W | X
    READONLY = R | X

a = Status.R
b = Status.W
c = Status.X

print(list(Status))
print(a,b,c)

perms1 = a | b | c
print(perms1)

# removing W permission
readonly_perms = perms1 & ~Status.W
print(readonly_perms == Status.READONLY)

print(Status.W in readonly_perms)

full_perms = readonly_perms | Status.FULL
print(full_perms == Status.FULL)

输出:

[<Status.R: 1>, <Status.W: 2>, <Status.X: 4>, <Status.FULL: 7>, <Status.READONLY: 5>]
Status.R Status.W Status.X
Status.FULL
True
True
True

检查Flag枚举文档here

对位操作数和位掩码使用非按位运算的一个很好的理由是,您可以轻松地更改其他位作为副作用,而无需注意。

答案 2 :(得分:1)

使用timeit进行的实验表明,在以下情况下添加速度更快:

import timeit
import statistics
times = {"+": [], "|": []}

for x in range(10):
    for y in range(x+1, 10):
        for op in "+|":
            t = timeit.timeit(stmt="x {} y".format(op), setup="x=2**{};y=2**{}".format(x, y))
            times[op].append(t)


statistics.mean(times["+"])  # 0.029464346377385986
statistics.mean(times["|"])  # 0.04432822428643703

答案 3 :(得分:1)

我一直在进行按位加法运算,以确定是否存在性能差异:

1)0-10的2的随机幂

# number of operations btw 1-100

import pandas as pd
import timeit
import numpy as np
from random import choice

scale = 100

df = pd.DataFrame({'num_operations' : np.arange(1, scale + 1), 'random_power_0_10': [[choice(range(10)) for _ in range(num_op)] for num_op in np.arange(1, scale + 1) ]})

df.head()

enter image description here

df['bitwise_timing'] = [timeit.timeit(stmt='reduce(lambda x, y: x | y, num)',
                                      setup=f'from functools import reduce;num={num}')
                        for num in ([2**e for e in pows] for pows in df.random_power_0_10)]

df['addition_timing'] = [timeit.timeit(stmt='reduce(lambda x, y: x + y, num)',
                                      setup=f'from functools import reduce;num={num}')
                        for num in ([2**e for e in pows] for pows in df.random_power_0_10)]

让我们绘制结果以查看差异

ax  = df.set_index('num_operations').plot(grid=True, title='Bitwise vs addition operation for random powers(0-10) of 2')
ax.set_ylabel('time in seconds')

enter image description here

df.describe()

enter image description here

平均加法时间似乎更好,但是由于差异很小,我们可以说加法和按位运算之间没有差异

2)10-100的2的随机次幂

得到以下图:

enter image description here

在这种情况下,我们可以说加法运算更好