Sieving in the sense of the old Greek is about eliminating composites by crossing out all multiples of primes (striding additively), and the original SoE does not divide, ever. Trial division is something completely different, and for numbers approaching 2^64 it takes several million times as long as a segmented Sieve of Eratosthenes, even if you trial-divide only by primes.
The memory footprint can be reduced by sieving only the odd numbers, since the only even prime can be pulled out of thin air if need be.
Sieves are usually represented as packed bitmaps rather than one number per array cell, because otherwise memory consumption would be huge and things would be exceedingly slow. More compact forms of representations exist, which extend the 'odds-only' idea to dropping multiples of more small primes. They are called 'wheels', and one of the regulars here has written a brilliant explanation of them.
The famous mod 30 wheel drops 2, 3, and 5, and thus effectively stuffs 30 integers into a single byte. Packed odds-only bitmaps are much less complicated than such wheels though and good enough for many practical purposes.
The biggest speedup - almost an order of magnitude - comes from sieving in small blocks that fit into the L1 cache of the CPU, 32 KBytes or thereabouts. However, this makes division creep back into the code, because it is necessary for computing the start offset for each prime when processing a segment.
The division for recomputing the striding position for a prime for every new segment can be eliminated by remembering the last offsets for each prime from segment to segment (or rather the next offsets). That results in a further speedup.
The last optimisation is called 'presieving', which in practical terms means blasting the bit pattern for the composites of a bunch of small primes all over a bitmap before commencing normal operation at the first prime not in that bunch.
There are two variations: the general one blasts a segment immediately before commencing work on it, thus also warming the caches. The special one blasts the whole sieve in one go, and it is slightly faster overall given certain conditions. Perfect for initialising an auxiliary small factors sieve (up to 2^32) for a sieve that operates up to 2^64.
My pastebin has several standalone .cpp programs demonstrating these optimisations and their effects, so that you can gauge what each optimisation can buy you in terms of speed. Presieving might be slightly more difficult to do in python but all the other optimisations apply directly.
As a rough guide, sieving the full 32-bit range using the plain odds-only Eratosthenes takes 20 to 30 seconds on modern CPUs (single-threaded). If you apply all the optimisations then you can cut that to 2 seconds. The segmented sieve logic is also a precondition for parallel sieving.
EDIT: if you are interested in primes in general and large lists - or fast bulk generation - in particular then have a peek at my topic
Checksumming large swathes of prime numbers? (for verification). It mentions quite a few sources, up to 2^64 - 10 * 2^32 in fast bulk, from then on at a rate of about 1 million primes per minute (by having gp/PARI dump forprime()). primos.mat.br has lists up to 10^12, by the way.
EDIT: some resources on how to do Sieve of Eratosthenes in python and who to apply some of the optimisations:
The keys to speedy sieving in languages like python are two:
- efficient, compact representation of the sieve (sets, bool arrays, uint32/uintp arrays)
- pushing processing into the engine/runtime without incurring gratuitous inefficiencies (e.g. instead of division filter
where(n % p != 0)
, use additive striding like is_composite[p*p::p] = True
for a bool array)
Things like presieving pretty much require using uintp
arrays to be really effective but sets and boolean arrays are more interesting for exploring the logic of the Sieve vs. the capabilities of python, numpy and scipy.
Note: your use of the division filter turns your code into 100% bona fide trial division, and it would make the old Greek turn in his grave... ;-)
N = 20
the final value ofP
is[2, 3, 5, 7, 23, 43]
which is neither the list of primes up to 20 nor the list of the first 20 primes. \$\endgroup\$for M in range(1)
thenP
should return the prime numbers of up toN
. In this case, I was trying to compute primes up to3*N
, carrying over all the primes computed previously. \$\endgroup\$for M in range(1)
andN = 20
I getP = [2, 3, 5, 7]
. \$\endgroup\$