Calcule el número de primos hasta n


64

π ( n ) es el número de primos menores o iguales que n .

Entrada: un número natural, n .

Salida: π (n).

Puntuación: este es un desafío de . La puntuación será la suma de veces para los casos de puntuación. Mediré cada entrada en mi computadora.

Reglas y detalles

  • Su código debería funcionar para n hasta 2 mil millones (2,000,000,000).

  • Los elementos integrados que trivializan esto no están permitidos. Esto incluye funciones π incorporadas o listas de valores para π ( n ).

  • Los elementos integrados que prueban la primalidad o generan primos no están permitidos. Esto incluye listas de números primos, que no se pueden buscar externamente o codificados localmente, excepto con respecto al siguiente punto.

  • Puede codificar primos de hasta 19 inclusive y no superiores.

  • Su implementación de π debe ser determinista. Esto significa que dado un n específico , su código debería ejecutarse (aproximadamente) la misma cantidad de tiempo.

  • Los idiomas utilizados deben estar disponibles gratuitamente en Linux (Centos 7). Se deben incluir instrucciones sobre cómo ejecutar su código. Incluya los detalles del compilador / intérprete si es necesario.

  • Los horarios oficiales serán de mi computadora.

  • Cuando publique, incluya un tiempo automedido en algunos / todos los casos de prueba / puntaje, solo para darme una estimación de qué tan rápido se está ejecutando su código.

  • Los envíos deben caber en una respuesta a esta pregunta.

  • Estoy ejecutando centos de 64 bits7. Solo tengo 8 GB de RAM y 1 GB de intercambio. El modelo de CPU es: AMD FX (tm) -6300 Procesador de seis núcleos.

Casos de prueba ( fuente ):

Input        Output
90           24
3000         430
9000         1117
4000000      283146           <--- input = 4*10^6
800000000    41146179         <--- input = 9*10^8
1100000000   55662470         <--- input = 1.1*10^9

Casos de puntuación ( misma fuente )

Como de costumbre, estos casos están sujetos a cambios. La optimización para los casos de puntuación no está permitida. También puedo cambiar el número de casos en un esfuerzo por equilibrar tiempos de ejecución razonables y resultados precisos.

Input        Output
1907000000   93875448         <--- input = 1.907*10^9
1337000000   66990613         <--- input = 1.337*10^9
1240000000   62366021         <--- input = 1.24*10^9
660000000    34286170         <--- input = 6.6*10^8
99820000     5751639          <--- input = 9.982*10^7
40550000     2465109          <--- input = 4.055*10^7
24850000     1557132          <--- input = 2.485*10^7
41500        4339

Duración

Dado que este es un desafío de y las entradas se deben ejecutar en mi computadora, me reservo el derecho de detener el cronometraje de entradas después de 2 semanas. Después de este punto, las entradas aún se aceptan, pero no hay garantía de que estén oficialmente programadas.

Dicho esto, no espero demasiadas respuestas a este desafío y probablemente continuaré cronometrando nuevas respuestas indefinidamente.

Detalles de puntuación

Programaba las entradas más rápidas con el siguiente script:

#!/bin/bash

a=(1907000000 1337000000 1240000000 660000000 99820000 40550000 24850000 41500)

echo DennisC
exec 2>> times/dennisc.txt
time for j in ${a[@]}; do ./dennisc $j; done >> /dev/null;

echo DennisPy
exec 2>> times/dennispy.txt
time for j in ${a[@]}; do pypy dennispy.py <<< $j; done >> /dev/null;

echo arjandelumens
exec 2>> times/arjandelumens.txt
time for j in ${a[@]}; do ./arjandelumens $j; done >> /dev/null;

echo orlp
exec 2>> times/orlp.txt
time for j in ${a[@]}; do ./orlp $j; done >> /dev/null;

# echo mwr247
# time node-v4.3.1-linux-x64/bin/node mwr247.js

# mwr247 using js seems a bit longer, so I am going to run the fastest
# and then come back to his. 

# mwr247 provided a function, so I appended
# console.log( F( <argument> ) )
# to his code, for each argument.

timeescribe en stderr, así que envié stderra un archivo de registro utilizando exec 2 >> <filename>. Puede notar que stdoutse envía a /dev/null. Esto no es un problema, porque ya verifiqué que los programas estaban produciendo la salida correcta.

Ejecuté el timeall.shscript anterior 10 veces usandofor i in {1..10}; do ./timeall.sh; done;

Luego promedié el real timepuntaje de cada entrada.

Tenga en cuenta que no se estaban ejecutando otros programas en mi computadora durante el tiempo.

Además, los horarios oficiales se han agregado a cada entrada. Por favor revise su propio promedio.


¿Qué nos impide usar una tabla de búsqueda con los primeros valores 2e9 de pi (n)? ¿Sería eso aceptable? (Sin embargo, no estoy seguro de lo rápido que sería, porque sería una mesa grande)
Luis Mendo

@DonMuesli Eso no sería aceptable (va en contra del espíritu del desafío), he editado para que también esté expresamente prohibido ahora.
Liam

8
Es peligroso referirse al "espíritu" del desafío. Tu "contra el espíritu" puede ser el "gran truco" de otra persona :-) Es mejor que lo hayas hecho explícito
Luis Mendo

1
¿Qué es un incorporado? Tengo una función de lista de primos en una biblioteca. ¿Puedo usarlo? Si no, ¿puedo copiar el código fuente de la biblioteca en mi programa y usarlo?
nimi

1
@Liam: Sí, lo sé, pero ¿qué cuenta como una función integrada? ¿Copiar el código fuente de una biblioteca es algo integrado?
nimi

Respuestas:


119

C, 0.026119s (12 de marzo de 2016)

#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define cache_size 16384
#define Phi_prec_max (47 * a)

#define bit(k) (1ULL << ((k) & 63))
#define word(k) sieve[(k) >> 6]
#define sbit(k) ((word(k >> 1) >> (k >> 1)) & 1)
#define ones(k) (~0ULL >> (64 - (k)))
#define m2(k) ((k + 1) / 2)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
#define ns(t) (1000000000 * t.tv_sec + t.tv_nsec)
#define popcnt __builtin_popcountll

#define mask_build(i, p, o, m) mask |= m << i, i += o, i -= p * (i >= p)
#define Phi_prec_bytes ((m2(Phi_prec_max) + 1) * sizeof(int16_t))
#define Phi_prec(i, j) Phi_prec_pointer[(j) * (m2(Phi_prec_max) + 1) + (i)]
#define Phi_6_next ((i / 1155) * 480 + Phi_5[i % 1155] - Phi_5[(i + 6) / 13])
#define Phi_6_upd_1() t = Phi_6_next, i += 1, *(l++) = t
#define Phi_6_upd_2() t = Phi_6_next, i += 2, *(l++) = t, *(l++) = t
#define Phi_6_upd_3() t = Phi_6_next, i += 3, *(l++) = t, *(l++) = t, *(l++) = t

typedef unsigned __int128 uint128_t;
struct timespec then, now;
uint64_t a, primes[4648] = { 2, 3, 5, 7, 11, 13, 17, 19 }, *primes_fastdiv;
uint16_t *Phi_6, *Phi_prec_pointer;

inline uint64_t Phi_6_mod(uint64_t y)
{
    if (y < 30030)
        return Phi_6[m2(y)];
    else
        return (y / 30030) * 5760 + Phi_6[m2(y % 30030)];
}

inline uint64_t fastdiv(uint64_t dividend, uint64_t fast_divisor)
{
    return ((uint128_t) dividend * fast_divisor) >> 64;
}

uint64_t Phi(uint64_t y, uint64_t c)
{
    uint64_t *d = primes_fastdiv, i = 0, r = Phi_6_mod(y), t = y / 17;

    r -= Phi_6_mod(t), t = y / 19;

    while (i < c && t > Phi_prec_max) r -= Phi(t, i++), t = fastdiv(y, *(d++));

    while (i < c && t) r -= Phi_prec(m2(t), i++), t = fastdiv(y, *(d++));

    return r;
}

uint64_t Phi_small(uint64_t y, uint64_t c)
{
    if (!c--) return y;

    return Phi_small(y, c) - Phi_small(y / primes[c], c);
}

uint64_t pi_small(uint64_t y)
{
    uint64_t i, r = 0;

    for (i = 0; i < 8; i++) r += (primes[i] <= y);

    for (i = 21; i <= y; i += 2)
        r += i % 3 && i % 5 && i % 7 && i % 11 && i % 13 && i % 17 && i % 19;

    return r;
}

int output(int result)
{
    clock_gettime(CLOCK_REALTIME, &now);
    printf("pi(x) = %9d    real time:%9ld ns\n", result , ns(now) - ns(then));

    return 0;
}

