Подтвердить что ты не робот

Является ли `scipy.misc.comb` быстрее, чем вычисление биномиального ad-hoc?

Является ли убедительным, что теперь scipy.misc.comb действительно быстрее, чем специальная реализация?

В соответствии со старым ответом Статистика: комбинации в Python эта функция доморощенного быстрее scipy.misc.comb при расчете комбинаций nCr:

def choose(n, k):
    """
    A fast way to calculate binomial coefficients by Andrew Dalke (contrib).
    """
    if 0 <= k <= n:
        ntok = 1
        ktok = 1
        for t in xrange(1, min(k, n - k) + 1):
            ntok *= n
            ktok *= t
            n -= 1
        return ntok // ktok
    else:
        return 0

Но после запуска некоторых тестов на моей машине это не похоже на этот случай, используя этот script:

from scipy.misc import comb
import random, time

def choose(n, k):
    """
    A fast way to calculate binomial coefficients by Andrew Dalke (contrib).
    """
    if 0 <= k <= n:
        ntok = 1
        ktok = 1
        for t in xrange(1, min(k, n - k) + 1):
            ntok *= n
            ktok *= t
            n -= 1
        return ntok // ktok
    else:
        return 0

def timing(f):
    def wrap(*args):
        time1 = time.time()
        ret = f(*args)
        time2 = time.time()
        print '%s function took %0.3f ms' % (f.__name__, (time2-time1)*1000.0)
        return ret
    return wrap

@timing
def test_func(combination_func, nk):
    for n,k in nk:
        combination_func(n, k)

nk = []
for _ in range(1000):
    n = int(random.random() * 10000)
    k = random.randint(0,n)
    nk.append((n,k))

test_func(comb, nk)
test_func(choose, nk)

Я получаю следующий вывод:

$ python test.py
/usr/lib/python2.7/dist-packages/scipy/misc/common.py:295: RuntimeWarning: overflow encountered in exp
  vals = exp(lgam(N+1) - lgam(N-k+1) - lgam(k+1))
999
test_func function took 32.869 ms
999
test_func function took 1859.125 ms

$ python test.py
/usr/lib/python2.7/dist-packages/scipy/misc/common.py:295: RuntimeWarning: overflow encountered in exp
  vals = exp(lgam(N+1) - lgam(N-k+1) - lgam(k+1))
999
test_func function took 32.265 ms
999
test_func function took 1878.550 ms

Прошел ли тест профилирования времени, что новый scipy.misc.comb быстрее, чем функция ad-hoc choose()? Есть ли какая-либо ошибка в моем тесте script, что делает время неточным?

Почему теперь scipy.misc.comb работает быстрее? Это связано с некоторыми трюками cython/c?


EDITED

После комментария @WarrenWeckesser:

Используя приближение с плавающей запятой по умолчанию при использовании scipy.misc.comb(), вычисление прерывается из-за переполнения с плавающей запятой.

(см. http://docs.scipy.org/doc/scipy-0.16.0/reference/generated/scipy.misc.comb.html для документации)

При тестировании с exact=True, который вычисляет с длинными целыми числами вместо плавающей запятой, используя приведенную ниже функцию, он намного медленнее при вычислении 1000 комбинаций:

@timing
def test_func(combination_func, nk):
    for i, (n,k) in enumerate(nk):
        combination_func(n, k, exact=True)

[выход]:

$ python test.py
test_func function took 3312.211 ms
test_func function took 1764.523 ms

$ python test.py
test_func function took 3320.198 ms
test_func function took 1782.280 ms
4b9b3361

Ответ 1

Ссылаясь на исходный код scipy.misc.comb, процедура обновления результата:

    val = 1
    for j in xrange(min(k, N-k)):
        val = (val*(N-j))//(j+1)
    return val

тогда как предложенная вами процедура обновления:

    ntok = 1
    ktok = 1
    for t in xrange(1, min(k, n - k) + 1):
        ntok *= n
        ktok *= t
        n -= 1
    return ntok // ktok

Мое предположение о том, почему реализация SciPy работает медленнее, объясняется тем, что подпрограмма включает в себя целочисленное деление на каждой итерации, в то время как ваша только вызывает деление один раз в операторе return.