Marc:
After many years, I finally understand what Percival, Crandall, Brandt, et al. are trying to tell me about how to do fft bignum multiplication, and I implemented it. Here are the timing results (in ms) on my 2GHz G5 comparing the code I sent you recently with the new code and mzscheme 360. Results that had an inordinate gc time are noted.
old new mzscheme
(expt 3 1000000) ; a 175 101 273 (expt 3 1000001) ; b 176 103 277 (* a a) ; c 204 116 478 (* a b) ; 302 165 667 (quotient c a) ; 1433 825 1670 (sqrt c) ; 1430 865 1176 (gcd a b) ; note 1 5385 3979 6759 (* a b) 145 84 191 (expt1 3 1000000) ; note 2 179 106 277 (expt2 3 1000000) ; note 3 763 433 957 (* a a) ; a=3^1000000 202 160 (48 gc) 473 (expt 10 10000000); a 450 227 492 (fib 10000) ; note 4 25 22 130 (89 gc) (factorial 10000) ; note 5 311 310 666 (486 gc) (partial-factorial 0 10000) ; note 6 29 19 30 (binary-splitting-compute-e 10000) ; note 7 1692 1469 1857 (binary-splitting-compute-pi 10000) ; note 9 2093 1820 2334 (pi-brent-salamin) ; n. 10, beta^k=10^100000 10325 (764 gc) 5918 (473 gc) 8051 (pi-brent-salamin) ; beta^k=2^332193 7703 (683 gc) 4497 (462 gc) 3985
MzScheme uses its own hybrid version (part of gmp 3, part gmp 4) of gmp without fft multiplication and some of the more-recently-added algorithms.
The new code uses less memory, so the number of gc's were reduced, too. We really need to improve our rational arithmetic if such a big improvement in basic large operations results in such a small improvement in code using rationals (binary-splitting-compute-e and binary-splitting-compute-pi). These times scale to arguments up to half a billion bits in size (after that, we slow down because we switch to Karatsuba multiply again because the floating-point error bounds get too bad).
Some timings for similar things from December 2003 can be found at
http://groups.google.com/group/comp.lang.scheme/msg/4306a6bd50004f9a
Note the different parameters for many of the timings.
I'm going to look at its packaging a bit more before sending it to you.
So now, instead of gambit taking about 5 times as long as gmp-4.2.1 for multiplication/division/sqrt and being a bit faster for gcd, gambit takes "only" a bit less than 3 times as long as gmp-4.2.1 for multiplication/division/sqrt and is noticeably faster for gcd. (That last bit will change with version 5 of gmp, which will use a faster version of gambit's algorithm that was written by Niels Möller of Sweden.) The basic fft code in _num.scm is about half as fast as, e.g., FFTW so this isn't too bad.
Brad
note 1:
a=(fib 1000000) b=(fib 1000001)
note 2:
(define (expt1 a b) (define (square x) (* x x)) (cond ((= b 0) 1) ((even? b) (square (expt1 a (quotient b 2)))) (else (* a (square (expt1 a (quotient b 2)))))))
note 3:
(define (expt2 a b) (define (square x) (* x x)) (cond ((= b 0) 1) ((even? b) (expt2 (square a) (quotient b 2))) (else (* a (expt2 (square a) (quotient b 2))))))
note 4:
(define (fib-ratio n) (if (= n 1) 1 (+ 1 (/ (fib-ratio (- n 1)))))) (define (fib n) (numerator (fib-ratio n)))
note 5:
(define (factorial n) (let loop ((i 1) (result 1)) (if (> i n) result (loop (+ i 1) (* i result))))) note 6:
(define (partial-factorial m n) ;; computes the product (m+1) * ... * (n-1) * n (if (< (- n m) 10) (do ((i (+ m 1) (+ i 1)) (result 1 (* result i))) ((> i n) result)) (* (partial-factorial m (quotient (+ m n) 2)) (partial-factorial (quotient (+ m n) 2) n))))
note 6:
(define (partial-factorial m n) ;; computes the product (m+1) * ... * (n-1) * n (if (< (- n m) 10) (do ((i (+ m 1) (+ i 1)) (result 1 (* result i))) ((> i n) result)) (* (partial-factorial m (quotient (+ m n) 2)) (partial-factorial (quotient (+ m n) 2) n))))
note 7:
(define (binary-splitting-partial-sum m n partial-term common-factor-ratio) ;; sums (partial) terms from m to n-1 ;; (partial-term n m) is the term at n with the common factors of terms >= m removed ;; (common-factor-ratio m n) is the ratio of the common factor of terms >= n divided by ;; the common factors of terms >= m (if (< (- n m) 10) (do ((i m (+ i 1)) (result 0 (+ result (partial-term m i)))) ((= i n) result)) (+ (binary-splitting-partial-sum m (quotient (+ m n) 2) partial-term common-factor-ratio) (* (common-factor-ratio m (quotient (+ m n) 2)) (binary-splitting-partial-sum (quotient (+ m n) 2) n partial-term common-factor-ratio)))))
(define (binary-splitting-sum n partial-term common-factor) (binary-splitting-partial-sum 0 n partial-term common-factor))
(define (binary-splitting-compute-e n) (binary-splitting-sum n (lambda (m n) (/ (partial-factorial m n))) (lambda (m n) (/ (partial-factorial m n)))))
note 9:
(define (binary-splitting-compute-atan n x) ;; here we just consider the common factor to be x^(2n+1) (* x ;; common factor for all terms (binary-splitting-sum n (lambda (m n) (/ (expt x (* 2 (- n m))) (* (if (odd? n) -1 1) ( + (* 2 n) 1)))) (lambda (m n) (expt x (* 2 (- n m)))))))
(define (binary-splitting-compute-pi n) (* 4 (- (* 4 (binary-splitting-compute-atan n 1/5)) (binary-splitting-compute-atan (quotient (* n 10) 34) 1/239))))
note 10:
(define (fixed.+ x y) (+ x y)) (define (fixed.- x y) (- x y)) (define (fixed.* x y) (quotient (* x y) beta^k)) (define (fixed.square x) (fixed.* x x)) (define (fixed./ x y) (quotient (* x beta^k) y)) (define (fixed.sqrt x) (##exact-int.sqrt (* x beta^k))) (define (number->fixed x) (round (* x beta^k))) (define (fixed->number x) (/ x beta^k))
(define (pi-brent-salamin) (let ((one (number->fixed 1))) (let loop ((a one) (b (fixed.sqrt (quotient one 2))) (t (quotient one 4)) (x 1)) (if (= a b) (fixed./ (fixed.square a) t) (let ((new-a (quotient (fixed.+ a b) 2))) (loop new-a (fixed.sqrt (fixed.* a b)) (fixed.- t (* x (fixed.square (fixed.- new-a a)))) (* 2 x)))))))
Afficher les réponses par date
Here's the new code. I tested it on an Opteron multiprocessor box, too. I also changed the parameters so fft multiplication won't be used until 20,000 bits (from 6800 bits), but fast-gcd will be used down to 1400 bits. Perhaps these are consequences of testing on 64- bit machines with 32-bit mdigits (so naive multiplication is four times as fast as on a 32-bit machines with 16-bit mdigits) and native 64-bit adigits (so the shifts needed in fast gcd are significantly faster than on a 32-bit machine).
Brad
I ran the tests on my opteron server (2.2 GHz, RHEL 4, gcc-4.1.2) after compiling mzscheme 370 on the box. Previously I used the prebuilt (presumably 32-bit) mzscheme binaries and native 64-bit gambit binaries on MacOS, but this build uses 64-bit operations in both the mzscheme and gambit libraries, and mzscheme looks better here. If mzscheme were to use the fft multiply routines in gmp it would look better still; with the current binaries of gsi/gsc and mzscheme, mzscheme would look worse for larger numbers.
Anyway, here are the results:
370 b22
(expt 3 1000000) ; a 124 89 (expt 3 1000001) ; b 126 89 (* a a) ; c 221 106 (* a b) ; 221 150 (quotient c a) ; 560 687 (sqrt c) ; 402 667 (gcd a b) ; note 1 1959 2708 (* a b) 62 62 (expt1 3 1000000) ; note 2 124 85 (expt2 3 1000000) ; note 3 362 370 (* a a) ; a=3^1000000 221 105 (expt 10 10000000); a 228 193 (fib 10000) ; note 4 33 29 (factorial 10000) ; note 5 137 158 (partial-factorial 0 10000) ; note 6 9 13 (binary-splitting-compute-e 10000) ; note 7 539 968 (binary-splitting-compute-pi 10000) ; note 9 716 1220 (pi-brent-salamin) ; n. 10, beta^k=10^100000 2646 3939 (pi-brent-salamin) ; beta^k=2^332193 1486 2889