int main(int argc, char *argv[])
{
    uint64_t b, i, j, k, limit, mask, P2, *p, start, t = 8, x = atoi(argv[1]);
    uint64_t root2 = sqrt(x), root3 = pow(x, 1./3), top = x / root3 + 1;
    uint64_t halftop = m2(top), *sieve, sieve_length = (halftop + 63) / 64;
    uint64_t i3 = 1, i5 = 2, i7 = 3, i11 = 5, i13 = 6, i17 = 8, i19 = 9;
    uint16_t Phi_3[] = { 0, 1, 1, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 7, 7, 8 };
    uint16_t *l, *m, Phi_4[106], Phi_5[1156];

    clock_gettime(CLOCK_REALTIME, &then);

    sieve = malloc(sieve_length * sizeof(int64_t));

    if (x < 529) return output(pi_small(x));

    for (i = 0; i < sieve_length; i++)
    {
        mask  = 0;

        mask_build( i3,  3,  2, 0x9249249249249249ULL);
        mask_build( i5,  5,  1, 0x1084210842108421ULL);
        mask_build( i7,  7,  6, 0x8102040810204081ULL);
        mask_build(i11, 11,  2, 0x0080100200400801ULL);
        mask_build(i13, 13,  1, 0x0010008004002001ULL);
        mask_build(i17, 17,  4, 0x0008000400020001ULL);
        mask_build(i19, 19, 12, 0x0200004000080001ULL);

        sieve[i] = ~mask;
    }

    limit = min(halftop, 8 * cache_size);

    for (i = 21; i < root3; i += 2)
        if (sbit(i))
            for (primes[t++] = i, j = i * i / 2; j < limit; j += i)
                word(j) &= ~bit(j);

    a = t;

    for (i = root3 | 1; i < root2 + 1; i += 2)
        if (sbit(i)) primes[t++] = i;

    b = t;

    while (limit < halftop)
    {
        start = 2 * limit + 1, limit = min(halftop, limit + 8 * cache_size);

        for (p = &primes[8]; p < &primes[a]; p++)
            for (j = max(start / *p | 1, *p) * *p / 2; j < limit; j += *p)
                word(j) &= ~bit(j);
    }

    P2 = (a - b) * (a + b - 1) / 2;

    for (i = m2(root2); b --> a; P2 += t, i = limit)
    {
        limit = m2(x / primes[b]), j = limit & ~63;

        if (i < j)
        {
            t += popcnt((word(i)) >> (i & 63)), i = (i | 63) + 1;

            while (i < j) t += popcnt(word(i)), i += 64;

            if (i < limit) t += popcnt(word(i) & ones(limit - i));
        }
        else if (i < limit) t += popcnt((word(i) >> (i & 63)) & ones(limit - i));
    }

    if (a < 7) return output(Phi_small(x, a) + a - 1 - P2);

    a -= 7, Phi_6 = malloc(a * Phi_prec_bytes + 15016 * sizeof(int16_t));
    Phi_prec_pointer = &Phi_6[15016];

    for (i = 0; i <= 105; i++)
        Phi_4[i] = (i / 15) * 8 + Phi_3[i % 15] - Phi_3[(i + 3) / 7];

    for (i = 0; i <= 1155; i++)
        Phi_5[i] = (i / 105) * 48 + Phi_4[i % 105] - Phi_4[(i + 5) / 11];

    for (i = 1, l = Phi_6, *l++ = 0; i <= 15015; )
    {
        Phi_6_upd_3(); Phi_6_upd_2(); Phi_6_upd_1(); Phi_6_upd_2();
        Phi_6_upd_1(); Phi_6_upd_2(); Phi_6_upd_3(); Phi_6_upd_1();
    }

    for (i = 0; i <= m2(Phi_prec_max); i++)
        Phi_prec(i, 0) = Phi_6[i] - Phi_6[(i + 8) / 17];

    for (j = 1, p = &primes[7]; j < a; j++, p++)
    {
        i = 1, memcpy(&Phi_prec(0, j), &Phi_prec(0, j - 1), Phi_prec_bytes);
        l = &Phi_prec(*p / 2 + 1, j), m = &Phi_prec(m2(Phi_prec_max), j) - *p;

        while (l <= m)
            for (k = 0, t = Phi_prec(i++, j - 1); k < *p; k++) *(l++) -= t;

        t = Phi_prec(i++, j - 1);

        while (l <= m + *p) *(l++) -= t;
    }

    primes_fastdiv = malloc(a * sizeof(int64_t));

    for (i = 0, p = &primes[8]; i < a; i++, p++)
    {
        t = 96 - __builtin_clzll(*p);
        primes_fastdiv[i] = (bit(t) / *p + 1) << (64 - t);
    }

    return output(Phi(x, a) + a + 6 - P2);
}

Esto usa el método Meissel-Lehmer .

Tiempos

En mi máquina, obtengo aproximadamente 5.7 milisegundos para los casos de prueba combinados. Esto está en un Intel Core i7-3770 con RAM DDR3 a 1867 MHz, ejecutando openSUSE 13.2.

$ ./timepi '-march=native -O3' pi 1000
pi(x) =  93875448    real time:  2774958 ns
pi(x) =  66990613    real time:  2158491 ns
pi(x) =  62366021    real time:  2023441 ns
pi(x) =  34286170    real time:  1233158 ns
pi(x) =   5751639    real time:   384284 ns
pi(x) =   2465109    real time:   239783 ns
pi(x) =   1557132    real time:   196248 ns
pi(x) =      4339    real time:    60597 ns

0.00572879 s

Debido a que la variación se hizo demasiado alta , estoy usando temporizaciones desde dentro del programa para los tiempos de ejecución no oficiales. Este es el script que calculó el promedio de los tiempos de ejecución combinados.

#!/bin/bash

all() { for j in ${a[@]}; do ./$1 $j; done; }

gcc -Wall $1 -lm -o $2 $2.c

a=(1907000000 1337000000 1240000000 660000000 99820000 40550000 24850000 41500)

all $2

r=$(seq 1 $3)

for i in $r; do all $2; done > times

awk -v it=$3 '{ sum += $6 } END { print "\n" sum / (1e9 * it) " s" }' times

rm times

Horarios oficiales

Esta vez es para hacer los casos de puntuación 1000 veces.

real    0m28.006s
user    0m15.703s
sys 0m14.319s

Cómo funciona

Fórmula

Deje ser un número entero positivo.X

