[alexandria-devel] Regarding %MULTIPLY-RANGE, BINOMIAL-COEFFICIENT
Robert Smith
quad at symbo1ics.com
Sat Jun 30 21:41:58 UTC 2012
Hello all.
I've been trying to speed up BINOMIAL-COEFFICIENT for combinatorial
calculations and ended up writing my own functions which were
originally based off of Alexandria's design. For computing the
binomial coefficient of (10^11, 10^5), Alexandria's takes about 9.990
seconds on my 64-bit Windows machine with a relatively recent SBCL
with (optimize speed (safety 0) (debug 0)).
I've ended up writing my own version of %MULTIPLY-RANGE, which I ended
up calling RANGE-PRODUCT, and it's definition is:
(declaim (ftype (function (integer integer) integer) range-product))
(defun range-product (lower upper)
"Compute LOWER * (LOWER+1) * ... * (UPPER-1) * UPPER."
(assert (<= lower upper))
(case (- upper lower)
((0) lower)
((1) (* lower upper))
(otherwise (let ((mid (floor (+ lower upper) 2)))
(* (range-product lower mid)
(range-product (1+ mid) upper))))))
[ from QTILITY:
https://bitbucket.org/tarballs_are_good/qtility/src/65eff10fdece/arithmetic.lisp#cl-43
]
>From that, the definition of FACTORIAL follows:
(declaim (ftype (function ((integer 0)) (integer 1))
factorial))
(defun factorial (n)
"Compute the factorial of N, N! = 1 * 2 * ... * N."
(if (zerop n)
1
(range-product 1 n)))
[ from QTILITY:
https://bitbucket.org/tarballs_are_good/qtility/src/65eff10fdece/arithmetic.lisp#cl-54
]
Then, I wrote BINOMIAL-COEFFICIENT that uses a closure to direct the
swapping of the K and N-K, instead of mutating them. This seemed to at
least help with SBCL's flow analysis. I also chose not to use
CHECK-TYPE in favor of declarations (which I understand are not the
same, but I think they should attempt to act the same given a
sufficient compiler policy).
(declaim (ftype (function ((integer 0) (integer 0)) (integer 0))
binomial-coefficient))
(defun binomial-coefficient (n k)
"Binomial coefficient of N and K."
(assert (>= n k))
(labels ((core (k n-k)
(if (= 1 n-k)
n
(nth-value 0 (floor (range-product (+ k 1) n)
(factorial n-k))))))
(declare (inline core)
(ftype (function ((integer 0) (integer 0)) (integer 0))
core))
(if (or (zerop k) (= n k))
1
(let ((n-k (- n k)))
(declare (type (integer 0) n-k))
(if (< k n-k)
(core n-k k)
(core k n-k))))))
[ from QTILITY:
https://bitbucket.org/tarballs_are_good/qtility/src/65eff10fdece/arithmetic.lisp#cl-62
]
With the code above, I can compute the same binomial coefficient in
about 7.503 seconds, so about a 2.5 second speed up.
I'd like to submit a patch using the code above, but I could see how
these speed ups are both implementation-dependent and
platform-dependent. A particular unfortunate consequence is that we
don't have those tweakable limits which (in theory; as the comments
say) could be computed at compile time.
Any thoughts?
-Robert
More information about the alexandria-devel
mailing list