[Haskell-cafe] Re: Code and Perf. Data for Prime
Finders(was:Genuine Eratosthenes sieve)
oneill at cs.hmc.edu
Mon Feb 26 01:15:34 EST 2007
Claus, you're absolutely right that my paper could be improved in a
number of ways, and that there is actually a surprising amount of
meat on these bones for further analysis. We'll see what happens.
If it actually gets accepted for JFP, it'll undoubtedly finally
appear as a revised version with less controversial language. In
hindsight, using loaded words was a huge mistake, because of the way
it distracts from my main points.
But one thing I've learned is that it's better to do something
imperfect and get it out there for people to read than to try to make
it perfect and end up with a perpetual work in progress. I had a
busy summer last summer, and the paper was as good as I could make it
in the very short amount of time I had. It may have annoyed you in
some ways, but hopefully it informed you in others.
> something in me balks at the idea that the folkore version of the
> sieve is not "the" sieve *algorithm*, in spite of your detailed
> cross-off footprint and performance curve arguments.
I don't think you're alone in that viewpoint.
But it's clear to me that while some people are aware of the
differences in crossing-off behavior, the performance costs that
entails, and the way the ``folklore sieve'' becomes a dual of the
very-naive-primes algorithm (trial division by all the primes less
than n) -- and despite those differences, want to say it's all
"okay"; there also are plenty of people who are unaware of these
subtleties and believe that they have an honest-to-goodness
functional implementation of one of the fastest prime finding methods
out there, are hugely disappointed at the performance they get, and
ascribe it not to poor algorithmic choices, but to the language they
are working with.
As I allude to in my paper, I've talked to various people locally
who've seen the ``folklore sieve'', and it's fascinating to watch the
light go on for them, and hear exclamations like ``Oh! So *that's*
why it's so slow!''. (FWIW, I think it was those reactions, which
included for some a sense of having been duped, that lead me to feel
okay with the rather strong terms I used in my paper.)
Claus also wrote:
> but we are in the grey area between "permute until sorted" and
> "sort like this", and thinking of the modulus of a number as a
> static property (that might be derived incrementally from that of
> its predecessor) rather than an explicit check
I think modulus is a bit of a red herring -- its a symptom, not the
disease. I would argue that if "the agent that crosses off 17s" has
to look at 19 and say "that's okay, let that one pass through",
whether it passes it through based on modulus, or because it is less
than 289, it's still an examination of 19 by the cross-off-17 agent
where that agent has the power to allow 19 through or not. Seen
that way, I have a very hard time accepting the algorithm as a
faithful implementation of the Sieve of Eratosthenes. Similarly, I
have a hard time accepting the faithfulness an algorithm where 70
doesn't get a visit from the cross-off agents for 5 and 7.
But I agree though, that it may be possible to perform progressive
transformations from a ``folklore sieve'', which doesn't cross off
according to the proper conventions to something that does.
Somewhere along the way, there's going to be a grey area, and arguing
about definitions in that grey area is likely to be a waste of time.
At some point in the middle, you'll have something that can be viewed
from both perspectives. But the existence of such a progression of
algorithms between algorithm A and algorithm B doesn't mean that A
and B are fundamentally the same.
> as it happens, even if we run folklore3 over [2..], 70 will be
> crossed off
> exactly once, by the sieve for 2. the sieves for 5 and 7 run over
> the gap
> left behind, as they do in folklore and folklore2 (one could move that
> gap jumping from sieve3 to insert, though..). the sieves for 5 and
> 7 "know" about 70 the same way that (`mod`5) and (`mod`7) know
> about 70, but that knowledge isn't called upon for sieving, only to
> find greater numbers with modulus 0.
Perhaps we're saying the same thing and misunderstanding each other,
but here's what I see on an instrumented version of your code that
shows the state of affairs at each loop iteration:
(Prime 67,[(2,68),(3,69),(5,70),(7,70),(11,121), ...]),
(Loops 68,[(2,68),(3,69),(5,70),(7,70),(11,121), ...]),
(Loops 69,[(3,69),(2,70),(5,70),(7,70),(11,121), ...]),
(Loops 70,[(2,70),(5,70),(7,70),(3,72),(11,121), ...]),
(Loops 71,[(5,70),(7,70),(2,72),(3,72),(11,121), ...]),
(Loops 71,[(7,70),(2,72),(3,72),(5,75),(11,121), ...]),
(Prime 71,[(2,72),(3,72),(5,75),(7,77),(11,121), ...]),
(Loops 72,[(2,72),(3,72),(5,75),(7,77),(11,121), ...]),
(Loops 73,[(3,72),(2,74),(5,75),(7,77),(11,121), ...]),
(Prime 73,[(2,74),(3,75),(5,75),(7,77),(11,121), ...]),
(Loops 74,[(2,74),(3,75),(5,75),(7,77),(11,121), ...]),
(Loops 75,[(3,75),(5,75),(2,76),(7,77),(11,121), ...]),
(Loops 76,[(5,75),(2,76),(7,77),(3,78),(11,121), ...]),
(Loops 76,[(2,76),(7,77),(3,78),(5,80),(11,121), ...]),
(Loops 77,[(7,77),(2,78),(3,78),(5,80),(11,121), ...]),
(Loops 78,[(2,78),(3,78),(5,80),(7,84),(11,121), ...]),
(Loops 79,[(3,78),(2,80),(5,80),(7,84),(11,121), ...]),
(Prime 79,[(2,80),(5,80),(3,81),(7,84),(11,121), ...]),
You may say that 70 is crossed off once, but the fact that the other
two iterators for 70 move on when you're ``looking at'' 71 seems like
splitting hairs. There is work that exactly corresponds to the three
factors of 70. In the earlier versions, there is no such work -- 70
gets filtered out by the 2s and never seen again.
In any case, it looks to me like a genuine sieve (and like my cousin
of my technique) in a way that the earlier iterations do not.
Perhaps here's another way of putting it. A real Sieve of
Eratosthenes ought to be easy to augment to output ("*for free*") not
just a list of primes, but a list of primes and composites where we
show how to factor each of the composites -- for each composite c, we
get a list of all the unique factors of c <= sqrt(c). (Imagine
Eratosthenes not merely crossing out each multiple of 17 but
annotating it so that we know it was a multiple of 17).
Although it would take a little bit of post-processing, it's easy to
see how we could extract that information from the output above, and
hopefully fairly easy to see that we could modify your algorithm (or
mine) to output the information directly. The ``folklore sieves''
cannot do this without a heavy performance penalty.
From an operational behavior standpoint, the ``folklore sieve'' just
doesn't do the same thing. Every expectation people have about the
behavior of the Sieve of Eratosthenes is confounded by the ``folklore
sieve''. Although, if all you're looking for is a short and elegant-
looking *specification* that brings to mind the Sieve of
Eratosthenes, the ``folklore sieve'' will suit just fine. To me
though, the Sieve of Eratosthenes doesn't exist as a specification
for primality -- their are easier definitions -- it serves as a
clever way to find primes (and factor composites) efficiently. The
fundamental operational details matter.
P.S. Here's my instrumented version of Claus's code:
primes = folklore3  [2..]
data Status a = Prime a
| Loops a
deriving (Eq, Ord, Read, Show)
folklore3 pns xs = (mx,pns) : more mx
where (mx,pns',xs') = sieve3 pns xs
more (Prime x) = folklore3 (insert (x,x*x) pns') xs'
more (Loops x) = folklore3 pns' xs'
sieve3 ((p,n):pns) (x:xs)
| x<n = (Prime x, ((p,n):pns),xs)
| otherwise = (Loops x, insert (p,(n+p)) pns, [x|
sieve3  (x:xs) = (Prime x, ,xs)
insert (p,n)  = [(p,n)]
insert (p,n) ((p',n'):pns) | n<=n' = (p,n):(p',n'):pns
| otherwise = (p',n'):insert (p,n) pns
More information about the Haskell-Cafe