Here’s the source code for the Wait Groups version. You can create the other version by using its parallel structure that’s shown.
My laptop is a Tuxedo Gemini 3, AMD Ryzen 9 7945HX, 16C|32T, 5.4GHz.
pairscnt is number of restwins values.
# Crystal >= 1.21
# Compile as: $ crystal build --release --mcpu native twinprimes_ssoz_wg.cr
# To reduce binary size do: $ strip twinprimes_ssoz_wg
# Single val: $ ./twinprimes_ssoz_wg val1
# Range vals: $ ./twinprimes_ssoz_wg val1 val2
# val1 and val2 can be entered as either: 123456789 or 123_456_789
require "wait_group"
def modinv(a0, m0)
return 1 if m0 == 1
a, m = a0, m0
x0, inv = 0, 1
while a > 1
inv &-= (a // m) &* x0
a, m = m, a % m
x0, inv = inv, x0
end
inv &+= m0 if inv < 0
inv
end
def gen_pg_parameters(prime)
puts "using Prime Generator parameters for P#{prime}"
primes = [2, 3, 5, 7, 11, 13, 17, 19, 23]
modpg, res_0 = 1, 0
primes.each { |prm| res_0 = prm; break if prm > prime; modpg &*= prm }
restwins = [] of Int32
inverses = Array.new(modpg + 2, 0)
rc, inc, res = 5, 2, 0
midmodpg = modpg >> 1
while rc < midmodpg
if rc.gcd(modpg) == 1
mc = modpg &- rc
inverses[rc] = modinv(rc, modpg)
inverses[mc] = modinv(mc, modpg)
restwins << rc << mc &+ 2 if res &+ 2 == rc
res = rc
end
rc &+= inc; inc ^= 0b110
end
restwins.sort!; restwins << (modpg + 1)
inverses[modpg + 1] = 1; inverses[modpg - 1] = modpg - 1
{modpg, res_0, restwins.size, restwins, inverses}
end
def set_sieve_parameters(start_num, end_num)
nrange = end_num - start_num
bn, pg = 0, 3
if end_num < 49
bn = 1; pg = 3
elsif nrange < 77_000_000
bn = 16; pg = 5
elsif nrange < 1_100_000_000
bn = 32; pg = 7
elsif nrange < 35_500_000_000
bn = 64; pg = 11
elsif nrange < 14_000_000_000_000
pg = 13
if nrange > 7_000_000_000_000; bn = 384
elsif nrange > 2_500_000_000_000; bn = 320
elsif nrange > 250_000_000_000; bn = 196
else bn = 128
end
elsif nrange < 480_000_000_000_000
bn = 448; pg = 17
else
bn = 640; pg = 19
end
modpg, res_0, pairscnt, restwins, resinvrs = gen_pg_parameters(pg)
kmin = (start_num-2) // modpg + 1
kmax = (end_num - 2) // modpg + 1
krange = kmax - kmin + 1
n = krange < 37_500_000_000_000 ? 12 : (krange < 975_000_000_000_000 ? 18 : 22)
b = bn * n * 1024
ks = krange < b ? krange : b
puts "segment size = #{ks.format} resgroups; seg array is [1 x #{(((ks-1) >> 6) + 1).format}] 64-bits"
maxpairs = krange * pairscnt
puts "twinprime candidates = #{maxpairs.format}; resgroups = #{krange.format}"
{modpg, res_0, ks, kmin, kmax, krange, pairscnt, restwins, resinvrs}
end
def sozp5(val, res_0, start_num, end_num)
md, rescnt = 30u64, 8
res = [7,11,13,17,19,23,29,31]
range_size = end_num - start_num
kmax = (val &- 2) // md &+ 1
prms = Array(UInt8).new(kmax, 0)
sqrtn = Math.isqrt(val-1|1)
k = sqrtn//md; resk = sqrtn-md*k; r=0
while resk >= res[r]; r &+= 1 end
pcs_to_sqrtn = k &* rescnt &+ r
pcs_to_sqrtn.times do |i|
k, r = i.divmod rescnt
next if prms[k] & (1 << r) != 0
prm_r = res[r]
prime = md &* k &+ prm_r
rem = start_num % prime
next unless (prime &- rem <= range_size) || rem == 0
res.each do |ri|
kn,rn = (prm_r &* ri &- 2).divmod md
bit_r = 1 << res.index(rn &+ 2).not_nil!
kpm = k &* (prime &+ ri) &+ kn
while kpm < kmax; prms[kpm] |= bit_r; kpm &+= prime end
end end
primes = [] of UInt64
res.each_with_index do |r_i, i|
kmax.times do |k|
if prms[k] & (1 << i) == 0
prime = md &* k &+ r_i
rem = start_num % prime
primes << prime if (res_0 <= prime <= val) && (prime &- rem <= range_size || rem == 0)
end end end
primes
end
def nextp_init(rhi, kmin, modpg, primes, resinvrs)
nextp = Slice(UInt64).new(primes.size*2)
r_hi, r_lo = rhi.to_u64, rhi.to_u64 &- 2
primes.each_with_index do |prime, j|
k = (prime &- 2) // modpg.to_u64
r = (prime &- 2) % modpg &+ 2
r_inv = resinvrs[r].to_u64
rl = (r_inv &* r_lo &- 2) % modpg &+ 2
rh = (r_inv &* r_hi &- 2) % modpg &+ 2
kl = (prime &+ rl) &* k &+ (rl &* r &- 2) // modpg # kl 1st mult resgroup
kh = (prime &+ rh) &* k &+ (rh &* r &- 2) // modpg # kh 1st mult resgroup
kl < kmin ? (kl = (kmin &- kl) % prime; kl = prime &- kl if kl > 0) : (kl &-= kmin)
kh < kmin ? (kh = (kmin &- kh) % prime; kh = prime &- kh if kh > 0) : (kh &-= kmin)
nextp[j << 1] = kl
nextp[j << 1 | 1] = kh
end
nextp
end
def twins_sieve(r_hi, kmin, kmax, ks, start_num, end_num, modpg, primes, resinvrs)
s = 6
bmask = (1 << s) &- 1
sum, ki, kn = 0_u64, kmin &- 1, ks
hi_tp, k_max = 0_u64, kmax
seg = Slice(UInt64).new(((ks - 1) >> s) &+ 1)
ki &+= 1 if r_hi &- 2 < (start_num &- 2) % modpg &+ 2
k_max &-= 1 if r_hi > (end_num &- 2) % modpg &+ 2
nextp = nextp_init(r_hi, ki, modpg, primes,resinvrs)
while ki < k_max
kn = k_max &- ki if ks > (k_max &- ki)
primes.each_with_index do |prime, j|
k1 = nextp.to_unsafe[j << 1]
while k1 < kn
seg.to_unsafe[k1 >> s] |= 1u64 << (k1 & bmask)
k1 &+= prime end
nextp.to_unsafe[j << 1] = k1 &- kn
k2 = nextp.to_unsafe[j << 1 | 1]
while k2 < kn
seg.to_unsafe[k2 >> s] |= 1u64 << (k2 & bmask)
k2 &+= prime end
nextp.to_unsafe[j << 1| 1] = k2 &- kn
end
seg.to_unsafe[(kn - 1) >> s] |= ~1u64 << ((kn &- 1) & bmask)
cnt = 0
seg[0..(kn - 1) >> s].each { |m| cnt &+= (~m).popcount }
if cnt > 0
sum &+= cnt
upk = kn &- 1
while seg.to_unsafe[upk >> s] & (1u64 << (upk & bmask)) != 0; upk &-= 1 end
hi_tp = ki &+ upk
end
ki &+= ks
seg.fill(0) if ki < k_max
end
hi_tp = (r_hi > end_num || sum == 0) ? 1u64 : hi_tp &* modpg &+ r_hi
{hi_tp, sum}
end
def twinprimes_ssoz()
end_num = {(ARGV[0].to_u64 underscore: true), 3u64}.max
start_num = ARGV.size > 1 ? {(ARGV[1].to_u64 underscore: true), 3u64}.max : 3u64
start_num, end_num = end_num, start_num if start_num > end_num
start_num |= 1
end_num = (end_num - 1) | 1
start_num = end_num = 7u64 if end_num - start_num < 2
puts "threads = #{System.cpu_count}"
ts = Time.instant
modpg, res_0, ks, kmin, kmax, krange, pairscnt, restwins, resinvrs = set_sieve_parameters(start_num, end_num)
primes = end_num < 49 ? [5u64] : sozp5(Math.isqrt(end_num), res_0, start_num, end_num)
puts "each of #{pairscnt.format} threads has nextp[2 x #{primes.size.format}] array"
te = (Time.instant - ts).total_seconds.round(6)
puts "setup time = #{te} secs"
puts "perform twinprimes ssoz sieve"
t1 = Time.instant
twinscnt = 0_u64
twinscnt += [3, 5, 11, 17].select { |tp| start_num <= tp < res_0 }.size if end_num > 3
cnts = Array(UInt64).new(pairscnt, 0)
lastwins = Array(UInt64).new(pairscnt, 0)
wg = WaitGroup.new(pairscnt)
threads = Fiber::ExecutionContext::Parallel.new("threads", System.cpu_count)
threadscnt = Atomic.new(0)
restwins.each_with_index do |r_hi, i|
threads.spawn do
lastwins[i], cnts[i] = twins_sieve(r_hi, kmin, kmax, ks, start_num, end_num, modpg, primes, resinvrs)
print "\r#{threadscnt.add(1)} of #{pairscnt} twinpairs done"
ensure
wg.done
end end
wg.wait
print "\r#{pairscnt} of #{pairscnt} twinpairs done"
last_twin = lastwins.max
twinscnt += cnts.sum
last_twin = 5 if end_num == 5 && twinscnt == 1
kn = krange % ks
kn = ks if kn == 0
t2 = (Time.instant - t1).total_seconds
puts "\nsieve time = #{t2.round(6)} secs"
puts "total time = #{(t2 + te).round(6)} secs"
puts "last segment = #{kn.format} resgroups; segment slices = #{((krange - 1)//ks + 1).format}"
puts "total twins = #{twinscnt.format}; last twin = #{last_twin.format}|-2"
end
twinprimes_ssoz
This freezes for input of 10^14.
➜ crystal-projects ./twinprimes_ssoz_wg 100_000_000_000_000
threads = 32
using Prime Generator parameters for P17
segment size = 5,505,024 resgroups; seg array is [1 x 86,016] 64-bits
twinprime candidates = 4,363,283,778,975; resgroups = 195,882,549
each of 22,275 threads has nextp[2 x 664,572] array
setup time = 0.023539 secs
perform twinprimes ssoz sieve
3 of 22275 twinpairs done^C
But works for 10x greater input of 10^15.
Try different values for system you run it on. Fewer threads, slower times.
➜ crystal-projects ./twinprimes_ssoz_wg 1_000_000_000_000_000
threads = 32
using Prime Generator parameters for P19
segment size = 7,864,320 resgroups; seg array is [1 x 122,880] 64-bits
twinprime candidates = 39,039,907,715,325; resgroups = 103,096,079
each of 378,675 threads has nextp[2 x 1,951,949] array
setup time = 0.186185 secs
perform twinprimes ssoz sieve
378675 of 378675 twinpairs done
sieve time = 9087.479603 secs
total time = 9087.665788 secs # 150m+|2.5h+
last segment = 859,919 resgroups; segment slices = 14
total twins = 1,177,209,242,304; last twin = 999,999,999,997,969|-2
I used htop to monitor operations.