On Jul 28, 2009, at 9:16 PM, Seth wrote:
>
> I found a simple, worthwhile improvement for a CPU bound
> implementation of the Sieve of Eratosthenes in Clojure and thought I'd
> share it. Also attached are a few metrics and code for the curious.
>
> So I'm using Clojure to solve some of the problems on Project Euler.
> I'd solved some of them before with other languages, but not in new
> lazy/immutable way Clojure is encouraging me to think.
>
> Anyways, my Sieve of Eratosthenes was too slow. It took about three
> minutes on a 2GHz Intel Core Duo MacBook Pro to find the primes lesser
> than 100,000. The blame's on me here -- I'm trying to do it the
> immutable/lazy/??? way rather than the big mutable array way. Patience
> is appreciated as I am still low on the learning curve.
A common problem is trying to do old things the new way. Functional
programming isn't just about immutability, it's a holistic approach,
and that often means new algorithms are in order. I ran into this
exact problem a couple years ago when I was learning Haskell and was
equally at a loss. In your particular case I think you might want to
take a look at the source code in the contrib project which implements
a lazy list of primes:
http://code.google.com/p/clojure-contrib/source/browse/trunk/src/clojure/contrib/lazy_seqs.clj
This is a simplified variation on the complex algorithm discussed in
"Lazy Wheel Sieves and Spirals of Primes":
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.55.7096&rep=rep1&type=pdf
The short answer is that the way to make algorithms spend less time in
the garbage collector is to spend less time creating garbage. :)
Unfortunately this advice (which I received a lot learning Haskell) is
never helpful. There's no substitute for a better algorithm, but it
was a long time before I ran into this algorithm. I'm sorry I don't
have great concrete advice for your particular implementation. It
doesn't look like a bad attempt to me, just not yet very functional. I
bet if you look at the lazy-seqs code above things will come together
a little better. Probably it would help to try and implement a lazy
list of the Fibonacci sequence before looking at that code, and then
maybe try some other mathematical sequences that are a little easier
to construct too.
Hope that helps a little,
—
Daniel Lyons
Hi Daniel,
> In your particular case I think you might want to take a look at the
> source code in the contrib project which implements a lazy list of
> primes:
>
> http://code.google.com/p/clojure-contrib/source/browse/trunk/src/clojure/contrib/lazy_seqs.clj
I came up with another approach on lazily calculating primes using the
Miller Rabin algorithm. Calculating the primes < 100.000 akes about 12
seconds on my 2.2 GHz dual core. Here it is:
--8<---------------cut here---------------start------------->8---
(ns de.tsdh.math.primes
(:use [clojure.contrib [math :only [expt]]
[test-is :only [deftest is]]
[def :only [defvar defvar-]]]))
(defvar *pseudo-accuracy* 10
"The chances that prime? returns true for a composite is (expt 4 (* -1
*pseudo-accuracy*)).")
(defn factorize-out
"Factorizes out all x factors from n.
Examples:
(factorize-out 10 2) ==> {:exponent 1, :rest 5}, because 2^1 * 5 = 10
(factorize-out 90 3) ==> {:exponent 2, :rest 10}, because 3^2 * 10 = 90"
[n x]
(loop [acc n exp 0]
(if (= 0 (mod acc x))
(recur (/ acc x) (inc exp))
(hash-map :exponent exp :rest acc))))
(defn expt-mod
"Equivalent to (mod (expt n e) m), but faster.
http://en.wikipedia.org/wiki/Modular_exponentiation#An_efficient_method:_the_right-to-left_binary_algorithm"
[n e m]
(loop [r 1, b n, e e]
(if (= e 0)
r
(recur (if (odd? e)
(mod (* r b) m)
r)
(mod (expt b 2) m)
(bit-shift-right e 1)))))
(defn prime?
"Checks if n is a prime using the Miller-Rabin pseudo-primality test. Also
see *pseudo-accuracy*."
[n]
(cond
(< n 2) false
(= n 2) true
(even? n) false
:else (let [m (factorize-out (dec n) 2)
d (:rest m)
s (:exponent m)]
(loop [k 1]
(if (> k *pseudo-accuracy*)
true
(let [a (+ 2 (rand-int (- n 4)))
x (expt-mod a d n)]
(if (or (= x 1) (= x (dec n)))
(recur (inc k))
(if (loop [r 1
x (expt-mod x 2 n)]
(cond
(or (= x 1) (> r (dec s))) false
(= x (dec n)) true
:else (recur (inc r) (mod (* x x) n))))
(recur (inc k))
false))))))))
(defn next-prime
"Returns the next prime greater than n."
[n]
(cond
(< n 2) 2
:else (loop [n (inc n)]
(if (prime? n)
n
(recur (inc n))))))
(defn primes
"Returns a lazy sequence of prime numbers"
[]
(iterate next-prime 2))
--8<---------------cut here---------------end--------------->8---
Bye,
Tassilo
Probably it would help to try and implement a lazy
list of the Fibonacci sequence before looking at that code, and then
maybe try some other mathematical sequences that are a little easier
to construct too.
Unfortunately, when running with
VM Arguments: jvm_args: -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -Xms256m -Xmx768m -XX:PermSize=256m -XX:MaxPermSize=768m -Djava.awt.headless=true … java_command: org.apache.catalina.startup.Bootstrap start
You get a SIGSEGV (which obviates the whole point of garbage-collection):
A fatal error has been detected by the Java Runtime Environment:SIGSEGV (0xb) at pc=0x0000000000e9c621, pid=13441, tid=114968912
JRE version: 6.0_14-b01 Java VM: OpenJDK 64-Bit Server VM (14.0-b10 mixed mode linux-amd64 ) Problematic frame: V [libjvm.so+0x296621]
The SIGSEGV happens because of new Garbage Collector, as invoked by "-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC". It dies after running for less than an hour:
Current thread (0x000000004149f000): ConcurrentGCThread [stack: 0x0000000000000000,0x0000000000000000] [id=13448]siginfo:si_signo=SIGSEGV: si_errno=0, si_code=1 (SEGV_MAPERR), si_addr=0x0000001600000050 … elapsed time: 2916 seconds
This issue doesn't occur with the default GC invoked with
jvm_args: -Xms256m -Xmx768m -XX:PermSize=256m -XX:MaxPermSize=768m -Djava.awt.headless=true"
I guess that's why this is an "Early Access" release.
On Wed, Jul 29, 2009 at 2:59 AM, Daniel Lyons <fus...@storytotell.org> wrote:Probably it would help to try and implement a lazy
list of the Fibonacci sequence before looking at that code, and then
maybe try some other mathematical sequences that are a little easier
to construct too.Using the super-lazy-seq macro Wrexsoul posted a month or so ago, that's dead easy:(super-lazy-seq [a 0 b 1] (next-item b b (+ a b)))
On the other hand,(defn fibs [] (cons 1 (cons 1 (lazy-seq (map + (fibs) (rest (fibs)))))))(def fibs (memoize fibs))