让我们考虑一下这种特殊情况,我希望传递一组对象的状态。为了方便和灵活(或者可能是任意),我选择使用二进制状态,然后使用按位或“|”连接在我传递它们之前:
status_a = 0b1
status_b = 0b10
status_c = 0b100
statuses_to_pass = status_a | status_c # 0b101
然后我意识到在这种情况下我也可以使用加法算术运算符“+”:
status_a | status_c == status_a + status_c
# 0b101 == 0b101 --> True
当然,当状态是2的正幂时,这是正确的;还有其他一些警告,如:
status_a | status_c | status_c == status_a + status_c + status_c
# 0b101 == 0b1001 --> False
但是我们假设我保持在限制范围内 - 是否有任何理由为什么按位运算符会优于算术运算符?在Python的引擎盖下有什么东西?哪一个更快?或者也许还有其他任何我没想到的副作用?
使用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
当我们进一步研究Python源代码时,我们注意到运算符调用了不同的函数。加法运算符调用binary_op()
,OR运算符调用binary_op1()
。
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;
}