@ Ответ DavidW бьет по голове, вот еще несколько экспериментов и подробностей, которые подтверждают его ответ.
Вызов специальной функции, которая возвращает «Нет», быстро, независимо от того, сколькоаргументы:
%%cython
cdef class CyA:
# special functions
def __setitem__(self, index, val):
pass
def __getitem__(self, index):
pass
и теперь
a=CyA()
%timeit a[0] # 29.8 ns ± 1.9 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit a[0]=3 # 29.3 ns ± 0.942 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Сигнатура функций известна, нет необходимости строить *args
, **kwargs
.Поиск в слоте выполняется так быстро, как только может.
Затраты на вызов нормальной функции зависят от количества аргументов :
%%cython
cdef class CyA:
...
# normal functions:
def fun0(self):
pass
def fun1(self, arg):
pass
def fun2(self, arg1, arg2):
pass
и теперь:
a=CyA()
...
%timeit a.fun0() # 64.1 ns ± 2.49 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit a.fun1(1) # 67.6 ns ± 0.785 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit a.fun2(2,3) # 94.7 ns ± 1.04 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Издержки больше, чем для вызова методов из слотов, но также больше, если есть (как минимум) два аргумента (без учета self
): 65ns
против 95ns
.
Причина: cython-методы могут быть одного из следующих типов
METH_NOARGS
- только с аргументом self
METH_O
- только с self
+ один аргумент METH_VARARGS|METH_KEYWORDS
- с произвольным числом элементов
Метод fun2
относится к третьему типу, поэтому для его вызова Python должен создать список *args
, что приводит к дополнительным издержкам.
** Возврат из специального метода может иметь больше издержек, так какиз обычного метода ":
%%cython
cdef class CyA:
...
def __len__(self):
return 1 # return 1000 would be slightly slower
def len(self):
return 1
приводит к:
a=CyA()
...
%timeit len(a) # 52.1 ns ± 1.57 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit a.len() # 57.3 ns ± 1.39 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Как указывалось @DavidW, для __len__
в каждом вызове" новое "int-object должен быть сконструирован из возвращенного Py_ssize_t
(в случае 1
это целое число из пула, поэтому он на самом деле не создан - но это было в случае больших чисел).
Этоэто не относится к len()
: для этой специальной реализации Cython инициализирует глобальный объект, который возвращается len()
- увеличение счетчика ссылок не требует больших затрат (по сравнению с созданием целого числа!).
Таким образом, __len__
и len()
работают примерно одинаково быстро, но время тратится на разные вещи (создание целочисленных и поисковых запросов).