Nimrod: ~ 38,667 (580,000,000 / 15,000)
Esta respuesta utiliza un enfoque bastante simple. El código emplea un tamiz simple de números primos que almacena el primo de la potencia prima más pequeña en cada ranura para números compuestos (cero para primos), luego usa programación dinámica para construir la función totient sobre el mismo rango, luego suma los resultados. El programa pasa prácticamente todo su tiempo construyendo el tamiz, luego calcula la función totient en una fracción del tiempo. Parece que se trata de construir un tamiz eficiente (con el ligero giro de que uno también tiene que poder extraer un factor primo para los números compuestos del resultado y mantener el uso de la memoria en un nivel razonable).
Actualización: rendimiento mejorado al reducir la huella de memoria y mejorar el comportamiento de la memoria caché. Es posible exprimir un 5% -10% más de rendimiento, pero el aumento en la complejidad del código no vale la pena. En última instancia, este algoritmo ejerce principalmente el cuello de botella de von Neumann de la CPU, y hay muy pocos ajustes algorítmicos que puedan solucionarlo.
También actualicé el divisor de rendimiento ya que el código C ++ no estaba destinado a ser compilado con todas las optimizaciones y nadie más lo hizo. :)
Actualización 2: operación de tamizado optimizada para un mejor acceso a la memoria. Ahora maneja primos pequeños a granel a través de memcpy () (~ 5% de aceleración) y omite múltiplos de 2, 3 y 5 cuando tamiza primos más grandes (~ 10% de aceleración).
Código C ++: 9.9 segundos (con g ++ 4.9)
Código Nimrod: 9.9 segundos (con -d: release, gcc 4.9 backend)
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)