Cada entero positivo satisface exactamente una de las siguientes condiciones.norteX

  1. norte=1

  2. es divisible por un número primo p en [ 1 , 3 nortepags.[1,X3]

  3. , donde p y q son (no necesariamente distintos) números primos en ( 3 norte=pagsqpagsq.(X3,X23)

  4. es primo yn > 3 nortenorte>X3

Supongamos que denota el número de primos p tal que p y . Hay π ( x ) - π ( 3 π(y)pagspagsynúmeros que caen en la cuarta categoría.π(X)-π(X3)

Supongamos que denota la cantidad de enteros positivos m y que son producto de exactamente k números primos que no se encuentran entre los primeros números primos c . Hay P 2 ( x , π ( 3 PAGSk(y,C)metroykCnúmeros que caen en la tercera categoría.PAGS2(X,π(X3))

Finalmente, dejemos que denote la cantidad de enteros positivos k y que son coprimos a los primeros números primos c . Hay x - ϕ ( x , π ( 3 ϕ(y,C)kyCnúmeros que caen en la segunda categoría.xϕ(x,π(x3))

Como hay números en todas las categorías,x

1+xϕ(x,π(x3))+P2(x,π(x3))+π(x)π(x3)=x

y por lo tanto,

π(X)=ϕ(X,π(X3))+π(X3)-1-PAGS2(X,π(X3))

Los números en la tercera categoría tienen una representación única si requerimos que y, por lo tanto, p pagsq . De esta manera, el producto de los primospyqestá en la tercera categoría si y solo si 3 pagsXpagsq , entonces hayπ(xX3<pagsqXpagsvalores posibles paraqpara un valor fijo dep, yP2(x,π(3π(Xpags)-π(pags)+1qpags, dondepkdenota elknúmeroprimo.PAGS2(X,π(X3))=π(X3)<kπ(X)(π(Xpagsk)-π(pagsk)+1)pagskkth

Finalmente, cada entero positivo que no es coprimo a los primeros números primos c se puede expresar de manera única como n = p k f , donde p k es el factor primo más bajo de n . De esta manera, k c , yf es coprimo a los primeros números primos k - 1 .norteyCnorte=pagskFpagsknortekCFk-1

Esto lleva a la fórmula recursiva . En particular, la suma está vacía sic=0, entoncesϕ(y,0)=y.ϕ(y,C)=y-1kCϕ(ypagsk,k-1)C=0 0ϕ(y,0 0)=y

Ahora tenemos una fórmula que nos permite calcular generando solo el primer π ( 3 π(X)números primos (millones frente a miles de millones).π(X23)

Algoritmo

Tendremos que calcular , dondeppuede llegar a ser tan bajo como3π(Xpags)pags . Si bien hay otras formas de hacer esto (como aplicar nuestra fórmula de forma recursiva), la forma más rápida parece ser enumerar todos los números primos de hasta3X3 , que se puede hacer con el tamiz de Eratóstenes.X23

Primero, identificamos y almacenamos todos los números primos en , y calculaπ( 3 [1,X]yπ(π(X3)al mismo tiempo. Entonces, calculamos xπ(X) para todos loskin(π(3Xpagskk, y cuenta los números primos hasta cada cociente sucesivo.(π(X3),π(X)]

Además, tiene la forma cerradaπ( 3 π(X3)<kπ(X)(-π(pagsk)+1) , que nos permite completar el cálculo deP2(x,π(3π(X3)-π(X))(π(X3)+π(X)-12.PAGS2(X,π(X3))

Eso deja el cálculo de , que es la parte más costosa del algoritmo. Simplemente usar la fórmula recursiva requeriría 2 llamadas de función c para calcular ϕ ( y , c ) .ϕ2Cϕ(y,C)

En primer lugar, para todos los valores de c , entonces ϕ ( y , c ) = y - 1 k c , p ky ϕ ( yϕ(0 0,C)=0 0C. Por sí sola, esta observación ya es suficiente para hacer factible el cálculo. Esto se debe a que cualquier número por debajo de2109es más pequeño que el producto de cualquiera de los diez primos distintos, por lo que la abrumadora mayoría de los sumandos desaparece.ϕ(y,C)=y-1kC,pagskyϕ(ypagsk,k-1)2109 9

Además, al agrupar y los primeros c sumandos de la definición de ϕ , obtenemos la fórmula alternativa ϕ ( y , c ) = ϕ ( y , c ) - c < k c , p ky ϕ ( yyCϕ. Por lo tanto, la precomputaciónϕ(y,c)para unacfijay los valores apropiados deyguardan la mayoría de las llamadas de función restantes y los cálculos asociados.ϕ(y,C)=ϕ(y,C)-C<kC,pagskyϕ(ypagsk,k-1)ϕ(y,C)cy

Si , entoncesϕ( m c ,c)=φ( m c ), ya que los enteros en[1, m c ]que son divisibles por ninguno de p 1 ,, p c son precisamente los que son coprimos para m c . Además, desdegcd(z+ m c , mmc=1kcpkϕ(mc,c)=φ(mc)[1,mc]p1,,pcmc , tenemos que ϕ ( y , c ) = ϕ ( ygcd(z+mc,mc)=gcd(z,mc).ϕ(y,c)=ϕ(ymcmc,c)+ϕ(y

Como la función totient de Euler es multiplicativa, , y tenemos una manera fácil de derivar ϕ ( y , c ) para todo y al precalcular los valores solo para aquellos y en [ 0 , m c ) .φ(metroC)=1kCφ(pagsk)=1kC(pagsk-1)ϕ(y,C)yy[0 0,metroC)

Además, si establecemos , obtenemos ϕ ( y , c ) = ϕ ( y , c - 1 ) - ϕ ( yC=C-1, la definición original del artículo de Lehmer. Esto nos da una manera simple de calcular previamenteϕ(y,c)para aumentar los valores dec.ϕ(y,C)=ϕ(y,C-1)-ϕ(ypagsC,C-1)ϕ(y,C)C

Además de calcular previamente para un cierto valor bajo de c , también lo calcularemos previamente para valores bajos de y , cortando la recursión poco después de caer por debajo de cierto umbral.ϕ(y,C)Cy

Implementación

La sección anterior cubre la mayoría de las partes del código. Un detalle importante restante es cómo Phise realizan las divisiones en la función .

Dado que calcular solo requiere dividir por el primer π ( 3 ϕnúmeros primos, podemos usar lafunción en su lugar. En lugar de simplemente dividir unaypor una primap, multiplicamosypordp 2 64π(X3)fastdivypagsy lugar y recuperaryrepags264pags comodpyypags . Debido a cómo se implementa la multiplicación de enteros enx64,no se requieredividir por264; los 64 bits superiores dedpyse almacenan en su propio registro.repagsy264264repagsy

Tenga en cuenta que este método requiere precomputar , que no es más rápido que calcular yrepags directamente. Sin embargo, dado que tenemos que dividir por los mismos números primos una y otra vez y la división esmucho más lentaque la multiplicación, esto resulta en una aceleración importante. Se pueden encontrar más detalles sobre este algoritmo, así como una prueba formal, enDivisión por enteros invariantes usando Multiplicación.ypags


22
¿Uno no simplemente supera a Dennis?
Addison Crump

8
Honestamente, no puedo creer lo rápido que es esto. No he tenido tiempo de revisar y comprender lo que está sucediendo, pero realmente necesito hacerlo.
Liam

27
@Liam, tengo la intención de explicar cómo funciona esto, pero todavía estoy tratando de acelerarlo. En este momento, realmente desearía que PPCG tuviera LaTeX ...
Dennis

15
Nota divertida: (En mi máquina) Esto actualmente está superando tanto a Mathematica como a kimwalisch en la biblioteca C ++ de github primecount, sin embargo, actualmente es la única entrada que lo hace.
Michael Klein

10
@TheNumberOne shh no le cuentes sobre eso ... otras personas podrían necesitar eso para vencerlo
Liam

24

C99 / C ++, 8.9208s (28 de febrero de 2016)

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

uint64_t popcount( uint64_t v )
    {
    v = (v & 0x5555555555555555ULL) + ((v>>1) & 0x5555555555555555ULL);
    v = (v & 0x3333333333333333ULL) + ((v>>2) & 0x3333333333333333ULL);
    v = (v & 0x0F0F0F0F0F0F0F0FULL) + ((v>>4) & 0x0F0F0F0F0F0F0F0FULL);
    v *= 0x0101010101010101ULL;
    return v >> 56;
    }

#define PPROD  3*5*7

int primecount( int limit )
    {
    int i,j;
    int reps = (limit-1)/(64*PPROD) + 1;
    int mod_limit = reps * (64*PPROD);
    int seek_limit = (int)ceil( sqrt(limit) );
    int primecount = 0;
    int slice_count = limit/250000 + 1;

    uint8_t *buf = (uint8_t *)malloc( mod_limit/8 + seek_limit);
    int *primes = (int *)malloc(seek_limit*sizeof(int));

    // initialize a repeating bit-pattern to fill our sieve-memory with
    uint64_t v[PPROD];
    memset(v, 0, sizeof(v) );
    for(i=0;i<(64*PPROD);i++)
        for(j=2;j<=7;j++)
            if( i % j == 0 )
                v[ i >> 6 ] |= 1ULL << (i & 0x3F);

    for(i=0; i<reps; i++)
        memcpy( buf + 8*PPROD*i, v, 8*PPROD );

    // use naive E-sieve to get hold of all primes to test for
    for(i=11;i<seek_limit;i+=2)
        {
        if( (buf[i >> 3] & (1 << (i & 7)) ) == 0 )
            {
            primes[primecount++] = i;
            for(j=3*i;j<seek_limit;j += 2*i )
                buf[j >> 3] |= (1 << (j&7) );
            }
        }

    // fill up whole E-sieve. Use chunks of about 30 Kbytes
    // so that the chunk of E-sieve we're working on
    // can fit into the L1-cache.
    for(j=0;j<slice_count;j++)
        {
        int low_bound = ((uint64_t)limit * j) / slice_count;
        int high_bound = ((uint64_t)limit * (j+1)) / slice_count - 1;

        for(i=0;i<primecount;i++)
            {
            int pm = primes[i];
            // compute the first odd multiple of pm that is larger than or equal
            // to the lower bound.
            uint32_t lb2 = (low_bound + pm - 1) / pm;
            lb2 |= 1;
            if( lb2 < 3 ) lb2 = 3;
            lb2 *= pm;
            uint32_t hb2 = (high_bound / pm) * pm;

            uint32_t kt1 = ((lb2 + 2*pm) >> 3) - (lb2 >> 3);
            uint32_t kt2 = ((lb2 + 4*pm) >> 3) - (lb2 >> 3);
            uint32_t kt3 = ((lb2 + 6*pm) >> 3) - (lb2 >> 3);

            uint32_t kx0 = 1 << (lb2 & 7);
            uint32_t kx1 = 1 << ((lb2 + 2*pm) & 7);
            uint32_t kx2 = 1 << ((lb2 + 4*pm) & 7);
            uint32_t kx3 = 1 << ((lb2 + 6*pm) & 7);

            uint8_t *lb3 = buf + (lb2 >> 3);
            uint8_t *hb3 = buf + (hb2 >> 3);

            uint8_t *kp;
            for(kp=lb3; kp<=hb3; kp+=pm)
                {
                kp[0]   |= kx0;
                kp[kt1] |= kx1;
                kp[kt2] |= kx2;
                kp[kt3] |= kx3;
                }
            }
        }

    // flag tail elements to exclude them from prime-counting.
    for(i=limit;i<mod_limit;i++)
        buf[i >> 3] |= 1 << (i&7);

    int sum = 0;
    uint64_t *bufx = (uint64_t *)buf;

    for(i=0;i<mod_limit>>6;i++)
        sum += popcount( bufx[i] );

    free(buf);
    free(primes);

    return mod_limit - sum + 3;
    }


int main( int argc, char **argv)
    {
    if( argc != 2 )
        {
        printf("Please provide an argument\n");
        exit(1);
        }

    int limit = atoi( argv[1] );
    if( limit < 3 || limit > 2000000000 )
        {
        printf("Argument %d out of range\n", limit );
        exit(1);
        }

    printf("%d\n", primecount(limit) );
    }

Una implementación de tamiz de erastotelios basada en mapas de bits. Realiza los siguientes pasos:

  1. Primero, genere un patrón de bits repetitivo para llenar el tamiz, que cubra múltiplos de 2,3,5,7
  2. Luego, use el método de tamizado para generar una matriz de todos los primos más pequeños que sqrt (n)
  3. Luego, use la lista principal del paso anterior para escribir en el tamiz. Esto se hace en trozos del tamiz que tienen aproximadamente el tamaño de caché L1, de modo que el procesamiento del tamiz no agite constantemente el caché L1; Esto parece provocar una aceleración de 5x sobre no fragmentar.
  4. Finalmente, realice un conteo de bits.

Compilado gcc primecount.c -O3 -lm -Wally ejecutado en ubuntu 15.10 (64 bits) en un i7-4970k, el conjunto completo de casos de puntuación tarda aproximadamente 2.2 segundos. El tiempo de ejecución está dominado por el paso 3; esto podría ser multiproceso si se desea, ya que los fragmentos son independientes; esto requeriría un poco de cuidado para garantizar que los límites de los fragmentos se alineen adecuadamente.

Asigna un poco más de memoria de la estrictamente necesaria para el tamiz; Esto deja espacio para un poco de desbordamiento de fin de búfer, que es necesario para que el desenrollado del bucle en el paso 3 funcione correctamente.

Horarios oficiales

real    0m8.934s
user    0m8.795s
sys 0m0.150s

real    0m8.956s
user    0m8.818s
sys 0m0.150s

real    0m8.907s
user    0m8.775s
sys 0m0.145s

real    0m8.904s
user    0m8.775s
sys 0m0.141s

real    0m8.902s
user    0m8.783s
sys 0m0.132s

real    0m9.087s
user    0m8.923s
sys 0m0.176s

real    0m8.905s
user    0m8.778s
sys 0m0.140s

real    0m9.005s
user    0m8.859s
sys 0m0.158s

real    0m8.911s
user    0m8.789s
sys 0m0.135s

real    0m8.907s
user    0m8.781s
sys 0m0.138s

8
¡Bienvenido a Programming Puzzles & Code Golf, y felicidades por tu primer post estelar !
Dennis

Considera usar -O3 -march=native. Su CPU es compatible con la popcntinstrucción , y los compiladores a veces pueden reconocer algunas implementaciones de C puro y compilar en una sola instrucción. (O mejor, usar __builtin_popcountllen GNU C, como la respuesta de Dennis).
Peter Cordes

-march=nativeen su CPU Haswell también habilitará BMI2 para obtener instrucciones de cambio de conteo variable más eficientes. ( SHLX en lugar del SHL heredado que necesita contar cl). La CPU AMD Piledriver del OP no tiene BMI2, pero tiene popcnt. Pero las CPU AMD ejecutan SHL de conteo variable más rápido que las CPU Intel, por lo que compilar con BMI2 durante el ajuste podría ser apropiado. Piledriver es bastante diferente de Haswell en lo que respecta a las micro optimizaciones, pero pedir -march=nativees bueno
Peter Cordes

12

Python 2 (PyPy 4.0), 2.36961s (29 de febrero de 2016)

def Phi(m, b):
    if not b:
        return m
    if not m:
        return 0
    if m >= 800:
        return Phi(m, b - 1) - Phi(m // primes[b - 1], b - 1)
    t = b * 800 + m
    if not Phi_memo[t]:
        Phi_memo[t] =  Phi(m, b - 1) - Phi(m // primes[b - 1], b - 1)
    return Phi_memo[t]

x = int(input())

if x < 6:
    print [0, 0, 1, 2, 2, 3][x]
    exit()

root2 = int(x ** (1./2))
root3 = int(x ** (1./3))
top = x // root3 + 1
sieve = [0, 0] + [1] * (top - 2)
pi = [0, 0]
primes = []
t = 0

for i in range(2, top):
    if sieve[i] == 1:
        t += 1
        primes.append(i)
        sieve[i::i] = [0] * len(sieve[i::i])
    pi.append(t)

a, b = pi[root3 + 1], pi[root2 + 1]
Phi_memo = [0] * ((a + 1) * 800)

print Phi(x, a) + a - 1 - sum(pi[x // p] - pi[p] + 1 for p in primes[a:b])

Esto usa el método Meissel-Lehmer.

Tiempos

$ time for i in 1.907e9 1.337e9 1.24e9 6.6e8 9.982e7 4.055e7 2.485e7 41500
> do pypy pi.py <<< $i; done
93875448
66990613
62366021
34286170
5751639
2465109
1557132
4339

real    0m1.696s
user    0m1.360s
sys     0m0.332s

Horarios oficiales

Como había otra respuesta con un tiempo similar, opté por obtener resultados más precisos. Lo cronometré 100 veces. El puntaje es el siguiente tiempo dividido por 100.

real    3m56.961s
user    3m38.802s
sys 0m18.512s

55
Además, solo para tener en cuenta: este código es 15,102.4 veces más rápido que el mío. +1
Addison Crump

12

Java, 25,725.315 segundos en esta máquina

Esto no va a ganar , solo quería publicar una respuesta que no use ningún tamiz.

ACTUALIZACIÓN: actualmente está clasificada en aproximadamente 150,440.4386 veces más lento que el puntaje principal. Sube y vota, su respuesta es increíble.

Código de bytes:

0000000: cafe babe 0000 0034 0030 0a00 0900 1709  .......4.0......
0000010: 0018 0019 0a00 1a00 1b0a 0008 001c 0a00  ................
0000020: 1d00 1e0a 0008 001f 0a00 2000 2107 0022  .......... .!.."
0000030: 0700 2301 0006 3c69 6e69 743e 0100 0328  ..#...<init>...(
0000040: 2956 0100 0443 6f64 6501 000f 4c69 6e65  )V...Code...Line
0000050: 4e75 6d62 6572 5461 626c 6501 0004 6d61  NumberTable...ma
0000060: 696e 0100 1628 5b4c 6a61 7661 2f6c 616e  in...([Ljava/lan
0000070: 672f 5374 7269 6e67 3b29 5601 0008 6e75  g/String;)V...nu
0000080: 6d50 7269 6d65 0100 0428 4929 4901 000d  mPrime...(I)I...
0000090: 5374 6163 6b4d 6170 5461 626c 6501 0007  StackMapTable...
00000a0: 6973 5072 696d 6501 0004 2849 295a 0100  isPrime...(I)Z..
00000b0: 0a53 6f75 7263 6546 696c 6501 0006 452e  .SourceFile...E.
00000c0: 6a61 7661 0c00 0a00 0b07 0024 0c00 2500  java.......$..%.
00000d0: 2607 0027 0c00 2800 290c 0010 0011 0700  &..'..(.).......
00000e0: 2a0c 002b 002c 0c00 1300 1407 002d 0c00  *..+.,.......-..
00000f0: 2e00 2f01 0001 4501 0010 6a61 7661 2f6c  ../...E...java/l
0000100: 616e 672f 4f62 6a65 6374 0100 106a 6176  ang/Object...jav
0000110: 612f 6c61 6e67 2f53 7973 7465 6d01 0003  a/lang/System...
0000120: 6f75 7401 0015 4c6a 6176 612f 696f 2f50  out...Ljava/io/P
0000130: 7269 6e74 5374 7265 616d 3b01 0011 6a61  rintStream;...ja
0000140: 7661 2f6c 616e 672f 496e 7465 6765 7201  va/lang/Integer.
0000150: 0008 7061 7273 6549 6e74 0100 1528 4c6a  ..parseInt...(Lj
0000160: 6176 612f 6c61 6e67 2f53 7472 696e 673b  ava/lang/String;
0000170: 2949 0100 136a 6176 612f 696f 2f50 7269  )I...java/io/Pri
0000180: 6e74 5374 7265 616d 0100 0770 7269 6e74  ntStream...print
0000190: 6c6e 0100 0428 4929 5601 000e 6a61 7661  ln...(I)V...java
00001a0: 2f6c 616e 672f 4d61 7468 0100 0473 7172  /lang/Math...sqr
00001b0: 7401 0004 2844 2944 0021 0008 0009 0000  t...(D)D.!......
00001c0: 0000 0004 0001 000a 000b 0001 000c 0000  ................
00001d0: 001d 0001 0001 0000 0005 2ab7 0001 b100  ..........*.....
00001e0: 0000 0100 0d00 0000 0600 0100 0000 0100  ................
00001f0: 0900 0e00 0f00 0100 0c00 0000 2c00 0300  ............,...
0000200: 0100 0000 10b2 0002 2a03 32b8 0003 b800  ........*.2.....
0000210: 04b6 0005 b100 0000 0100 0d00 0000 0a00  ................
0000220: 0200 0000 0300 0f00 0400 0a00 1000 1100  ................
0000230: 0100 0c00 0000 6600 0200 0300 0000 2003  ......f....... .
0000240: 3c03 3d1c 1aa2 0018 1b1c b800 0699 0007  <.=.............
0000250: 04a7 0004 0360 3c84 0201 a7ff e91b ac00  .....`<.........
0000260: 0000 0200 0d00 0000 1600 0500 0000 0600  ................
0000270: 0200 0700 0900 0800 1800 0700 1e00 0900  ................
0000280: 1200 0000 1800 04fd 0004 0101 5001 ff00  ............P...
0000290: 0000 0301 0101 0002 0101 fa00 0700 0a00  ................
00002a0: 1300 1400 0100 0c00 0000 9700 0300 0300  ................
00002b0: 0000 4c1a 05a2 0005 03ac 1a05 9f00 081a  ..L.............
00002c0: 06a0 0005 04ac 1a05 7099 0009 1a06 709a  ........p.....p.
00002d0: 0005 03ac 1a87 b800 078e 0460 3c10 063d  ...........`<..=
00002e0: 1c1b a300 1b1a 1c04 6470 9900 0b1a 1c04  ........dp......
00002f0: 6070 9a00 0503 ac84 0206 a7ff e604 ac00  `p..............
0000300: 0000 0200 0d00 0000 2200 0800 0000 0c00  ........".......
0000310: 0700 0d00 1300 0e00 2100 0f00 2a00 1000  ........!...*...
0000320: 3200 1100 4400 1000 4a00 1200 1200 0000  2...D...J.......
0000330: 1100 0907 0901 0b01 fd00 0b01 0114 01fa  ................
0000340: 0005 0001 0015 0000 0002 0016            ............

Código fuente:

public class E {
    public static void main(String[]args){
        System.out.println(numPrime(Integer.parseInt(args[0])));
    }
    private static int numPrime(int max) {
        int toReturn = 0;
        for (int i = 0; i < max; i++)
            toReturn += (isPrime(i))?1:0;
        return toReturn;
    }
    private static boolean isPrime(int n) {
            if(n < 2) return false;
            if(n == 2 || n == 3) return true;
            if(n%2 == 0 || n%3 == 0) return false;
            int sqrtN = (int)Math.sqrt(n)+1;
            for(int i = 6; i <= sqrtN; i += 6)
                if(n%(i-1) == 0 || n%(i+1) == 0) return false;
            return true;
    }
}

Resulta que el optimizador estaba, de hecho, aumentando el tiempo necesario. >.> Maldita sea.

La entrada por debajo de 1000 parece tomar un tiempo promedio de .157s en mi computadora (probablemente debido a la carga de la clase ಠ_ಠ), pero después de 1e7 se pone inquieto.

Lista de tiempos:

> time java E 41500;time java E 24850000;time java E 40550000;time java E 99820000;time java E 660000000;time java E 1240000000;time java E 1337000000;time java E 1907000000
4339

real    0m0.236s
user    0m0.112s
sys     0m0.024s
1557132

real    0m8.842s
user    0m8.784s
sys     0m0.060s
2465109

real    0m18.442s
user    0m18.348s
sys     0m0.116s
5751639

real    1m15.642s
user    1m8.772s
sys     0m0.252s
34286170

real    40m35.810s
user    16m5.240s
sys     0m5.820s
62366021

real    104m12.628s
user    39m32.348s
sys     0m13.584s
66990613

real    110m22.064s
user    42m28.092s
sys     0m11.320s
93875448

real    171m51.650s
user    68m39.968s
sys     0m14.916s

11
Java se está ejecutando actualmente en una CPU 100% consistente en este momento. Esto es totalmente eficiente, ¿de qué estás hablando?
Addison Crump

¿Me puede dar un tutorial para dejar de fumar sobre Java (porque C / C ++> Java). Compilo con javac voteToClose.java(cambié el nombre de la clase) y luego ¿qué?
Liam

@Liamjava voteToClose <input>
Addison Crump

1
Espera ... ¿Por qué dice el código de byte cafe babe?
Cyoce

12
@Cyoce Todos los archivos de clase Java están encabezados con 0xCAFEBABE.
Addison Crump

8

Óxido, 0.37001 seg (12 de junio de 2016)

Aproximadamente 10 veces más lento que más lento que la Crespuesta de Dennis , pero 10 veces más rápido que su entrada en Python. Esta respuesta es posible gracias a @Shepmaster y @Veedrac que ayudaron a mejorarla en la Revisión de Código . Se toma textualmente de la publicación de @ Veedrac .

use std::env;

const EMPTY: usize = std::usize::MAX;
const MAX_X: usize = 800;

fn main() {
    let args: Vec<_> = env::args().collect();
    let x: usize = args[1].trim().parse().expect("expected a number");

    let root = (x as f64).sqrt() as usize;
    let y = (x as f64).powf(0.3333333333333) as usize + 1;

    let sieve_size = x / y + 2;
    let mut sieve = vec![true; sieve_size];
    let mut primes = vec![0; sieve_size];
    sieve[0] = false;
    sieve[1] = false;

    let mut a = 0;
    let mut num_primes = 1;

    let mut num_primes_smaller_root = 0;

    // find all primes up to x/y ~ x^2/3 aka sieve_size
    for i in 2..sieve_size {
        if sieve[i] {
            if i <= root {
                if i <= y {
                    a += 1;
                }
                num_primes_smaller_root += 1;
            }

            primes[num_primes] = i;
            num_primes += 1;
            let mut multiples = i;
            while multiples < sieve_size {
                sieve[multiples] = false;
                multiples += i;
            }
        }
    }

    let interesting_primes = primes[a + 1..num_primes_smaller_root + 1].iter();

    let p_2 =
        interesting_primes
        .map(|ip| primes.iter().take_while(|&&p| p <= x / ip).count())
        .enumerate()
        .map(|(i, v)| v - 1 - i - a)
        .fold(0, |acc, v| acc + v);

    let mut phi_results = vec![EMPTY; (a + 1) * MAX_X];
    println!("pi({}) = {}", x, phi(x, a, &primes, &mut phi_results) + a - 1 - p_2);
}

fn phi(x: usize, b: usize, primes: &[usize], phi_results: &mut [usize]) -> usize {
    if b == 0 {
        return x;
    }

    if x < MAX_X && phi_results[x + b * MAX_X] != EMPTY {
        return phi_results[x + b * MAX_X];
    }

    let value = phi(x, b - 1, primes, phi_results) - phi(x / primes[b], b - 1, primes, phi_results);
    if x < MAX_X {
        phi_results[x + b * MAX_X] = value;
    }
    value
}

Programado con: time ./time.shdonde se time.shve así:

#!/bin/bash

a=(1907000000 1337000000 1240000000 660000000 99820000 40550000 24850000 41500)

for i in {0..100}; do
    for j in ${a[@]}; do
        ./target/release/pi_n $j  > /dev/null;
    done;
done;

Aquí está la salida.

[me@localhost pi_n]$ time ./time.sh 

real    0m37.011s
user    0m34.752s
sys 0m2.410s

8

Node.js (JavaScript / ES6), 83.549s (11 de noviembre de 2016)

var n=process.argv[2]*1,r=new Uint8Array(n),p=0,i=1,j
while(++i<=n){
  if(r[i]===0){
    for(j=i*i;j<=n;j+=i){r[j]=1}
    p+=1
  }
}
console.log(p)

Finalmente pude rehacer esto, y es más pequeño / simple y MUCHO más rápido que antes. En lugar de un método de fuerza bruta más lento, utiliza el Tamiz de Eratóstenes junto con estructuras de datos más eficientes, por lo que ahora puede terminar realmente en un tiempo respetable (por lo que puedo encontrar en Internet, es el recuento principal JS más rápido funcionar por ahí).

Algunos tiempos de demostración (i7-3770k):

10^4 (10,000) => 0.001 seconds
10^5 (100,000) => 0.003 seconds
10^6 (1,000,000) => 0.009 seconds
10^7 (10,000,000) => 0.074 seconds
10^8 (100,000,000) => 1.193 seconds
10^9 (1,000,000,000) => 14.415 seconds

¿Por qué +=1y no ++?
ETHproductions

@ETHproductions Depende de si quieres decir antes o después del incremento. i++tiene que mantener el cambio de valor para otra operación, lo que a esta escala conduce a un golpe de rendimiento pequeño pero notable. No probé el pre-incremento, pero sospecho que será más o menos igual que +=1.
Mwr247

Pero +=1necesita asignar 1en la memoria. Yo creo que. Si yo fuera tú, lo usaría ++i. Creo que hay una sola instrucción para incrementar un valor, así que no estoy seguro.
Ismael Miguel

¿Por qué está tan condensado? Esto no es código golf , y esto es realmente difícil de leer.
Cyoce

Además, podría ser útil cambiar (...)|0;i=0a(...)|(i=0)
Cyoce

6

C ++ 11, 22.6503s (28 de febrero de 2016)

Compilar con g++ -O2 -m64 -march=native -ftree-vectorize -std=c++11 numprimes.cpp. Estas opciones son importantes. También necesita tener Boost instalado. En Ubuntu, esto está disponible mediante la instalación libboost-all-dev.

Si está en Windows, puedo recomendarle instalar g++e impulsar a través de MSYS2 . He escrito un buen tutorial sobre cómo instalar MSYS2. Después de seguir el tutorial, puede instalar Boost usando pacman -Sy `pacman -Ssq boost`.

#include <cmath>
#include <cstdint>
#include <iostream>
#include <vector>
#include <boost/dynamic_bitset.hpp>

uint64_t num_primes(uint64_t n) {
    // http://stackoverflow.com/questions/4643647/fast-prime-factorization-module
    uint64_t pi = (n >= 2) + (n >= 3);
    if (n < 5) return pi;

    n += 1;
    uint64_t correction = n % 6 > 1;
    uint64_t wheels[6] = { n, n - 1, n + 4, n + 3, n + 2, n + 1 };
    uint64_t limit = wheels[n % 6];

    boost::dynamic_bitset<> sieve(limit / 3);
    sieve.set();
    sieve[0] = false;

    for (uint64_t i = 0, upper = uint64_t(std::sqrt(limit))/3; i <= upper; ++i) {
        if (sieve[i]) {
            uint64_t k = (3*i + 1) | 1;
            for (uint64_t j = (k*k) / 3;                   j < limit/3; j += 2*k) sieve[j] = false;
            for (uint64_t j = (k*k + 4*k - 2*k*(i & 1))/3; j < limit/3; j += 2*k) sieve[j] = false;
        }
    }

    pi += sieve.count();
    for (uint64_t i = limit / 3 - correction; i < limit / 3; ++i) pi -= sieve[i];

    return pi;
}


int main(int argc, char** argv) {
    if (argc <= 1) {
        std::cout << "Usage: " << argv[0] << " n\n";
        return 0;
    }

    std::cout << num_primes(std::stoi(argv[1])) << "\n";
    return 0;
}

En mi máquina, esto se ejecuta en 4.8 segundos para 1907000000 (1.9e9).

El código anterior fue reutilizado de mi biblioteca personal de C ++ , por lo que tenía una ventaja inicial.

Horarios oficiales

real    0m22.760s
user    0m22.704s
sys 0m0.080s

real    0m22.854s
user    0m22.800s
sys 0m0.077s

real    0m22.742s
user    0m22.700s
sys 0m0.066s

real    0m22.484s
user    0m22.450s
sys 0m0.059s

real    0m22.653s
user    0m22.597s
sys 0m0.080s

real    0m22.665s
user    0m22.602s
sys 0m0.088s

real    0m22.528s
user    0m22.489s
sys 0m0.062s

real    0m22.510s
user    0m22.474s
sys 0m0.060s

real    0m22.819s
user    0m22.759s
sys 0m0.084s

real    0m22.488s
user    0m22.459s
sys 0m0.053s

: o Dayyyum. Eso es rapido. Cual es tu maquina
Addison Crump

@VoteToClose Intel i5-4670k con Windows 7 de 64 bits
orlp

¿Quieres añadir una explicación?
Liam

@Liam Es solo un tamiz que tiene cualquier número que sea un múltiplo de 2 y 3 fuera del tamiz.
orlp

3

C ++, 2.47215s (29 de febrero de 2016)

Esta es una versión (descuidada) multiproceso de mi otra respuesta.

#include <cstdint>
#include <vector>
#include <iostream>
#include <limits>
#include <cmath>
#include <array>
// uses posix ffsll
#include <string.h>
#include <algorithm>
#include <thread>

constexpr uint64_t wheel_width = 2;
constexpr uint64_t buf_size = 1<<(10+6);
constexpr uint64_t dtype_width = 6;
constexpr uint64_t dtype_mask = 63;
constexpr uint64_t buf_len = ((buf_size*wheel_width)>>dtype_width);
constexpr uint64_t seg_len = 6*buf_size;
constexpr uint64_t limit_i_max = 0xfffffffe00000001ULL;

typedef std::vector<uint64_t> buf_type;

void mark_composite(buf_type& buf, uint64_t prime,
                    std::array<uint64_t, 2>& poff,
                    uint64_t seg_start, uint64_t max_j)
{
  const auto p = 2*prime;
  for(uint64_t k = 0; k < wheel_width; ++k)
  {
    for(uint64_t j = 2*poff[k]+(k==0); j < max_j; j += p)
    {
      buf[(j-seg_start)>>dtype_width] |= 1ULL << (j & dtype_mask);
      poff[k] += prime;
    }
  }
}

struct prime_counter
{
  buf_type buf;
  uint64_t n;
  uint64_t seg_a, seg_b;
  uint64_t nj;
  uint64_t store_max;
  uint64_t& store_res;

  prime_counter(uint64_t n, uint64_t seg_a, uint64_t seg_b, uint64_t nj, uint64_t store_max,
                uint64_t& store_res) :
    buf(buf_len), n(n), nj(nj), seg_a(seg_a), seg_b(seg_b),
    store_max(store_max), store_res(store_res)
  {}

  prime_counter(const prime_counter&) = default;
  prime_counter(prime_counter&&) = default;

  prime_counter& operator =(const prime_counter&) = default;
  prime_counter& operator =(prime_counter&&) = default;

  void operator()(uint64_t nsmall_segs,
                  const std::vector<uint64_t>& primes,
                  std::vector<std::array<uint64_t, 2> > poffs)
  {
    uint64_t res = 0;
    // no new prime added portion
    uint64_t seg_start = buf_size*wheel_width*seg_a;
    uint64_t seg_min = seg_len*seg_a+5;

    if(seg_a > nsmall_segs)
    {
      uint64_t max_j = buf_size*wheel_width*nsmall_segs+(seg_a-nsmall_segs)*(buf_len<<dtype_width);
      for(size_t k = 0; k < wheel_width; ++k)
      {
        for(uint64_t i = 0; i < poffs.size() && max_j >= (2*poffs[i][k]+(k==0)); ++i)
        {
          // adjust poffs
          // TODO: might be a more efficient way
          auto w = (max_j-(2*poffs[i][k]+(k==0)));
          poffs[i][k] += primes[i]*(w/(2*primes[i]));
          if(w % (2*primes[i]) != 0)
          {
            poffs[i][k]+=primes[i];// += primes[i]*(w/(2*primes[i])+1);
          }
          /*else
          {

          }*/
        }
      }
    }

    for(uint64_t seg = seg_a; seg < seg_b; ++seg)
    {
      std::fill(buf.begin(), buf.end(), 0);
      const uint64_t limit_i = std::min<uint64_t>((((seg_len+seg_min) >= limit_i_max) ?
                                                   std::numeric_limits<uint32_t>::max() :
                                                   ceil(sqrt(seg_len+seg_min))),
                                                  store_max);
      uint64_t max_j = std::min(seg_start+(buf_len<<dtype_width), nj);
      for(uint64_t i = 0; i < primes.size() && primes[i] <= limit_i; ++i)
      {
        mark_composite(buf, primes[i], poffs[i], seg_start, max_j);
      }
      // sieve
      uint64_t val;
      const uint64_t stop = std::min(seg_min+seg_len, n);
      for(uint64_t i = ffsll(~(buf[0]))-((~buf[0]) != 0)+64*((~buf[0]) == 0);
          (val = 6ULL*(i>>1)+seg_min+2ULL*(i&1ULL)) < stop;)
      {
        if(!(buf[i>>dtype_width] & (1ULL << (i & dtype_mask))))
        {
          ++res;
          ++i;
        }
        else
        {
          uint64_t mask = buf[i>>dtype_width]>>(i&dtype_mask);
          const int64_t inc = ffsll(~mask)-((~mask) != 0)+64*((~mask) == 0);
          i += inc;
        }
      }
      seg_min += seg_len;
      seg_start += buf_size*wheel_width;
    }
    store_res = res;
  }
};

uint64_t num_primes(uint64_t n)
{
  uint64_t res = (n >= 2) + (n >= 3);
  if(n >= 5)
  {
    buf_type buf(buf_len);
    // compute and store primes < sqrt(n)
    const uint64_t store_max = ceil(sqrt(n));

    // only primes >= 5
    std::vector<uint64_t> primes;
    std::vector<std::array<uint64_t, 2> > poffs;
    primes.reserve(ceil(1.25506*store_max/log(store_max)));
    poffs.reserve(ceil(1.25506*store_max/log(store_max)));
    uint64_t seg_start = 0;
    uint64_t seg_min = 5;
    const uint64_t num_segs = 1+(n-seg_min)/seg_len;
    const uint64_t nj = (n-seg_min)/3+1;
    // compute how many small segments there are
    const uint64_t nsmall_segs = 1+(store_max-seg_min)/seg_len;
    for(uint64_t seg = 0; seg < nsmall_segs; ++seg)
    {
      std::fill(buf.begin(), buf.end(), 0);
      // mark off small primes
      const uint64_t limit_i = std::min<uint64_t>((((seg_len+seg_min) >= limit_i_max) ?
                                                   std::numeric_limits<uint32_t>::max() :
                                                   ceil(sqrt(seg_len+seg_min))),
                                                  store_max);
      uint64_t max_j = std::min(seg_start+(buf_len<<dtype_width), nj);
      for(uint64_t i = 0; i < primes.size() && primes[i] <= limit_i; ++i)
      {
        mark_composite(buf, primes[i], poffs[i], seg_start, max_j);
      }
      // sieve
      uint64_t val;
      const uint64_t stop = std::min(seg_min+seg_len, n);
      for(uint64_t i = ffsll(~(buf[0]))-((~buf[0]) != 0)+64*((~buf[0]) == 0);
            (val = 6ULL*(i>>1)+seg_min+2ULL*(i&1ULL)) < stop;)
      {
        if(!(buf[i>>dtype_width] & (1ULL << (i & dtype_mask))))
        {
          if(val <= store_max)
          {
            // add prime and poffs
            primes.push_back(val);
            poffs.emplace_back();
            poffs.back()[0] = (val*val-1)/6-1;
            if(i&1)
            {
              // 6n+1 prime
              poffs.back()[1] = (val*val+4*val-5)/6;
            }
            else
            {
              // 6n+5 prime
              poffs.back()[1] = (val*val+2*val-5)/6;
            }
            // mark-off multiples
            mark_composite(buf, val, poffs.back(), seg_start, max_j);
          }
          ++res;
          ++i;
        }
        else
        {
          uint64_t mask = buf[i>>dtype_width]>>(i&dtype_mask);
          const int64_t inc = ffsll(~mask)-((~mask) != 0)+64*((~mask) == 0);
          i += inc;
        }
      }
      seg_min += seg_len;
      seg_start += buf_size*wheel_width;
    }
    // multi-threaded sieving for remaining segments
    std::vector<std::thread> workers;
    auto num_workers = std::min<uint64_t>(num_segs-nsmall_segs, std::thread::hardware_concurrency());
    std::vector<uint64_t> store_reses(num_workers);

    workers.reserve(num_workers);
    auto num_segs_pw = ceil((num_segs-nsmall_segs)/static_cast<double>(num_workers));
    for(size_t i = 0; i < num_workers; ++i)
    {
      workers.emplace_back(prime_counter(n, nsmall_segs+i*num_segs_pw,
                                         std::min<uint64_t>(nsmall_segs+(i+1)*num_segs_pw,
                                                            num_segs),
                                         nj, store_max, store_reses[i]),
                           nsmall_segs, primes, poffs);
    }
    for(size_t i = 0; i < num_workers; ++i)
    {
      workers[i].join();
      res += store_reses[i];
    }
  }
  return res;
}

int main(int argc, char** argv)
{
  if(argc <= 1)
  {
    std::cout << "usage: " << argv[0] << " n\n";
    return -1;
  }
  std::cout << num_primes(std::stoll(argv[1])) << '\n';
}

Utiliza un tamiz segmentado de Eratóstenes con una factorización de rueda de 6 para omitir todos los múltiplos de 2/3. Hace uso de POSIXffsll para omitir valores compuestos consecutivos.

Compilar:

g++ -std=c++11 -o sieve_mt -O3 -march=native -pthread sieve_mt.cpp

horarios no oficiales

Programado con un Intel i5-6600k en Ubuntu 15.10, tomó el caso 1907000000 0.817s.

Horarios oficiales

Para obtener tiempos más precisos, cronometré esto 100 veces, luego dividí el tiempo por 100.

real    4m7.215s
user    23m54.086s
sys 0m1.239s

Dado que esta y la respuesta de Python de @Dennis están tan cerca, puedo volver a tomarlas para obtener resultados más precisos.
Liam

Wow wow wow. Esto tiene aún menos sentido para mí que CJam o Pyth. ¡Lo llamaré el monstruo bit-shift! +1
Tamoghna Chowdhury

Además, ¿podrías probar CUDA / OpenCL para una aceleración de GPU? Si supiera más C, podría haberlo hecho.
Tamoghna Chowdhury

Sí, supongo que fui un poco excesivo con el desplazamiento / enmascaramiento de bits: PI no sabe si GPGPU sería útil aquí o no; la única área que puedo ver que posiblemente ayuda es pre-tamizar pequeños números primos, e incluso entonces las velocidades de transferencia de datos podrían ser suficientes para matar eso. Lo que todavía me fastidia es que todavía estoy apagado por un factor de aproximadamente 10 desde la implementación de tamiz más rápida que he visto
helloworld922

2

C, 2m42.7254s (28 de febrero de 2016)

Guardar como pi.c, compilar como gcc -o pi pi.c, ejecutar como ./pi <arg>:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

unsigned char p[2000000001];

int main(int argc, char **argv)
{
        unsigned int n, c, i, j;

        n = atoi(argv[1]);
        memset(p, 1, n + 1);

        p[1] = p[0] = 0;

        for (i = 2, c = 0; i <= n; i++)
        {
                if (p[i])
                {
                        c++;
                        for (j = i + i; j <= n; j += i)
                                p[j] = 0;
                }
        }

        printf("%d: %d\n", n, c);

        return 0;
}

¡Necesita mucha memoria para ejecutarse! Si su hardware no puede ahorrar hasta dos gigabytes de memoria real, entonces el programa se bloqueará o se ejecutará muy lentamente debido a la agitación VMM y HD.

El tiempo aproximado en mi hardware es 1.239 × 10 -8 · n 1.065 s. Por ejemplo, una entrada de n = 2 × 10 9 tarda unos 100 s en ejecutarse.

Horarios oficiales

real    2m42.657s
user    2m42.065s
sys 0m0.757s

real    2m42.947s
user    2m42.400s
sys 0m0.708s

real    2m42.827s
user    2m42.282s
sys 0m0.703s

real    2m42.800s
user    2m42.300s
sys 0m0.665s

real    2m42.562s
user    2m42.050s
sys 0m0.675s

real    2m42.788s
user    2m42.192s
sys 0m0.756s

real    2m42.631s
user    2m42.074s
sys 0m0.720s

real    2m42.658s
user    2m42.115s
sys 0m0.707s

real    2m42.710s
user    2m42.219s
sys 0m0.657s

real    2m42.674s
user    2m42.110s
sys 0m0.730s

Esto funciona utilizando el tamiz de eratóstenes? Lo cronometraré cuando llegue a casa
Liam

Estoy segfalando en el primer caso (otros funcionan bien). Sucede después de ~ 1 minuto de tiempo de ejecución. Agregué una if (p==NULL) {exit(1);}línea al código, por lo que no creo que el malloc esté fallando (también fallaría al principio, no en 1 minuto). ¿Ideas sobre lo que está pasando?
Liam

Muchos sistemas, incluido Linux, realizan una asignación optimista. Por ejemplo, si solicita 1 Gb, se lo "dará", pero cuando realmente vaya a usarlo, y si el sistema no puede encontrarlo, se bloqueará. Si este fuera el caso, probablemente se estrellaría en el memset. El minuto que está tomando es el tiempo que pasa tratando de fusionar el montón en un bloque contiguo. También verifique que su sistema sea sizeof (bool) == 1. Si es == 4, entonces puedo reescribir esto para usar char.

Ya lo revisé. Bool es de 1 byte. ¿Es posible simplemente pedir 2 * 10 ^ 9 bytes de memoria en la pila? Es decir, declaro una variable global que (en gcc) creo que se iniciará a 0. Esto, en charcambio, creo que debería usarse .
Liam

1
@Liam Difícil de decir. El desbordamiento de entero firmado es un comportamiento indefinido, por lo que sin mirar el ensamblado generado, es difícil predecir lo que hizo el compilador.
Dennis

2

Julia, 1m 21.1329s

Me gustaría llegar a algo un poco más rápido, pero por ahora aquí hay una implementación bastante ingenua del Tamiz de Eratóstenes.

function eratos(n::Int64)
    sieve = trues(n)
    sieve[1] = false
    for p = 2:isqrt(n)
        @inbounds sieve[p] || continue
        for i = 2:n÷p
            @inbounds sieve[p*i] = false
        end
    end
    return sum(sieve)
end

const x = parse(Int64, ARGS[1])

println(eratos(x))

Obtenga la última versión de Julia para su sistema aquí . Asegúrese de que el ejecutable de Julia esté en su camino. Guarde el código como sieve.jly ejecútelo desde la línea de comando como julia sieve.jl N, donde Nestá la entrada.

Horarios oficiales

real    1m21.227s
user    1m20.755s
sys 0m0.576s

real    1m20.944s
user    1m20.426s
sys 0m0.640s

real    1m21.052s
user    1m20.581s
sys 0m0.573s

real    1m21.328s
user    1m20.862s
sys 0m0.570s

real    1m21.253s
user    1m20.780s
sys 0m0.588s

real    1m20.925s
user    1m20.460s
sys 0m0.576s

real    1m21.011s
user    1m20.512s
sys 0m0.601s

real    1m21.011s
user    1m20.550s
sys 0m0.564s

real    1m20.875s
user    1m20.409s
sys 0m0.569s

real    1m21.703s
user    1m21.088s
sys 0m0.701s

1
Implementé el Tamiz de Atkin y mi implementación para eso es más lenta. >: U
Alex A.

@Liam Whoa. Me pregunto por qué los horarios oficiales son mucho más largos que los no oficiales. Los horarios oficiales son bastante horribles.
Alex A.

Bueno, los horarios oficiales son para todos los casos de puntuación juntos. Los no oficiales van número por número. Además, mi computadora probablemente no sea tan rápida como la tuya.
Liam

@Liam Oh, eso tiene más sentido. Dang, pensé que esto era decente. Oh bien, regresa a la mesa de dibujo.
Alex A.

Estoy a punto de robar el algoritmo de Dennis ... solo para poder entender lo rápido que es.
Liam

2

Java, 42.663122s * (3 de marzo de 2016)

* esto fue programado internamente por el programa (aunque en la computadora del OP)

public class PrimeCounter
{
public static final String START_CODE="=",
TEST_FORMAT="Input = %d , Output = %d , calculated in %f seconds%n",
PROMPT="Enter numbers to compute pi(x) for (Type \""+START_CODE+"\" to start):%n",
WAIT="Calculating, please wait...%n",
WARNING="Probably won't work with values close to or more than 2^31%n",
TOTAL_OUTPUT_FORMAT="Total time for all inputs is %f seconds%n";
public static final int NUM_THREADS=16,LOW_LIM=1,HIGH_LIM=1<<28;
private static final Object LOCK=new Lock();
private static final class Lock{}
/**
 * Generates and counts primes using an optimized but naive iterative algorithm.
 * Uses MultiThreading for arguments above LOW_LIM
 * @param MAX : argument x for pi(x), the limit to which to generate numbers.
 */
public static long primeCount(long MAX){
    long ctr=1;
    if(MAX<1<<7){
        for(long i=3;i<=MAX;i+=2){
            if(isPrime(i))++ctr;
        }
    }else{
        long[] counts=new long[NUM_THREADS];
        for(int i=0;i<NUM_THREADS;++i){
            counts[i]=-1;
        }
        long range=Math.round((double)MAX/NUM_THREADS);
        for(int i=0;i<NUM_THREADS;++i){
            long start=(i==0)?3:i*range+1,end=(i==NUM_THREADS-1)?MAX:(i+1)*range;
            final int idx=i;
            new Thread(new Runnable(){
                    public void run(){
                        for(long j=start;j<=end;j+=2){
                            if(isPrime(j))++counts[idx];
                        }
                    }
                }).start();
        }
        synchronized(LOCK){
            while(!completed(counts)){
                try{
                    LOCK.wait(300);}catch(InterruptedException ie){}
            }
            LOCK.notifyAll();
        }
        for(long count:counts){
            ctr+=count;
        }
        ctr+=NUM_THREADS;
    }
    return ctr;
}

/**
 * Checks for completion of threads
 * @param array : The array containing the completion data
 */
private static boolean completed(long[] array){
    for(long i:array){
        if(i<0)return false;
    }return true;
}

/**
 * Checks if the parameter is prime or not.
 * 2,3,5,7 are hardcoded as factors.
 * @param n : the number to check for primality
 */
private static boolean isPrime(long n){
    if(n==2||n==3||n==5||n==7)return true;
    else if(n%2==0||n%3==0||n%5==0||n%7==0)return false;
    else{
        for(long i=11;i<n;i+=2){
            if(n%i==0)return false;
        }
        return true;
    }
}

/**
 * Calculates primes using the atandard Sieve of Eratosthenes.
 * Uses 2,3,5,7 wheel factorization for elimination (hardcoded for performance reasons)
 * @param MAX : argument x for pi(x)
 * Will delegate to <code>primeCount(long)</code> for MAX<LOW_LIM and to <code>bitPrimeSieve(long)</code>
 * for MAX>HIGH_LIM, for performance reasons.
 */
public static long primeSieve(long MAX){
    if(MAX<=1)return 0;
    else if(LOW_LIM>0&&MAX<LOW_LIM){return primeCount(MAX);}
    else if(HIGH_LIM>0&&MAX>HIGH_LIM){return bitPrimeSieve(MAX);}
    int n=(int)MAX;
    int sn=(int)Math.sqrt(n),ctr=2;
    if(sn%2==0)--sn;
    boolean[]ps=new boolean[n+1];
    for(int i=2;i<=n;++i){
        if(i==2||i==3||i==5||i==7)ps[i]=true;
        else if(i%2!=0&&i%3!=0&&i%5!=0&&i%7!=0)ps[i]=true;
        else ++ctr;
    }
    for(int i=(n>10)?11:3;i<=sn;i+=2){
        if(ps[i]){
            for(int j=i*i;j<=n;j+=i){
                if(ps[j]){ ps[j]=false;++ctr;}
            }
        }
    }
    return (n+1-ctr);
}
/**
 * Calculates primes using bitmasked Sieve of Eratosthenes.
 * @param MAX : argument x for pi(x)
 */
public static long bitPrimeSieve(long MAX) {
    long SQRT_MAX = (long) Math.sqrt(MAX);
    if(SQRT_MAX%2==0)--SQRT_MAX;
    int MEMORY_SIZE = (int) ((MAX+1) >> 4);
    byte[] array = new byte[MEMORY_SIZE];
    for (long i = 3; i <= SQRT_MAX; i += 2) {
        if ((array[(int) (i >> 4)] & (byte) (1 << ((i >> 1) & 7))) == 0) {
            for(long j=i*i;j<=MAX;j+=i<<1) {
                if((array[(int) (j >> 4)] & (byte) (1 << ((j >> 1) & 7))) == 0){
                    array[(int) (j >> 4)] |= (byte) (1 << ((j >> 1) & 7));
                }
            }
        }
    }
    long pi = 1;
    for (long i = 3; i <= MAX; i += 2) {
        if ((array[(int) (i >> 4)] & (byte) (1 << ((i >> 1) & 7))) == 0) {
            ++pi;
        }
    }
    return pi;
}
/**
 * Private testing and timer function
 * @param MAX : input to be passed on to <code>primeSieve(long)</code>
 */
private static long sieveTest(long MAX){
    long start=System.nanoTime();
    long ps=primeSieve(MAX);
    long end=System.nanoTime();
    System.out.format(TEST_FORMAT,MAX,ps,((end-start)/1E9));
    return end-start;
}
/**
 * Main method: accepts user input and shows total execution time taken
 * @param args : The command-line arguments
 */
public static void main(String[]args){
    double total_time=0;
    java.util.Scanner sc=new java.util.Scanner(System.in);
    java.util.ArrayList<Long> numbers=new java.util.ArrayList<>();
    System.out.format(PROMPT+WARNING);
    String line=sc.nextLine();
    while(!line.equals(START_CODE)/*sc.hasNextLine()&&Character.isDigit(line.charAt(0))*/){
        numbers.add(Long.valueOf(line));
        line=sc.nextLine();
    }
    System.out.format(WAIT);
    for(long num:numbers){
        total_time+=sieveTest(num);
    }
    System.out.format(TOTAL_OUTPUT_FORMAT,total_time/1e9);
}
}

Sigue la gran tradición PPCG de código autodocumentado (aunque no en el sentido literal: p).

Esto es para demostrar que Java puede ser lo suficientemente rápido como para ser competitivo con otros lenguajes de VM cuando se utilizan algoritmos similares.

Ejecutar información

Ejecútelo como tendría la respuesta de @ CoolestVeto, pero el mío no necesita argumentos de línea de comandos, puede obtenerlos de STDIN.

Ajustar el NUM_THREADS constante para configurarlo al doble de tu número de núcleos nativos para obtener el máximo rendimiento (como he observado: en mi caso, tengo 8 núcleos virtuales, por lo que está configurado en 16, el OP podría querer 12 para su procesador hexa-core).

Cuando ejecuté estas pruebas, utilicé JDK 1.7.0.45 con BlueJ 3.1.6 (IntelliJ se estaba actualizando) en Windows 10 Enterpise x64 en una computadora portátil ASUS K55VM (Core i7 3610QM, 8GB RAM). Google Chrome 49.0 de 64 bits con 1 pestaña (PPCG) abierta y QBittorrent descargando 1 archivo se estaban ejecutando en segundo plano, 60% de uso de RAM al inicio de la ejecución.

Básicamente,

javac PrimeCounter.java
java PrimeCounter

El programa lo guiará a través del resto.

El tiempo lo hace Java incorporado System.nanoTime().

Detalles del algoritmo:

Tiene 3 variantes para diferentes casos de uso: una versión ingenua como @ CoolestVeto (pero multiproceso) para entradas por debajo de 2 ^ 15, y un tamiz de Eratóstenes con máscara de bits con eliminación impar para entradas por encima de 2 ^ 28, y un tamiz normal de Eratóstenes con un Factorización de rueda 2/3/5/7 para la eliminación previa de múltiples.

Utilizo el tamiz enmascarado para evitar argumentos especiales de JVM para los casos de prueba más grandes. Si eso se puede hacer, se puede eliminar la sobrecarga para el cálculo del recuento en la versión enmascarada de bits.

Aquí está la salida:

Enter numbers to compute pi(x) for (Type "=" to start):
Probably won't work with values close to or more than 2^31
41500
24850000
40550000
99820000
660000000
1240000000
1337000000
1907000000
=
Calculating, please wait...
Input = 41500 , Output = 4339 , calculated in 0.002712 seconds
Input = 24850000 , Output = 1557132 , calculated in 0.304792 seconds
Input = 40550000 , Output = 2465109 , calculated in 0.523999 seconds
Input = 99820000 , Output = 5751639 , calculated in 1.326542 seconds
Input = 660000000 , Output = 34286170 , calculated in 4.750049 seconds
Input = 1240000000 , Output = 62366021 , calculated in 9.160406 seconds
Input = 1337000000 , Output = 66990613 , calculated in 9.989093 seconds
Input = 1907000000 , Output = 93875448 , calculated in 14.832107 seconds
Total time for all inputs is 40.889700 seconds

Enviar solo el resultado de pi (n) (sin avisos) puede ahorrar algo de tiempo, porque STDOUT es ... bueno, digamos que podría ser un poco más rápido.
user48538

@ zyabin101, si alguien tuviera la paciencia para revisar el código, él / ella entendería que se ha tenido en cuenta la latencia STDOUT.
Tamoghna Chowdhury

También por tiempo, he estado enviando stdout a / dev / null
Liam

@Liam Supongo que tendrás que hacer una excepción en mi caso, entonces. Podría ajustar el método principal para los argumentos de la línea de comandos, pero el programa se temporiza de todos modos. Compruébalo de todos modos. ¿Por favor?
Tamoghna Chowdhury

Por su puesto que lo hare. Lo haré mañana. Si tengo problemas, te enviaré un ping en el chat
Liam

2

Python 3

import sys

sys.setrecursionlimit(sys.maxsize)

n = int(sys.argv[-1])

if n < 4:
    print(0 if n < 2 else n-1)
    exit()

p = [0, 0] + [True] * n

i = 0
while i < pow(n, 0.5):
    if p[i]:
        j = pow(i, 2)
        while j < n:
            p[j] = False
            j += i
    i += 1

print(sum(p) - 2)

Utiliza el tamiz de Eratóstenes. Corre a un promedio de 8.775sdónde n = 10^7. Hasta el momento, utilicé el timecomando incorporado . Por ejemplo:

$ time python3 test.py 90
24

real    0m0.045s
user    0m0.031s
 sys    0m0.010s

Es el tamiz! No podía usar esto en Java porque no le gustaba la cantidad de memoria que usaba una matriz booleana. RE:
Addison Crump

error de memoria en los casos más grandes.
Liam

Que casos Creo que lo he arreglado. @Liam
Zach Gates

2
@VoteToClose Entonces no use una matriz booleana. Use una matriz de enteros y un desplazamiento / enmascaramiento de bits, y cada bit representa un valor booleano.
mbomb007

AttributeError: 'module' object has no attribute 'maxint'
Dennis

1

C ++, 9.3221s (29 de febrero de 2016)

#include <cstdint>
#include <vector>
#include <iostream>
#include <limits>
#include <cmath>
#include <array>
// uses posix ffsll
#include <string.h>
#include <algorithm>

constexpr uint64_t wheel_width = 2;
constexpr uint64_t buf_size = 1<<(10+6);
constexpr uint64_t dtype_width = 6;
constexpr uint64_t dtype_mask = 63;
constexpr uint64_t buf_len = ((buf_size*wheel_width)>>dtype_width);

typedef std::vector<uint64_t> buf_type;

void mark_composite(buf_type& buf, uint64_t prime,
                    std::array<uint64_t, 2>& poff,
                    uint64_t seg_start, uint64_t max_j)
{
  const auto p = 2*prime;
  for(uint64_t k = 0; k < wheel_width; ++k)
  {
    for(uint64_t j = 2*poff[k]+(k==0); j < max_j; j += p)
    {
      buf[(j-seg_start)>>dtype_width] |= 1ULL << (j & dtype_mask);
      poff[k] += prime;
    }
  }
}

uint64_t num_primes(uint64_t n)
{
  uint64_t res = (n >= 2) + (n >= 3);
  if(n >= 5)
  {
    buf_type buf(buf_len);
    // compute and store primes < sqrt(n)
    const uint64_t store_max = ceil(sqrt(n));

    // only primes >= 5
    std::vector<uint64_t> primes; // 5,7,11
    std::vector<std::array<uint64_t, 2> > poffs;// {{3,0},{0,5},{8,1}};
    primes.reserve(ceil(1.25506*store_max/log(store_max)));
    poffs.reserve(ceil(1.25506*store_max/log(store_max)));
    uint64_t seg_start = 0;
    uint64_t seg_min = 5;
    constexpr uint64_t seg_len = 6*buf_size;///wheel_width;
    constexpr uint64_t limit_i_max = 0xfffffffe00000001ULL;
    const uint64_t num_segs = 1+(n-seg_min)/seg_len;
    const uint64_t nj = (n-seg_min)/3+1;
    for(uint64_t seg = 0; seg < num_segs; ++seg)
    {
      std::fill(buf.begin(), buf.end(), 0);
      // mark off small primes
      const uint64_t limit_i = std::min<uint64_t>((((seg_len+seg_min) >= limit_i_max) ?
                                                   std::numeric_limits<uint32_t>::max() :
                                                   ceil(sqrt(seg_len+seg_min))),
                                                  store_max);
      uint64_t max_j = std::min(seg_start+(buf_len<<dtype_width), nj);
      for(uint64_t i = 0; i < primes.size() && primes[i] <= limit_i; ++i)
      {
        mark_composite(buf, primes[i], poffs[i], seg_start, max_j);
      }
      // sieve
      uint64_t val;
      const uint64_t stop = std::min(seg_min+seg_len, n);
      for(uint64_t i = ffsll(~(buf[0]))-((~buf[0]) != 0)+64*((~buf[0]) == 0);
            (val = 6ULL*(i>>1)+seg_min+2ULL*(i&1ULL)) < stop;)
      {
        if(!(buf[i>>dtype_width] & (1ULL << (i & dtype_mask))))
        {
          if(val <= store_max)
          {
            // add prime and poffs
            primes.push_back(val);
            poffs.emplace_back();
            poffs.back()[0] = (val*val-1)/6-1;
            if(i&1)
            {
              // 6n+1 prime
              poffs.back()[1] = (val*val+4*val-5)/6;
            }
            else
            {
              // 6n+5 prime
              poffs.back()[1] = (val*val+2*val-5)/6;
            }
            // mark-off multiples
            mark_composite(buf, val, poffs.back(), seg_start, max_j);
          }
          ++res;
          ++i;
        }
        else
        {
          uint64_t mask = buf[i>>dtype_width]>>(i&dtype_mask);
          const int64_t inc = ffsll(~mask)-((~mask) != 0)+64*((~mask) == 0);
          i += inc;
        }
      }
      seg_min += seg_len;
      seg_start += buf_size*wheel_width;
    }
  }
  return res;
}

int main(int argc, char** argv)
{
  if(argc <= 1)
  {
    std::cout << "usage: " << argv[0] << " n\n";
    return -1;
  }
  std::cout << num_primes(std::stoll(argv[1])) << '\n';
}

Utiliza un tamiz segmentado de Eratóstenes con una factorización de rueda de 6 para omitir todos los múltiplos de 2/3. Utiliza POSIXffsll para omitir valores compuestos consecutivos.

Potencialmente podría acelerarse haciendo que el tamiz segmentado funcione en paralelo.

Compilar:

g++ -std=c++11 -o sieve -O3 -march=native sieve.cpp

horarios no oficiales

Programado con un Intel i5-6600k en Ubuntu 15.10, tomó el caso 1907000000 2.363s.

41500
4339

real    0m0.001s
user    0m0.000s
sys     0m0.000s

24850000
1557132

real    0m0.036s
user    0m0.032s
sys     0m0.000s

40550000
2465109

real    0m0.056s
user    0m0.052s
sys     0m0.000s

99820000
5751639

real    0m0.149s
user    0m0.144s
sys     0m0.000s

660000000
34286170

real    0m0.795s
user    0m0.788s
sys     0m0.000s

1240000000
62366021

real    0m1.468s
user    0m1.464s
sys     0m0.000s

1337000000
66990613

real    0m1.583s
user    0m1.576s
sys     0m0.004s

1907000000
93875448

real    0m2.363s
user    0m2.356s
sys     0m0.000s

Tiempos oficiales

real    0m9.415s
user    0m9.414s
sys 0m0.014s

real    0m9.315s
user    0m9.315s
sys 0m0.013s

real    0m9.307s
user    0m9.309s
sys 0m0.012s

real    0m9.333s
user    0m9.330s
sys 0m0.017s

real    0m9.288s
user    0m9.289s
sys 0m0.012s

real    0m9.319s
user    0m9.318s
sys 0m0.015s

real    0m9.285s
user    0m9.284s
sys 0m0.015s

real    0m9.342s
user    0m9.342s
sys 0m0.014s

real    0m9.305s
user    0m9.305s
sys 0m0.014s

real    0m9.312s
user    0m9.313s
sys 0m0.012s
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.