Below is the Nim direct translation of the C++ code in my paper. I have
verified it on Nim 0.17 on Linux. In comparison on this same machine the Nim
code runs a bit faster for some (larger >= 10^9) test values (not
comprehensibly benchmarked yet).
I'll post it to a gist later to make it easier to download. I'm now
re-architecting it to make it parallel friendlier (I hope). Feedback welcome on
style, and performance tips.
ssozp5.nim
#[
This Nim source file will compile to an executable program to
perform the Segmented Sieve of Zakiya (SSoZ) to find primes <= N.
It is based on the P5 Strictly Prime (SP) Prime Generator.
Prime Genrators have the form: mod*k + ri; ri -> {1,r1..mod-1}
The residues ri are integers coprime to mod, i.e. gcd(ri,mod) = 1
For P5, mod = 2*3*5 = 30 and the number of residues are
rescnt = (2-1)(3-1)(5-1) = 8, which are {1,7,11,13,17,19,23,29}.
Adjust segment byte length parameter B (usually L1|l2 cache size)
for optimum operational performance for cpu being used.
Verified on Nim 0.17, using clang (smaller exec) or gcc
$ nim c --cc:[clang|gcc] --d:release ssozp5.nim
Then run executable: $ ./ssozp5 <cr>, and enter value for N.
As coded, input values cover the range: 7 -- 2^64-1
Related code, papers, and tutorials, are downloadable here:
http://www.4shared.com/folder/TcMrUvTB/_online.html
Use of this code is free subject to acknowledgment of copyright.
Copyright (c) 2017 Jabari Zakiya -- jzakiya at gmail dot com
Version Date: 2017/08/21
This code is provided under the terms of the
GNU General Public License Version 3, GPLv3, or greater.
License copy/terms are here: http://www.gnu.org/licenses/
]#
import math
import strutils, typetraits
let pbits = [
8,7,7,6,7,6,6,5,7,6,6,5,6,5,5,4,7,6,6,5,6,5,5,4,6,5,5,4,5,4,4,3
,7,6,6,5,6,5,5,4,6,5,5,4,5,4,4,3,6,5,5,4,5,4,4,3,5,4,4,3,4,3,3,2
,7,6,6,5,6,5,5,4,6,5,5,4,5,4,4,3,6,5,5,4,5,4,4,3,5,4,4,3,4,3,3,2
,6,5,5,4,5,4,4,3,5,4,4,3,4,3,3,2,5,4,4,3,4,3,3,2,4,3,3,2,3,2,2,1
,7,6,6,5,6,5,5,4,6,5,5,4,5,4,4,3,6,5,5,4,5,4,4,3,5,4,4,3,4,3,3,2
,6,5,5,4,5,4,4,3,5,4,4,3,4,3,3,2,5,4,4,3,4,3,3,2,4,3,3,2,3,2,2,1
,6,5,5,4,5,4,4,3,5,4,4,3,4,3,3,2,5,4,4,3,4,3,3,2,4,3,3,2,3,2,2,1
,5,4,4,3,4,3,3,2,4,3,3,2,3,2,2,1,4,3,3,2,3,2,2,1,3,2,2,1,2,1,1,0
]
let residues = [1, 7, 11, 13, 17, 19, 23, 29, 31]
# Global parameters
const
modp5 = 30 # prime generator mod value
rescnt = 8 # number of residues for prime generator
var
pcnt = 0 # number of primes from r1..sqrt(N)
primecnt = 0'u64 # number of primes <= N
next: seq[uint64] # array of primes first multiples
primes: seq[int] # array of primes <= sqrt(N)
seg: seq[uint8] # seg[B] segment byte array
proc sozp7(val: int | int64) =
let md = 210
let rscnt = 48
let res = [1, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67
, 71, 73, 79, 83, 89, 97,101,103,107,109,113,121,127,131,137,139
,143,149,151,157,163,167,169,173,179,181,187,191,193,197,199,209,211]
var posn = newSeq[int](210)
for i in 0 .. <rscnt: posn[res[i]] = i-1
let num = (val-1) or 1 # if value even number then subtract 1
var k = num div md # kth residue group
var modk = md * k # base num value
var r = 1 # starting with first residue
while num >= modk+res[r]: r += 1 # find last pc position <= num
let maxprms = k*rscnt + r - 1 # maximum number of prime candidates
var prms = newSeq[bool](maxprms) # array of prime candidates set False
let sqrtN = int(sqrt float64(num))
modk=0; r=0; k=0
# sieve to eliminate nonprimes from small primes prms array
for prm in prms:
r += 1; if r > rscnt: (r = 1; modk += md; k += 1)
if prm: continue
let res_r = res[r]
let prime = modk + res_r
if prime > sqrtN: break
let prmstep = prime * rscnt
for ri in res[1..rscnt]:
let prod = res_r * ri # residues cross-product
var nonprm = (k*(prime + ri) + prod div md)*rscnt + posn[prod mod md]
while nonprm < maxprms: prms[nonprm] = true; nonprm += prmstep
# the prms array now has all the positions for primes r1..N
# extract prime numbers and count from prms into primes array
primes = @[7] # r1 prime for P5
modk = 0; r = 0
for prm in prms:
r += 1; if r > rscnt: (r = 1; modk += md)
if not prm: primes.add(modk + res[r])
pcnt = len(primes)
proc next_init() =
var pos = newSeq[int](modp5)
for i in 0 .. <rescnt: pos[residues[i]] = i-1
pos[1] = rescnt-1
# for each prime store resgroup on each restrack for prime*(modk+ri)
for j, prime in primes:
let k = uint((prime-2)) div uint(modp5) # find the resgroup it's
in
let r = uint((prime-2)) mod uint(modp5) + 2 # its base residue value
for ri in residues[1 .. rescnt]: # for each residue value
let prod: int = int(r) * ri # create residues
cross-product r*ri
let row: int = pos[prod mod modp5] * pcnt # create residue track
address
next[row + j] = k*(uint(prime) + uint(ri)) + uint(prod-2) div
uint(modp5)
proc segsieve(Kn: int) =
# for Kn resgroups in segment
for b in 0 .. <Kn: # for every byte in the segment
seg[b] = 0 # set every byte bit to prime (0)
for r in 0 .. <rescnt: # for each ith (of 8) residues for P5
let biti = uint8(1 shl r) # set the ith residue track bit mask
let row = r*pcnt # set address to ith row in next[]
for j in 0 .. <pcnt: # for each prime <= sqrt(N) for
restrack
if next[row+j] < uint(Kn): # if 1st mult resgroup index <= seg
size
var k: int = int(next[row+j]) # get its resgroup value
let prime = primes[j] # get the prime
while k < Kn: # for each primenth byte < segment
size
seg[k] = seg[k] or biti # set ith residue in byte as nonprime
k += int(prime)
next[row+j] = uint(k - Kn) # 1st resgroup in next eligible
segment
else: next[row+j] -= uint(Kn) # if 1st mult resgroup index > seg
size
# count the primes in the segment
for byt in seg[0..<Kn]: # for the Kn resgroup bytes
primecnt += uint(pbits[byt]) # count the '0' bits as primes
proc printprms(Kn: int, Ki: uint64) =
# Extract and print the primes in each segment:
# recommend piping output to less: ./ssozpxxx | less
# can then use Home, End, Pgup, Pgdn keys to see primes
for k in 0 .. <Kn: # for Kn residues groups|bytes
for r in 0 .. <8: # for each residue|bit in a byte
if (int(seg[k]) and (1 shl r)) == 0: # if it's '0' it's a prime
write(stdout, " ", uint64(modp5)*(Ki+uint64(k)) +
uint64(residues[r+1]))
#echo "\n"
proc ssozp5() =
stdout.write "Enter integer number: "
let val = stdin.readline.parseBiggestUInt # find primes <= val (7..2^64-1)
let B = 262144 # L2D_CACHE_SIZE 256*1024 bytes
let KB = B # number of segment resgroups
seg = newSeq[uint8](B) # create segment array of B bytes size
writeLine(stdout, "segment has ", B, " bytes and ", KB, " residues
groups")
let num = uint64((val-1) or 1) # if val even subtract 1
var k: uint64 = num div modp5 # resgroup of num
var modk = uint64(modp5) * k # base value of num
var r = 1 # starting with first residue
while num >= modk+uint64(residues[r]): r += 1 # find last pc position <=
num
let maxpcs = k * uint64(rescnt) + uint64(r-1) # maximum number of prime
candidates
let Kmax = uint64(num-2) div uint64(modp5) + 1 # maximum number of
resgroups for val
writeLine(stdout, "prime candidates = ", maxpcs, "; resgroups = ", Kmax)
let sqrtN = int(sqrt float64(num))
sozP7(sqrtN) # get pcnt and primes <= sqrt(nun)
writeLine(stdout, "create next[", rescnt, "x", pcnt, "] array")
next = newSeq[uint64](rescnt*pcnt) # create the next[] array
next_init() # load with first nonprimes resgroups
primecnt = 3 # 2,3,5 the P5 excluded primes count
var Kn: int = KB # set sieve resgroups size to segment
size
var Ki = 0'u64 # starting resgroup index for each
segment
echo "perform segmented SoZ\n";
while Ki < Kmax: # for KB size resgroup slices up to Kmax
if Kmax-Ki < uint64(KB): Kn = int(Kmax-Ki) # set sieve resgroups size
for last segment
segsieve(Kn) # sieve primes for current segment
#printprms(Kn,Ki) # print primes for the segment (optional)
Ki += uint64(KB) # next segment slice
var lprime = 0'u64 # get last prime and primecnt <= val
modk = uint64(modp5)*(Kmax-1) # mod for last resgroup in last segment
var b = Kn-1 # num bytes to last resgroup in segment
r = rescnt-1 # from last restrack in resgroup
while true: # repeat until last prime determined
if (int(seg[b]) and (1 shl r)) == 0:
lprime = modk+uint64(residues[r+1]) # determine the prime value
if lprime <= num: break # if <= num exit from loop with lprime
primecnt -= 1 # else reduce total primecnt
# reduce restrack, setup next resgroup
r -= 1; if r < 0: (r = rescnt-1; modk -= modp5; b -= 1) # if necessary
writeLine(stdout, "last segment = ", Kn, " resgroups; segments iterations
= ", Ki div uint64(KB))
writeLine(stdout, "last prime (of ", primecnt, ") is ", lprime)
ssozp5()