为什么当我将计算量减半时,我在时间上只得到了微小的改进?

Why am I getting only marginal improvements in time when I cut the computations in half?

我想编写一个程序来打印和计算 1 到 k 之间的所有素数。这个想法是我们取 1 和 k 之间的每个 odd 数字 i 并检查 i 的奇数因子直到 sqrt(i)。我的计算机花了 1 分 47 秒来检查 k = 1 000 000 以内的素数。

然后我尝试改进我的代码:我没有检查从 1 到 k 的所有奇数,而是从这些奇数中删除了所有 3、5 和 7 的倍数,所以基本上我删除了大约一半的数字必须检查。我再次 运行 程序,并且 k = 1 000 000 我得到了 1 分 40 秒的时间,只有 7 秒的改进。

这是我的代码:

   #The program has some weird quirks 
#(it doesn't print 3 and 5 because of some errors I was getting) but other than that 
#it seems to work fine

#k is the number that we check up to
k <- 1000000

#n is the number of primes, initialized at 2
n <- 2

#We take only the odd numbers between 5 and k
y <- seq(5, k, 2)

#We take each member of i and check it for primality
for (i in y) {
  #i is assumed to be prime until proved otherwise
  prime <- TRUE

  #We check the remainder when i is divided by every odd number less than its square root
  for (j in c(2, seq(3, ceiling(sqrt(i)), 2))) {
    if (i %% j == 0) {

      #If it's found that some number divides i, we set prime to false and move on 
      #to the next i
      prime <- FALSE
      break
    }
  }
  #If no number has divided i, we haven't broken the loop so R will get to this point
  #We shouldn't need the if statement, but for some reason the n counter 
  #gets too big if I remove the statement
  if (prime) {
    print(i)
    n <- n + 1
  }
}

#Print out the number of primes
print(paste("There are", n, "prime numbers between", 1, "and", k))

在我还删除了 3、5 和 7 的倍数的版本中,我只是将 y 定义为

y <- setdiff((setdiff((setdiff(seq(5, k, 2), seq(6, k, 3))), seq(10, k, 5))), seq(14, k, 7))

抱歉,它很乱,但它有效。

分析您的代码以找出大部分时间在哪里。作为一般规则,不要使用必须确定其方法的泛型。将 seq.int 换成 seq。尤其是在 R 中,您不能只计算您认为某事会进行的计算次数并减少计算次数。其实看看东西怎么叫。

以下是分析代码的方法:

调用Rprof(tmp <- tempfile()),然后执行你的算法,然后调用Rprof();summaryRprof(tmp)。查看高 total.pct 的调用,并在那里进行调整。

如果您想要一个快速函数来快速确定 R 中某个数以内的所有素数,这里有一个适合您。我不认为它是我写的,但我的文件没有告诉我它是从哪里得到的。它是 Eratosthenes 筛法的一个实现。

sieve <- function(n){
  n <- as.integer(n)
  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 中实现了它,但事实并非如此。对代码的快速 google 搜索显示,几年前我还是一个潜伏者时,我直接从 post 那里得到了它。第一个筛选答案是由@GeorgeDontas post编辑的:

编辑 2:现在回顾我的文件,我发现我的最小调整可以节省更多时间,并且可能不可靠地避免了罕见的 10 倍增加:

prime_logic <- function(n){
  n <- as.integer(n)
  primes <- rep_len(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[seq.int(last.prime+1, fsqr+1)])
    if(any(sel)){
      last.prime <- last.prime + min(sel)
    }else last.prime <- fsqr+1
  }
  primes
}

Unit: milliseconds
               expr      min       lq     mean   median       uq       max neval cld
       sieve(1e+06) 29.80531 36.41955 50.24195 38.53144 41.86716 889.47422   100   a
 prime_logic(1e+06) 22.19734 33.05792 39.35390 35.52268 41.02838  71.68033   100   a

我知道这有点离题,但我认为这是一个有用的注释。

当数字越来越大时,你根本无法使用这种方法来寻找质数。您需要使用概率方法。 Miller–Rabin primality test which is used for example in RSA's prime numbers.

最著名的检查数字是否为素数的方法之一。

这是@DanHall 答案的另一种选择function,它使用了您的逻辑(如您的素数测试方法):

primetest <- function(i, k){
  if(i >= 5 && i < k){
    vtest <- i %% c(2, seq(3, ceiling(sqrt(i)), 2))
    condition <- !is.element(0, vtest)
    if(condition){return(i)}
  }
}
V1 <- unlist(sapply(y, primetest, k = k))
length(V1)
# number of primes between 5 and k
[1] 78495
# here are the first 100 primes yielded, i.e. >= 5
V1[1:100]
 [1]   5   7  11  13  17  19  23  29  31  37  41  43  47  53  59  61  67  71  73  79  83  89
 [23]  97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199
 [45] 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337
 [67] 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463
 [89] 467 479 487 491 499 503 509 521 523 541 547 557

绝对比使用 loop 快,但我必须承认我不知道其他答案是否更快。