C, 0.026119 초 (2016 년 3 월 12 일)
#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);
}
이것은 Meissel-Lehmer 방법을 사용합니다 .
타이밍
내 컴퓨터 에서 결합 된 테스트 사례에 대해 약 5.7 밀리 초가 되었습니다. 이것은 1867MHz DDR3 RAM이있는 Intel Core i7-3770에 있으며 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
때문에 분산이 너무 가지고 , 나는 비공식 실행 시간에 대한 프로그램 내에서 타이밍을 사용하고 있습니다. 이것은 결합 된 런타임의 평균을 계산 한 스크립트입니다.
#!/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
공식 시간
이번에는 스코어 케이스를 1000 번 수행합니다.
real 0m28.006s
user 0m15.703s
sys 0m14.319s
작동 원리
공식
를 양의 정수라고 하자 .엑스
각 양의 정수 는 다음 조건 중 하나를 정확히 만족시킵니다.n ≤ x
n = 1
p [ 1 , 3 √엔 은 에서 소수 로 나눌 수 있습니다.피[ 1 , x−−√삼]
p q ( 3 √n = p q , 와 (반드시 구별되지 않음)에있는 소수 .피큐( x−−√삼, x2−−√삼)
n > 3 √엔 은 소수이고n > x−−√삼
하자 소수의 수가 나타내는 되도록 . 네 번째 범주 에는 숫자가 있습니다.p p ≤ y π ( x ) − π ( 3 √π( y)피p ≤ yπ( x ) − π( x−−√삼)
하자 양의 정수를 나타낸다 량 정확하게의 산물 아닌 제 중 소수를 소수를. 거기 세 번째 카테고리에 분류 번호.피케이( y, C )m ≤ y케이씨피2( x , π( x−−√삼) )
마지막으로, 는 첫 번째 소수 에 대해 공동 소수 인 양의 정수 의 양을 나타냅니다 . 거기 제 카테고리에 속하는 번호.ϕ ( y, C )k ≤ y씨x − ϕ ( x , π( x−−√삼) )
모든 카테고리에 숫자 가 있으므로엑스
1 + x − ϕ ( x , π( x−−√삼) ) + P2( x , π( x−−√삼) ) + π( x ) − π( x−−√삼) = x
따라서,
π(x)=ϕ(x,π(x−−√3))+π(x−−√3)−1−P2(x,π(x−−√3))
및 가 필요한 경우 세 번째 범주의 숫자는 고유 한 표현을 갖습니다 . 이런 식으로 프라임 와 의 곱은 인 경우에만 세 번째 범주 에 있으므로 에 대한 가능한 값 의 고정 값 및 여기서 는 소수를 나타냅니다.p≤qp≤x−−√pqx−−√3<p≤q≤xpπ(xp)−π(p)+1qpP2(x,π(x−−√3))=∑π(x√3)<k≤π(x√)(π(xpk)−π(pk)+1)pkkth
마지막으로, 첫 소수 와 동일 하지 않은 모든 양의 정수 는 로 고유 한 방식으로 표현 될 수 있습니다 . 여기서 는 의 가장 작은 소수입니다 . 이런 식으로 , 는 첫 번째 소수에 대한 코 프라임입니다.n≤ycn=pkfpknk≤cfk−1
이것은 재귀 공식 집니다. 특히 이면 합은 비어 있으므로 입니다.ϕ(y,c)=y−∑1≤k≤cϕ(ypk,k−1)c=0ϕ(y,0)=y
이제 첫 번째 소수 (백만 대 수십억) 만 생성하여 를 계산할 수있는 공식이 있습니다 .π(x)π(x2−−√3)
연산
를 계산해야합니다 . 여기서 는 만큼 낮아질 수 있습니다 . 이를 수행하는 다른 방법이 있지만 (공식을 재귀 적으로 적용하는 것과 같이) 가장 빠른 방법은 모든 소수를 까지 열거하는 것 같습니다 . 이것은 에라토스테네스의 체로 수행 할 수 있습니다.π(xp)px−−√3x2−−√3
먼저 모든 소수를 식별하고 에 저장하고 및 를 동시에 계산합니다. 그런 다음 에서 모든 에 대해 를 계산하고 각 연속 몫까지의 소수를 계산합니다. .[1,x−−√]π(x−−√3)π(x−−√)xpkk(π(x−−√3),π(x−−√)]
또한 은 닫힌 , 어느 의 계산을 완료 할 수 있습니다 .∑π(x√3)<k≤π(x√)(−π(pk)+1)π(x√3)−π(x√))(π(x√3)+π(x√)−12P2(x,π(x−−√3))
알고리즘의 가장 비싼 부분 인 의 계산을 남깁니다 . 단순히 재귀 수식을 사용하면 를 계산하기 위해 함수 호출 이 필요합니다 .ϕ2cϕ(y,c)
우선, 의 모든 값에 대한 , 그래서 . 그 자체만으로도이 관측으로 계산이 가능해집니다. 이것은 이하의 숫자 가 10 개의 고유 소수의 곱보다 작기 때문에 압도적 인 대다수의 소환사가 사라지기 때문입니다.ϕ(0,c)=0cϕ(y,c)=y−∑1≤k≤c,pk≤yϕ(ypk,k−1)2⋅109
또한 와 정의의 첫 번째 요약을 그룹화 하면 대체 공식 . 따라서 고정 및 적절한 값에 대해 사전 계산 를 수행 하면 나머지 함수 호출과 관련 계산이 대부분 절약됩니다.yc′ϕϕ(y,c)=ϕ(y,c′)−∑c′<k≤c,pk≤yϕ(ypk,k−1)ϕ(y,c′)c′y
만약 후 에서 정수 보낸 없음의 배수임을 는 정확히 와 합니다. 또한 이므로 .mc=∏1≤k≤cpkϕ(mc,c)=φ(mc)[1,mc]p1,⋯,pcmcgcd(z+mc,mc)=gcd(z,mc)ϕ(y,c)=ϕ(⌊ymc⌋mc,c)+ϕ(y
오일러의 고문 함수는 곱셈이므로 이며 가장 쉬운 방법은 유도하도록 모두 만의 값을 미리 계산하여 의 .φ(mc)=∏1≤k≤cφ(pk)=∏1≤k≤c(pk−1)ϕ(y,c)yy[0,mc)
또한 설정 하면 습니다. Lehmer의 논문에서 원래 정의. 이것은 우리에게 미리 계산하는 간단한 방법 제공 값의 증가에 대한 .ϕ ( y , c ) = ϕ ( y , c - 1 ) -c′=c−1ϕ(y,c)ϕ(y,c)=ϕ(y,c−1)−ϕ(ypc,c−1)ϕ(y,c)c
사전 계산에 대한 또 의 어느 낮은 값 , 우리는 낮은 값을 미리 계산하는 것이다 이 소정 임계치 아래로 떨어지는 후 짧은 순환을 절단.c yϕ(y,c)cy
이행
이전 섹션에서는 코드의 대부분을 다룹니다. 남아있는 중요한 세부 사항 중 하나는 함수의 나누기 Phi
가 수행되는 방법입니다.
계산 하려면 첫 번째 소수 로만 구분 하면되므로 대신 함수를 사용할 수 있습니다 . 단순히 를 소수 나누는 대신 , 에 를 곱하고 대신 를 . x64 에서 정수 곱셈이 구현되는 방식 때문에 로 나누지 않아도됩니다. 의 상위 64 비트는 자체 레지스터에 저장됩니다.πϕypydp≈π(x−−√3)fastdiv
ypydp≈264pyp 264일dpy264264dpy
이 방법은 사전 계산 필요로 하며 이는 직접 계산하는 것보다 빠르지 않습니다 . 그러나 동일한 소수로 반복해서 나누고 나누는 것보다 나누기가 훨씬 느리기 때문에 중요한 속도 향상이 발생합니다. 이 알고리즘에 대한 자세한 내용과 공식적인 증거는 곱셈을 사용하는 Invariant Integers의 Division 에서 찾을 수 있습니다 .dpyp