Nimrod: ~ 38.667 (580.000.000 / 15.000)
Questa risposta utilizza un approccio piuttosto semplice. Il codice impiega un semplice setaccio di numeri primi che memorizza il numero primo della più piccola potenza principale in ogni slot per numeri compositi (zero per numeri primi), quindi utilizza la programmazione dinamica per costruire la funzione di totalità nello stesso intervallo, quindi somma i risultati. Il programma impiega praticamente tutto il suo tempo a costruire il setaccio, quindi calcola la funzione totient in una frazione del tempo. Sembra che si tratti di costruire un setaccio efficiente (con la leggera svolta che uno deve anche essere in grado di estrarre un fattore primo per i numeri compositi dal risultato e deve mantenere l'utilizzo della memoria a un livello ragionevole).
Aggiornamento: prestazioni migliorate riducendo il footprint di memoria e migliorando il comportamento della cache. È possibile aumentare del 5% -10% le prestazioni in più, ma l'aumento della complessità del codice non ne vale la pena. In definitiva, questo algoritmo esercita principalmente il collo di bottiglia di von Neumann sulla CPU, e ci sono pochissime modifiche algoritmiche che possono aggirare questo.
Anche aggiornato il divisore delle prestazioni poiché il codice C ++ non doveva essere compilato con tutte le ottimizzazioni e nessun altro lo ha fatto. :)
Aggiornamento 2: operazione di setaccio ottimizzata per un migliore accesso alla memoria. Ora gestendo piccoli numeri in blocco tramite memcpy () (~ 5% speedup) e saltando multipli di 2, 3 e 5 quando setacci numeri primi più grandi (~ 10% speedup).
Codice C ++: 9,9 secondi (con g ++ 4.9)
Codice Nimrod: 9,9 secondi (con -d: release, backend gcc 4.9)
proc handleSmallPrimes(sieve: var openarray[int32], m: int) =
# Small primes are handled as a special case through what is ideally
# the system's highly optimized memcpy() routine.
let k = 2*3*5*7*11*13*17
var sp = newSeq[int32](k div 2)
for i in [3,5,7,11,13,17]:
for j in countup(i, k, 2*i):
sp[j div 2] = int32(i)
for i in countup(0, sieve.high, len(sp)):
if i + len(sp) <= len(sieve):
copyMem(addr(sieve[i]), addr(sp[0]), sizeof(int32)*len(sp))
else:
copyMem(addr(sieve[i]), addr(sp[0]), sizeof(int32)*(len(sieve)-i))
# Fixing up the numbers for values that are actually prime.
for i in [3,5,7,11,13,17]:
sieve[i div 2] = 0
proc constructSieve(m: int): seq[int32] =
result = newSeq[int32](m div 2 + 1)
handleSmallPrimes(result, m)
var i = 19
# Having handled small primes, we only consider candidates for
# composite numbers that are relatively prime with 31. This cuts
# their number almost in half.
let steps = [ 1, 7, 11, 13, 17, 19, 23, 29, 31 ]
var isteps: array[8, int]
while i * i <= m:
if result[i div 2] == 0:
for j in 0..7: isteps[j] = i*(steps[j+1]-steps[j])
var k = 1 # second entry in "steps mod 30" list.
var j = 7*i
while j <= m:
result[j div 2] = int32(i)
j += isteps[k]
k = (k + 1) and 7 # "mod 30" list has eight elements.
i += 2
proc calculateAndSumTotients(sieve: var openarray[int32], n: int): int =
result = 1
for i in 2'i32..int32(n):
var tot: int32
if (i and 1) == 0:
var m = i div 2
var pp: int32 = 2
while (m and 1) == 0:
pp *= 2
m = m div 2
if m == 1:
tot = pp div 2
else:
tot = (pp div 2) * sieve[m div 2]
elif sieve[i div 2] == 0: # prime?
tot = i - 1
sieve[i div 2] = tot
else:
# find and extract the first prime power pp.
# It's relatively prime with i/pp.
var p = sieve[i div 2]
var m = i div p
var pp = p
while m mod p == 0 and m != p:
pp *= p
m = m div p
if m == p: # is i a prime power?
tot = pp*(p-1)
else:
tot = sieve[pp div 2] * sieve[m div 2]
sieve[i div 2] = tot
result += tot
proc main(n: int) =
var sieve = constructSieve(n)
let totSum = calculateAndSumTotients(sieve, n)
echo totSum
main(580_000_000)