我想优化这个程序

时间:2019-12-07 16:31:20

标签: c algorithm primes

我最近开始学习c,并且作为编程练习,我编写了一个程序,该程序计算并列出从0到用户输入的最大值的质数。这是一个相当简短的程序,因此我将在此处发布源代码。

// playground.c
#include <stdio.h>
#include <stdbool.h>
#include <math.h>

int main ()
{
    int max;
    printf("Please enter the maximum number up to which you would like to see all primes listed: "
      );   scanf("%i", &max);

    printf("All prime numbers in the range 0 to %i:\nPrime number: 2\n", max);


    bool isComposite;
    int primesSoFar[(max >> 1) + 1];
    primesSoFar[0] = 2;
    int nextIdx = 1;

    for (int i = 2; i <= max; i++)
    {
        isComposite = false;
        for (int k = 2; k <= (int)sqrt(i) + 1; k++)
        {
            if (k - 2 < nextIdx)
            {
                if (i % primesSoFar[k - 2] == 0)
                {
                    isComposite = true;
                    k = primesSoFar[k - 2];
                }
            }else
            {
                if (i % k == 0) isComposite = true;
            }

        }
        if (!isComposite)
        {
            printf("Prime number: %i\n", i);
            primesSoFar[nextIdx] = i;
            nextIdx++;

        }

    }

    double primeRatio = (double)(nextIdx + 1) / (double)(max);
    printf("The ratio of prime numbers to composites in range 0 to %d is %lf", max, primeRatio);

    return 0;
}

我对优化该程序非常着迷,但遇到了麻烦。数组primesSoFar是基于计算出的最大大小分配的,理想情况下,该大小不大于从0到max的素数的数量。即使只是稍大一点,也可以。只要不小。有没有一种方法可以计算出数组所需的大小,而不必依赖于首先计算素数到max的素数?

我已经更新了代码,既应用了建议的优化方法,又在可能有用的地方添加了内部文档。

// can compute all the primes up to 0x3FE977 (4_188_535). Largest prime 4_188_533

#include <stdio.h>
#include <stdbool.h>
#include <math.h>

int main ()
{
    int max;
    printf("Please enter the maximum number up to which you would like to see all primes listed: "
      );   scanf("%i", &max);

    // The algorithm proper doesn't print 2.
    printf("All prime numbers in the range 0 to %i:\nPrime number: 2\n", max);


    bool isComposite;
    // primesSoFar is a memory hog. It'd be nice to reduce its size in proportion to max. The frequency
    // of primes diminishes at higher numerical ranges. A formula for calculating the number of primes for
    // a given numerical range would be nice. Sadly, it's not linear.
    int PRIMES_MAX_SIZE = (max >> 1) + 1;
    int primesSoFar[PRIMES_MAX_SIZE];
    primesSoFar[0] = 2;
    int nextIdx = 1;
    int startConsecCount = 0;

    for (int i = 2; i <= max; i++)
    {
        isComposite = false; // Assume the current number isn't composite.
        for (int k = 2; k <= (int)sqrt(i) + 1; k++)
        {
            if (k - 2 < nextIdx) // Check it against all primes found so far.
            {
                if (i % primesSoFar[k - 2] == 0)
                {
                    // If i is divisible by a previous prime number, break.
                    isComposite = true;
                    break;
                }else
                {
                    // Prepare to start counting consecutive integers at the largest prime + 1. if i 
                    // isn't divisible by any of the primes found so far.
                    startConsecCount = primesSoFar[k - 2] + 1;
                }
            }else
            {
                if (startConsecCount != 0) // Begin counting consecutively at the largest prime + 1.
                {
                    k = startConsecCount;
                    startConsecCount = 0;
                }

                if (i % k == 0)
                {
                    // If i is divisible by some value of k, break.
                    isComposite = true;
                    break;
                }
            }

        }
        if (!isComposite)
        {
            printf("Prime number: %i\n", i);

            if (nextIdx < PRIMES_MAX_SIZE)
            {
                // If the memory allocated for the array is sufficient to store an additional prime, do so.
                primesSoFar[nextIdx] = i;
                nextIdx++;
            }

        }

    }

    // I'm using this to get data with which I can find a way to compute a smaller size for primesSoFar.
    double primeRatio = (double)(nextIdx + 1) / (double)(max);
    printf("The ratio of prime numbers to composites in range 0 to %d is %lf\n", max, primeRatio);

    return 0;
}

编辑primesSoFar的大小应为0到最大范围的一半。毫无疑问,这引起了一些混乱。

3 个答案:

答案 0 :(得分:4)

在我讨论这个问题的项目中,我可以给您两个主要想法。

  1. 大于3的质数是6k-16k+1,因此例如183不能是质数,因为183=6x30+3,因此您甚至不必检查它。 (请注意,此条件是必要的,但还不够,25例如6x4+1,但不是素数)
  2. 如果数字不能被任何小于或等于其根的质数除以质数,那么最好从已经发现的较小质数中受益。

