10億未満の素数のリストを生成しようとしています。私はこれを試していますが、この種の構造はかなりくだらないです。助言がありますか?
a <- 1:1000000000
d <- 0
b <- for (i in a) {for (j in 1:i) {if (i %% j !=0) {d <- c(d,i)}}}
これは、Rでの Sieve of Eratosthenes アルゴリズムの実装です。
sieve <- function(n)
{
n <- as.integer(n)
if(n > 1e6) stop("n too large")
primes <- rep(TRUE, n)
primes[1] <- FALSE
last.prime <- 2L
for(i in last.prime:floor(sqrt(n)))
{
primes[seq.int(2L*last.prime, n, last.prime)] <- FALSE
last.prime <- last.prime + min(which(primes[(last.prime+1):n]))
}
which(primes)
}
sieve(1000000)
ジョージドンタスによって投稿されたそのふるいは良い出発点です。これは、元のバージョンの30秒とは対照的に、0.095秒の1e6素数の実行時間を持つはるかに高速なバージョンです。
sieve <- function(n)
{
n <- as.integer(n)
if(n > 1e8) stop("n too large")
primes <- rep(TRUE, n)
primes[1] <- FALSE
last.prime <- 2L
fsqr <- floor(sqrt(n))
while (last.prime <= fsqr)
{
primes[seq.int(2L*last.prime, n, last.prime)] <- FALSE
sel <- which(primes[(last.prime+1):(fsqr+1)])
if(any(sel)){
last.prime <- last.prime + min(sel)
}else last.prime <- fsqr+1
}
which(primes)
}
以下に、Rで可能な限り高速にコーディングされたいくつかの代替アルゴリズムを示します。これらはふるいよりも低速ですが、質問者の元の投稿よりもはるかに高速です。
これは、modを使用するが、ベクトル化された再帰関数です。ほぼ瞬時に1e5に戻り、2秒未満で1e6に戻ります。
primes <- function(n){
primesR <- function(p, i = 1){
f <- p %% p[i] == 0 & p != p[i]
if (any(f)){
p <- primesR(p[!f], i+1)
}
p
}
primesR(2:n)
}
次のものは再帰的ではなく、再び高速です。以下のコードは、私のマシンで約1.5秒で1e6までプライミングします。
primest <- function(n){
p <- 2:n
i <- 1
while (p[i] <= sqrt(n)) {
p <- p[p %% p[i] != 0 | p==p[i]]
i <- i+1
}
p
}
ところで、spuRsパッケージには、Eのふるいを含む、いくつかの主要な検索機能があります。それらの速度がどのようなものかを確認していません。
そして、私が非常に長い答えを書いている間...これが1つの値が素数であるかどうかRをチェックインする方法です。
isPrime <- function(x){
div <- 2:ceiling(sqrt(x))
!any(x %% div == 0)
}
(狂った数学に入ることなく)すべての素数を生成するために私が知っている最良の方法は、 エラトステネスのふるい を使用することです。
実装は非常に簡単で、除算やモジュラスを使用せずに素数を計算できます。唯一の欠点は、メモリを大量に消費することですが、メモリを改善するためにさまざまな最適化を行うことができます(たとえば、すべての偶数を無視します)。
OPは、10億未満のすべての素数を生成するように要求しました。これまでに提供されたすべての回答は、これを実行できないか、実行に時間がかかるか、現在Rでは利用できません(@Charlesによる answer を参照)。パッケージRcppAlgos
(私は作成者です)は、1つのスレッドのみを使用して_1 second
_のすぐ上で要求された出力を生成することができます。これは、エラトステネスのセグメント化されたふるいに基づいています Kim Walisch 。
_library(RcppAlgos)
system.time(primeSieve(1e9)) ## using 1 thread
user system elapsed
1.099 0.077 1.176
_
また、最近のバージョン(つまり、_>= 2.3.0
_)では、複数のスレッドを利用してさらに高速に生成できます。たとえば、今では0.5秒以内に最大10億の素数を生成できます!
_system.time(primeSieve(10^9, nThreads = 8))
user system elapsed
2.046 0.048 0.375
_
_library(schoolmath)
library(primefactr)
library(sfsmisc)
library(primes)
library(numbers)
library(spuRs)
library(randtoolbox)
library(matlab)
## and 'sieve' from @John
_
始める前に、schoolmath
で@Henrikが指摘した問題がまだ存在していることに注意してください。観察する:
_## 1 is NOT a prime number
schoolmath::primes(start = 1, end = 20)
[1] 1 2 3 5 7 11 13 17 19
## This should return 1, however it is saying that 52
## "prime" numbers less than 10^4 are divisible by 7!!
sum(schoolmath::primes(start = 1, end = 10^4) %% 7L == 0)
[1] 52
_
重要なのは、この時点で素数を生成するためにschoolmath
を使用しないことです(作成者に不快感を与えることはありません。
randtoolbox
は非常に効率的であるように見えるので、見てみましょう。観察する:
_library(microbenchmark)
## the argument for get.primes is for how many prime numbers you need
## whereas most packages get all primes less than a certain number
microbenchmark(priRandtoolbox = get.primes(78498),
priRcppAlgos = RcppAlgos::primeSieve(10^6), unit = "relative")
Unit: relative
expr min lq mean median uq max neval
priRandtoolbox 1.00000 1.00000 1.000000 1.000000 1.000000 1.0000000 100
priRcppAlgos 12.79832 12.55065 6.493295 7.355044 7.363331 0.3490306 100
_
よく見ると、それは本質的にルックアップテーブルであることがわかります(ソースコードのファイル_randtoolbox.c
_にあります)。
_#include "primes.h"
void reconstruct_primes()
{
int i;
if (primeNumber[2] == 1)
for (i = 2; i < 100000; i++)
primeNumber[i] = primeNumber[i-1] + 2*primeNumber[i];
}
_
ここで、_primes.h
_は、 "素数間の差の半分"の配列を含むヘッダーファイルです。したがって、素数(つまり、最初の10万個の素数)を生成するためのその配列内の要素の数によって制限されます。小さい素数(_1,299,709
_未満(つまり、100,000番目の素数))のみを使用していて、nth
素数を必要とするプロジェクトで作業している場合、randtoolbox
は行く方法。
以下では、残りのパッケージでベンチマークを実行します。
_microbenchmark(priRcppAlgos = RcppAlgos::primeSieve(10^6),
priNumbers = numbers::Primes(10^6),
priSpuRs = spuRs::primesieve(c(), 2:10^6),
priPrimes = primes::generate_primes(1, 10^6),
priPrimefactr = primefactr::AllPrimesUpTo(10^6),
priSfsmisc = sfsmisc::primes(10^6),
priMatlab = matlab::primes(10^6),
priJohnSieve = sieve(10^6),
unit = "relative")
Unit: relative
expr min lq mean median uq max neval
priRcppAlgos 1.000000 1.00000 1.00000 1.000000 1.00000 1.00000 100
priNumbers 21.550402 23.19917 26.67230 23.140031 24.56783 53.58169 100
priSpuRs 232.682764 223.35847 233.65760 235.924538 236.09220 212.17140 100
priPrimes 46.591868 43.64566 40.72524 39.106107 39.60530 36.47959 100
priPrimefactr 39.609560 40.58511 42.64926 37.835497 38.89907 65.00466 100
priSfsmisc 9.271614 10.68997 12.38100 9.761438 11.97680 38.12275 100
priMatlab 21.756936 24.39900 27.08800 23.433433 24.85569 49.80532 100
priJohnSieve 10.630835 11.46217 12.55619 10.792553 13.30264 38.99460 100
_
_microbenchmark(priRcppAlgos = RcppAlgos::primeSieve(10^7),
priNumbers = numbers::Primes(10^7),
priSpuRs = spuRs::primesieve(c(), 2:10^7),
priPrimes = primes::generate_primes(1, 10^7),
priPrimefactr = primefactr::AllPrimesUpTo(10^7),
priSfsmisc = sfsmisc::primes(10^7),
priMatlab = matlab::primes(10^7),
priJohnSieve = sieve(10^7),
unit = "relative", times = 20)
Unit: relative
expr min lq mean median uq max neval
priRcppAlgos 1.00000 1.00000 1.00000 1.00000 1.00000 1.00000 20
priNumbers 30.57896 28.91780 31.26486 30.47751 29.81762 40.43611 20
priSpuRs 533.99400 497.20484 490.39989 494.89262 473.16314 470.87654 20
priPrimes 125.04440 114.71349 112.30075 113.54464 107.92360 103.74659 20
priPrimefactr 52.03477 50.32676 52.28153 51.72503 52.32880 59.55558 20
priSfsmisc 16.89114 16.44673 17.48093 16.64139 18.07987 22.88660 20
priMatlab 30.13476 28.30881 31.70260 30.73251 32.92625 41.21350 20
priJohnSieve 18.25245 17.95183 19.08338 17.92877 18.35414 32.57675 20
_
次の2つのベンチマークでは、RcppAlgos
、numbers
、sfsmisc
、matlab
、および@Johnによるsieve
関数のみを考慮します。
_microbenchmark(priRcppAlgos = RcppAlgos::primeSieve(10^8),
priNumbers = numbers::Primes(10^8),
priSfsmisc = sfsmisc::primes(10^8),
priMatlab = matlab::primes(10^8),
priJohnSieve = sieve(10^8),
unit = "relative", times = 20)
Unit: relative
expr min lq mean median uq max neval
priRcppAlgos 1.00000 1.00000 1.00000 1.00000 1.00000 1.00000 20
priNumbers 35.64097 33.75777 32.83526 32.25151 31.74193 31.95457 20
priSfsmisc 21.68673 20.47128 20.01984 19.65887 19.43016 19.51961 20
priMatlab 35.34738 33.55789 32.67803 32.21343 31.56551 31.65399 20
priJohnSieve 23.28720 22.19674 21.64982 21.27136 20.95323 21.31737 20
_
N.B. sieve
関数の条件if(n > 1e8) stop("n too large")
を削除する必要があります。
_## See top section
## system.time(primeSieve(10^9))
## user system elapsed
## 1.099 0.077 1.176 ## RcppAlgos single-threaded
## gc()
system.time(matlab::primes(10^9))
user system elapsed
31.780 12.456 45.549 ## ~39x slower than RcppAlgos
## gc()
system.time(numbers::Primes(10^9))
user system elapsed
32.252 9.257 41.441 ## ~35x slower than RcppAlgos
## gc()
system.time(sieve(10^9))
user system elapsed
26.266 3.906 30.201 ## ~26x slower than RcppAlgos
## gc()
system.time(sfsmisc::primes(10^9))
user system elapsed
24.292 3.389 27.710 ## ~24x slower than RcppAlgos
_
これらの比較から、nが大きくなるにつれて、RcppAlgos
のスケーリングが大幅に向上することがわかります。
_ _________________________________________________________
| | 1e6 | 1e7 | 1e8 | 1e9 |
| |---------|----------|-----------|-----------
| RcppAlgos | 1.00 | 1.00 | 1.00 | 1.00 |
| sfsmisc | 9.76 | 16.64 | 19.66 | 23.56 |
| JohnSieve | 10.79 | 17.93 | 21.27 | 25.68 |
| numbers | 23.14 | 30.48 | 32.25 | 34.86 |
| matlab | 23.43 | 30.73 | 32.21 | 38.73 |
---------------------------------------------------------
_
複数のスレッドを使用すると、違いはさらに劇的になります。
_microbenchmark(ser = primeSieve(1e6),
par = primeSieve(1e6, nThreads = 8), unit = "relative")
Unit: relative
expr min lq mean median uq max neval
ser 1.741342 1.492707 1.481546 1.512804 1.432601 1.275733 100
par 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 100
microbenchmark(ser = primeSieve(1e7),
par = primeSieve(1e7, nThreads = 8), unit = "relative")
Unit: relative
expr min lq mean median uq max neval
ser 2.632054 2.50671 2.405262 2.418097 2.306008 2.246153 100
par 1.000000 1.00000 1.000000 1.000000 1.000000 1.000000 100
microbenchmark(ser = primeSieve(1e8),
par = primeSieve(1e8, nThreads = 8), unit = "relative", times = 20)
Unit: relative
expr min lq mean median uq max neval
ser 2.914836 2.850347 2.761313 2.709214 2.755683 2.438048 20
par 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 20
microbenchmark(ser = primeSieve(1e9),
par = primeSieve(1e9, nThreads = 8), unit = "relative", times = 10)
Unit: relative
expr min lq mean median uq max neval
ser 3.081841 2.999521 2.980076 2.987556 2.961563 2.841023 10
par 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 10
_
そして、上記の表に、シリアル結果のそれぞれの中央値を掛けます。
_ _____________________________________________________________
| | 1e6 | 1e7 | 1e8 | 1e9 |
| |---------|----------|-----------|-----------
| RcppAlgos-Par | 1.00 | 1.00 | 1.00 | 1.00 |
| RcppAlgos-Ser | 1.51 | 2.42 | 2.71 | 2.99 |
| sfsmisc | 14.76 | 40.24 | 53.26 | 70.39 |
| JohnSieve | 16.32 | 43.36 | 57.62 | 76.72 |
| numbers | 35.01 | 73.70 | 87.37 | 104.15 |
| matlab | 35.44 | 74.31 | 87.26 | 115.71 |
-------------------------------------------------------------
_
_microbenchmark(priRcppAlgos = RcppAlgos::primeSieve(10^9, 10^9 + 10^6),
priNumbers = numbers::Primes(10^9, 10^9 + 10^6),
priPrimes = primes::generate_primes(10^9, 10^9 + 10^6),
unit = "relative", times = 20)
Unit: relative
expr min lq mean median uq max neval
priRcppAlgos 1.0000 1.0000 1.000 1.0000 1.0000 1.0000 20
priNumbers 115.3000 112.1195 106.295 110.3327 104.9106 81.6943 20
priPrimes 983.7902 948.4493 890.243 919.4345 867.5775 708.9603 20
_
_## primes less than 10 billion
system.time(tenBillion <- RcppAlgos::primeSieve(10^10, nThreads = 8))
user system elapsed
26.077 2.063 5.602
length(tenBillion)
[1] 455052511
## Warning!!!... Large object created
tenBillionSize <- object.size(tenBillion)
print(tenBillionSize, units = "Gb")
3.4 Gb
_
バージョン_2.3.0
_より前は、すべての大きさの数値に同じアルゴリズムを使用していました。これは、ほとんどのふるい素数が各セグメントに少なくとも1つの倍数を持っている場合、小さい数でも問題ありません(通常、セグメントサイズは_L1 Cache ~32KiB
_のサイズによって制限されます)。ただし、より大きな数を扱う場合、ふるい素数には、セグメントごとに1つ未満の倍数を持つ多くの数が含まれます。キャッシュを汚染する多くの価値のないチェックを実行しているため、この状況では多くのオーバーヘッドが発生します。したがって、数が非常に多い場合、素数の生成がはるかに遅くなります。バージョン_2.2.0
_を観察します( 古いバージョンのRパッケージのインストール を参照):
_## Install version 2.2.0
## packageurl <- "http://cran.r-project.org/src/contrib/Archive/RcppAlgos/RcppAlgos_2.2.0.tar.gz"
## install.packages(packageurl, repos=NULL, type="source")
system.time(old <- RcppAlgos::primeSieve(1e15, 1e15 + 1e9))
user system elapsed
7.932 0.134 8.067
_
そして、元々 TomásOliveira によって開発されたキャッシュフレンドリーな改善を使用すると、劇的な改善が見られます。
_## Reinstall current version from CRAN
## install.packages("RcppAlgos"); library(RcppAlgos)
system.time(cacheFriendly <- primeSieve(1e15, 1e15 + 1e9))
user system elapsed
2.258 0.166 2.424 ## Over 3x faster than older versions
system.time(primeSieve(1e15, 1e15 + 1e9, nThreads = 8))
user system elapsed
4.852 0.780 0.911 ## Over 8x faster using multiple threads
_
RcppAlgos::primeSieve
_に一致するものはありません。randtoolbox::get.primes
_以上を探す必要はありません。numbers
、primes
、およびRcppAlgos
が最適です。この方法は、より速く、より簡単でなければなりません。
allPrime <- function(n) {
primes <- rep(TRUE, n)
primes[1] <- FALSE
for (i in 1:sqrt(n)) {
if (primes[i]) primes[seq(i^2, n, i)] <- FALSE
}
which(primes)
}
私のコンピューターで0.12秒n = 1e6
これをパッケージprimefactrの関数AllPrimesUpTo
に実装しました。
primegen 、DanBernsteinによるAtkin-Bernsteinふるいの実装をお勧めします。それは非常に高速で、他の問題にうまく対応できます。それを使用するには、プログラムにデータを渡す必要がありますが、それを行う方法はあると思いますか?
schoolmath
パッケージのprimes()
関数をごまかして使用することもできます:D
上記のisPrime()関数はsieve()を使用できます。素数<ceiling(sqrt(x))のいずれかがxを余りなしで除算するかどうかを確認するだけで済みます。 1と2も処理する必要があります。
isPrime <- function(x) {
div <- sieve(ceiling(sqrt(x)))
(x > 1) & ((x == 2) | !any(x %% div == 0))
}
prime = function(a,n){
n=c(2)
i=3
while(i <=a){
for(j in n[n<=sqrt(i)]){
r=0
if (i%%j == 0){
r=1}
if(r==1){break}
}
if(r!=1){n = c(n,i)}
i=i+2
}
print(n)
}
for (i in 2:1000) {
a = (2:(i-1))
b = as.matrix(i%%a)
c = colSums(b != 0)
if (c == i-2)
{
print(i)
}
}