【问题标题】:How does this primality test make the code 5000x faster?这种素性测试如何使代码速度提高 5000 倍?
【发布时间】:2013-09-06 06:50:09
【问题描述】:

警告:此代码是 Project Euler Problem 50 的解决方案。不想剧透的就别看这里了。

在这里,我的代码可以搜索一长串连续素数,这些素数加起来也是素数。在某一时刻,我需要测试一个和是否是素数。

我有两个测试,在函数 computeMaxPrime 中使用 ifdef。第一个检查总和与 std::set 素数。第二个使用由 GMP 实施的 Miller-Rabin 测试。该函数仅被调用 6 次。当我使用第一个测试时,函数 computeMaxPrime 需要 0.12 秒。当我使用第二个测试时,只需要 ~.00002 秒。有人可以解释这是怎么可能的吗?我认为检查一个号码是否在一组中的 6 次调用不会花费 100 毫秒。我也尝试过使用 unordered_set,它的表现是一样的。

我认为这可能是一个计时问题,但我已经通过从终端(在 OSX 上)计时整个程序执行来验证它。我还验证了如果我将测试更改为首先使用 Miller-Rabin 测试,然后确认使用集合,它会调用集合并且时钟报告 0.02 秒,这正是我所期望的(1 /6th 仅使用设置测试的总时间。

#include "PrimeGenerator2.h"
#include <set>
#include <stdio.h>
#include <time.h>
#include <gmp.h>

typedef std::set<u_int64t>       intSet;

bool isInIntSet (intSet       set,
                 u_int64t     key)
{
  return (set.count(key) > 0);
}

bool isPrime (u_int64t key)
{
  mpz_t      integ;

  mpz_init (integ);
  mpz_set_ui (integ, key);
  return (mpz_probab_prime_p (integ, 25) > 0);
}

void computeInitialData (const u_int64t   limit,
                         intSet      *primeSet,
                         intList     *sumList,
                         u_int64t    *maxCountUpperBound)
{
  PrimeSieve     sieve;
  u_int64t     cumSum = 0;
  u_int64t     pastUpperBound = 0;

  *maxCountUpperBound = 0;

  for (u_int64t prime = sieve.NextPrime(); prime < limit; prime = sieve.NextPrime()) {
    primeSet->insert(prime);

    cumSum += prime;
    sumList->push_back(cumSum);
    if (cumSum < limit)
      (*maxCountUpperBound)++;
    else
      pastUpperBound++;
  }
}

u_int64t computeMaxPrime (const u_int64t   limit,
                          const intSet  &primeSet,
                          const intList &sumList,
                          const u_int64t   maxCountUpperBound)
{
  for (int maxCount = maxCountUpperBound; ; maxCount--) {
    for (int i = 0; i + maxCount < sumList.size(); i++) {
      u_int64t   sum;

      sum = sumList[maxCount + i] - sumList[i];
      if (sum > limit)
        break;
#if 0
      if (isInIntSet (primeSet, sum))
        return sum;
#else
      if (isPrime (sum))
        return sum;
#endif
    }
  }

  return 0; // This should never happen
}

u_int64t findMaxCount (const u_int64t   limit)
{ 
  intSet       primeSet;  // Contains the set of all primes < limit
  intList      sumList; // Array of cumulative sums of primes

  u_int64t     maxCountUpperBound = 0;  // Used an initial guess for the maximum count
  u_int64t     maxPrime;          // Final return value

  clock_t      time0, time1, time2;

  time0     = clock();
  computeInitialData (limit, &primeSet, &sumList, &maxCountUpperBound);
  time1     = clock();
  maxPrime  = computeMaxPrime (limit, primeSet, sumList, maxCountUpperBound);
  time2     = clock();  

  printf ("%f seconds for primes \n"  , (double)(time1 - time0)/CLOCKS_PER_SEC);
  printf ("%f seconds for search \n"  , (double)(time2 - time1)/CLOCKS_PER_SEC);  

  return maxPrime;
}

int main(void)
{
  printf ("%lld\n", findMaxCount(1000000));
}

编辑:哦,这更奇怪。似乎与 STL 集无关。如果我做一个 hack 来制作 isInIntSet 只是检查它被调用了多少次,与 GMP 测试相比它同样慢。这让我觉得我可能只是遇到了编译器错误(EDIT2:永远不要责怪编译器!)

bool isInIntSet (intSet set, u_int64t key)
{
  static int  counter = 0;
  counter++;
  return (counter == 6);
}

【问题讨论】:

  • std::count 是大小的对数。你的系列有多大?数字 6 是从哪里来的?
  • 数字 6 只是我使用静态计数器查看函数被调用的次数。我刚刚进行了编辑;看来 set 和 std::count 与它无关。即使是微不足道的 hack 函数(最终会计算相同的结果)也很慢。
  • 好的。有些事情并不完全加起来。 1M 以下应该有大约 60k 个素数。您的计数上限将是 500 的数量级(只是猜测)。你有这些嵌套循环试图找到系列......你怎么能只调用这个 6 次?
  • 是的,计数上限在 500 到 600 之间(猜得好!)。你说得对,6 确实 看起来真的很低。我什至在函数中添加了 printf 来验证它(打印了 6 行)。
  • 好吧,情况可能是这样(奇怪但仍然如此),但请记住,您不仅要测量调用函数的时间,还要测量循环中所花费的时间,以达到您的 break 语句。无论如何,有些事情没有加起来。使用调试器单步执行...

标签: c++ stl set primes gmp


【解决方案1】:

呃。函数 isInIntSet 直接将 intSet 作为参数,因此整个集合被复制。我的意思是通过引用(intSet &set)。这将使用 unordered_set 的搜索时间缩短到 0.000003 秒。

【讨论】:

  • 您的好问题和快速回答,节省了很多时间:D
猜你喜欢
  • 1970-01-01
  • 2014-06-14
  • 2020-08-16
  • 2022-11-22
  • 2016-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多