因此,您可以从包含primesList2的{​​{1}}开始,然后迭代3测试所有k6k-1如果您仅发现一个元素,则使用我给您的第二条规则,对6k+1中的元素进行除法,这些元素小于或等于要检查的数字的根划分它,您只需停下来并传递到另一个元素,因为该元素不是素数,否则(如果没有人可以划分它):通过添加此新素数来更新5, 7, 11, 13, 17, 19, 23, 25...

答案 1 :(得分:2)

首先要进行一些调试。

当我看到测试为 <= 时,我的大脑说BUG,因为数组的下标范围是0 .. max-1。

for (int i = 2; i <= max; i++)

所以我去看一下数组。

int primesSoFar[(max >> 1) + 1];

哦,他在尺寸上加了一个,所以应该没问题。 等待。为什么在那里发生这种转变? (最大值>> 1)是除以2。

我编译并运行了代码,MSVC报告了内存错误。 我删除了移位,并且内存错误报告消失了。该程序按预期工作。

因此, PiNaKa30 II Saggio Vecchino 有很好的建议。算法的选择将极大地影响性能。

垫子提供了很好的建议。阅读Wikipedia条目。它充满了精彩的信息。

  • 选择正确的算法是关键。
  • 如何表示要检查的数据是一个因素。 int 具有可以容纳的最大值。
  • 性能profiler可以告诉您很多有关Hot Spots在程序中的位置的有用信息。

恭喜您学习C。您选择了一条非常好的学习之路。

答案 2 :(得分:0)

下面的源代码基本上是一个重写。在我撰写本文时,它正在运行。我输入了0x7FFF_FFFF,这是32位有符号整数的正最大值。在装有Linux Mint的AMD ryzen 3上运行的Acer aspire笔记本电脑上,仅需几分钟,就已经有数亿个!旧版本的内存使用量是最大内存使用量的一半,因此在我4GB的RAM上无法实现大于0x3EF977的任何内容。现在,当计算从0到2_147_483_647的质数时,它仅将370728字节的内存用于其数组数据。

/*
  A super optimized prime number generator using my own implementation of the sieve of Eratosthenes.
*/

#include <stdio.h>
#include <stdbool.h>
#include <math.h>

int main ()
{
    int max;
    printf("Please enter the maximum to which you would like to see all primes listed: "
      ); scanf("%i", &max);

    /*
      Primes and their multiples will be stored until the next multiple of the prime is larger than max.
      That prime and its corresponding multiple will then be replaced with a new prime and its corresponding
      multiple.
    */
    int PRIMES_MAX_SIZE = (int)sqrt(max) + 1;
    int primes[PRIMES_MAX_SIZE];
    int multiples[PRIMES_MAX_SIZE];
    primes[0] = 2;
    multiples[0] = 2;
    int nextIdx = 1;

    int const NO_DISPOSE_SENTINAL_VALUE = -1;
    int nextDispose = NO_DISPOSE_SENTINAL_VALUE;
    int startConsecCount = 0;
    int updateFactor;
    bool isComposite;

    printf("All prime numbers in the range 0 to %i:\n\n", max);
    // Iterate from i = 2 to i = max and test each i for primality.
    for (int i = 2; i <= max; i++)
    {
        isComposite = false;
        /*
          Check whether the current i is prime by comparing it with the current multiples of
          prime numbers, updating them when they are less than the current i and then proceeding
          to check whether any consecutive integers up to sqrt(i) divide the current i evenly.
        */
        for (int k = 2; k < (int)sqrt(i) + 1; k++)
        {
            if (k < nextIdx)
            {
                // Update the multiple of a prime if it's smaller than the current i.
                if (multiples[k] < i)
                {
                    updateFactor = (int)(i / primes[k]);
                    multiples[k] = updateFactor * primes[k] + primes[k];
                    // Mark the value for disposal if it's greater than sqrt(max).
                    if (multiples[k] > (int)sqrt(max)) nextDispose = k;
                }
                if (i == multiples[k])
                {
                    isComposite = true;
                    break;
                }else
                {
                    startConsecCount = multiples[k] + 1;
                }
            } else
            {
                if (startConsecCount != 0)
                {
                    k = startConsecCount;
                    startConsecCount = 0;
                }
                if (i % k == 0)
                {
                    isComposite = true;
                    break;
                }
            }
        }
        /*
          Print the prime numbers and either insert them at indices occupied by disposed primes or at
          the next array index if available.
        */
        if (!isComposite)
        {
            printf("Prime number: %i\n", i);

            if (nextDispose != NO_DISPOSE_SENTINAL_VALUE)
            {
                primes[nextDispose] = i;
                // This will trigger the update code before the comparison in the inner loop.
                multiples[nextDispose] = 0;
                nextDispose = NO_DISPOSE_SENTINAL_VALUE;
            }else
            {
                if (nextIdx < PRIMES_MAX_SIZE)
                {
                    primes[nextIdx] = i;
                    multiples[nextIdx] = 0;
                }
            }
        }
    }

    return 0;
}

此功能将在眨眼之间完成旧的0到0x3EF977。旧版本无法在我的系统上执行32位最大值。已经超过2.01亿。我对结果超级满意。感谢您的建议。没有帮助,我不会走那么远。