Skip Menu |

This queue is for tickets about the Math-NumSeq CPAN distribution.

Report information
The Basics
Id: 83581
Status: resolved
Priority: 0/
Queue: Math-NumSeq

People
Owner: Nobody in particular
Requestors: DANAJ [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Normal
Broken in: 56
Fixed in: (no value)



Subject: Enhancement: faster Lucky Number sieve
The Lucky number sieve used in version 56 is extremely slow. time perl -MMath::NumSeq::LuckyNumbers -E 'my $seq = Math::NumSeq::LuckyNumbers->new; foreach my $i (1 .. 50_000) { my($i,$value) = $seq->next; }' takes 1m 17s. It also has no support for ith(). Attached is a version that takes 4.5s for the same range. Using ith($i) takes 5.2s. It is the sieve I used for computing Lucky primes in Math::Prime::Util, and regenerates the entire range every time we expand. It could be rewritten to just sieve the new range and would be much better for this use, but I thought I'd submit this as an example. The constants it uses for expansion could use tweaking also, but these seem to be working. The pathological case for this non-incremental code would be someone constantly making a new object, retrieving the first couple numbers, then throwing away the object. My notes from primes.pl: # First do a (very basic) lucky number sieve to generate A000959. # Evens removed for k=1: # my @lucky = map { $_*2+1 } (0 .. int(($end-1)/2)); # Remove the 3rd elements for k=2: # my @lucky = grep { my $m = $_ % 6; $m == 1 || $m == 3 } (0 .. $end); # Remove the 4th elements for k=3: # my @lucky = grep { my $m = $_ % 21; $m != 18 && $m != 19 } # grep { my $m = $_ % 6; $m == 1 || $m == 3 } # map { $_*2+1 } (0 .. int(($end-1)/2)); The code implements a more memory efficient version of the k=3 start.
Subject: lucky-code.pl
sub rewind { my ($self) = @_; $self->{'i'} = $self->i_start; $self->{'array'} = []; } sub next { my ($self) = @_; ### LuckyNumbers next(): "i=$self->{'i'}" my $i = $self->{'i'}; my $count = scalar @{$self->{'array'}}; if ($count == 0) { $self->{'array'} = [ _luckygen( 1, 5000 ) ]; } elsif ($i > $count) { my $last = $self->{'array'}->[-1]; my $newlast = $last + int(10000 * log($last)); $self->{'array'} = [ _luckygen( 1, $newlast ) ]; } return ($self->{'i'}++, $self->{'array'}->[$i-1]); } sub ith { my ($self, $i) = @_; my $count = scalar @{$self->{'array'}}; if ($i > $count) { my $est = $i * 1.6*log($i) + 10; $self->{'array'} = [ _luckygen( 1, $est ) ]; } return $self->{'array'}->[$i-1]; } sub _luckygen { my ($start, $end) = @_; my @lucky; my $n = 1; while ($n <= $end) { my $m21 = $n % 21; push @lucky, $n unless $m21 == 18 || $m21 == 19; push @lucky, $n+2 unless $m21 == 16 || $m21 == 17; $n += 6; } delete $lucky[-1] if $lucky[-1] > $end; for (my $k = 3; $k < scalar @lucky; $k++) { my $skip = $lucky[$k]; my $index = $skip-1; last if $index > $#lucky; do { splice(@lucky, $index, 1); $index += $skip-1; } while ($index <= $#lucky); } shift @lucky while $lucky[0] < $start; return @lucky; }
On Sat Feb 23 16:31:27 2013, DANAJ wrote: Show quoted text
> The Lucky number sieve used in version 56 is extremely slow.
Err, yes, a bit. I see I shouldn't keep all the generated values, only up to log or some such where a multiple would be cast out, and have a sub-sequence to generate at that point. Show quoted text
> Attached is a version that takes 4.5s for the same range. Using ith($i) > takes 5.2s.
I have tended not to write an ith() unless it can be better than what running next() up to that point would be. Perhaps for convenience I ought to make an ith() which does merely that, if there's no better way. Show quoted text
> It is the sieve I used for computing Lucky primes in Math::Prime::Util, > and regenerates the entire range every time we expand.
Ah yes. I'm surprised the repeated splice() array copying is still so much faster. Perhaps a little linked list would help yet further, or perhaps there's soon relatively few deletes. Show quoted text
> The pathological case for this non-incremental code would be someone > constantly making a new object, retrieving the first couple numbers, > then throwing away the object.
I've aimed to be progressive in the iterating, so that if just a few values are wanted then it doesn't do too much pre-calculation, but grow the block size to help when many values are wanted. I initially had some stuff where you could say a limit on the values wanted, but I quietly dropped that.
It took a while but version 63 is an improvement. It's still a bit slower than what you proposed, but it uses less memory. Some recursive trickery might let it use even less in the future.
On Fri Aug 30 21:12:39 2013, KRYDE wrote: Show quoted text
> It took a while but version 63 is an improvement. It's still a bit > slower than what you proposed, but it uses less memory. Some > recursive trickery might let it use even less in the future.
Verified the improvement. My sieve proposal as given certainly had flaws with use in an iterator -- intended more as a proof of concept. On this workstation, 1 to 50000: v61: 132.0s v63: 14.4s sieve: 7.3s Much nicer. Thanks!