计算C#素数的最快方法?

时间:2008-08-27 18:54:44

标签: c# .net performance algorithm bitarray

我实际上对我的问题有一个答案,但它没有并行化,所以我对改进算法的方法很感兴趣。无论如何,它对某些人来说可能是有用的。

int Until = 20000000;
BitArray PrimeBits = new BitArray(Until, true);

/*
 * Sieve of Eratosthenes
 * PrimeBits is a simple BitArray where all bit is an integer
 * and we mark composite numbers as false
 */

PrimeBits.Set(0, false); // You don't actually need this, just
PrimeBits.Set(1, false); // remindig you that 2 is the smallest prime

for (int P = 2; P < (int)Math.Sqrt(Until) + 1; P++)
    if (PrimeBits.Get(P))
        // These are going to be the multiples of P if it is a prime
        for (int PMultiply = P * 2; PMultiply < Until; PMultiply += P)
            PrimeBits.Set(PMultiply, false);

// We use this to store the actual prime numbers
List<int> Primes = new List<int>();

for (int i = 2; i < Until; i++)
    if (PrimeBits.Get(i))
        Primes.Add(i);

也许我可以一起使用多个BitArrayBitArray.And()

8 个答案:

答案 0 :(得分:5)

您可以通过使用双向链接列表交叉引用您的位数组来节省一些时间,这样您就可以更快地进入下一个素数。

此外,一旦你第一次碰到一个新的素数p就消除了后来的复合物 - 剩下的p的第一个复合倍数将是p * p,因为之前的所有内容都已被消除。实际上,您只需要将p乘以列表中剩余的所有剩余潜在素数,并在产品超出范围(大于直到)时立即停止。

还有一些很好的概率算法,例如Miller-Rabin测试。 The wikipedia page是一个很好的介绍。

答案 1 :(得分:2)

除了并行化之外,您不希望在每次迭代时计算sqrt(Until)。您还可以假设2,3和5的倍数,并且只计算{1,5}中的N%6或{1,7,11,13,17,19,23,29}中的N%30。

您应该能够非常轻松地并行化因子分解算法,因为第N阶段仅取决于第(n)个结果,因此一段时间后不会有任何冲突。但这不是一个好的算法,因为它需要大量的划分。

如果您有一个写入工作数据包,保证在读取之前完成,您还应该能够并行化筛选算法。大多数作者不应该与读者发生冲突 - 至少一旦你完成了一些条目,他们应该在阅读器上面至少工作N,所以你只需要偶尔进行同步读取(当N超过最后一次同步读取时)值)。您不需要跨任意数量的写入程序线程同步bool数组,因为不会出现写入冲突(最坏的情况是,多个线程会将真实写入同一位置)。

主要问题是确保任何等待写作的工人已经完成。在C ++中,您将使用compare-and-set切换到正在等待的工作者。我不是C#wonk所以不知道如何使用该语言,但Win32 InterlockedCompareExchange函数应该可用。

您也可以尝试基于actor的方法,因为这样您可以安排演员使用最低值,这可能更容易保证您正在阅读筛子的有效部分而无需锁定总线N的每个增量。

无论哪种方式,您必须确保所有工作人员在您阅读之前已经超过了条目N,并且这样做的成本是在并行和串行之间进行权衡的地方。

答案 2 :(得分:1)

  

如果没有分析,我们无法分辨程序的哪一部分需要优化。

如果您在大型系统中,那么可以使用分析器来查找素数生成器是需要优化的部分。

使用十几个左右的指令对循环进行概要分析通常不值得 - 与循环体相比,分析器的开销很大,并且改善循环的唯一方法是将算法更改为减少迭代次数。所以IME,一旦你消除了任何昂贵的函数并且已经知道几行简单代码的目标,你最好改变算法和定时端到端运行,而不是试图通过指令级来改进代码分析

答案 3 :(得分:0)

@DrPizza分析只是真的有助于改进实现,它没有揭示并行执行的机会,或者建议更好的算法(除非你有其他方面的经验,在这种情况下我真的很想看你的探查器)

我家里只有一台核心机器,但运行了一台Java等效的BitArray筛网,以及筛网反转的单线程版本 - 将标记素数保存在一个数组中,并使用{{3}将搜索空间减少五倍,然后使用每个标记素数以轮的增量标记一个位数组。它还将存储减少到O(sqrt(N))而不是O(N),这有助于最大N,分页和带宽。

对于N(1e8到1e12)的中等值,可以很快找到高达sqrt(N)的素数,之后你应该能够很容易地在CPU上并行化后续搜索。在我的单核机器上,轮子方法在28s内找到高达1e9的质数,而你的筛子(在将sqrt移出循环后)需要86s - 改进是由于轮子;反转意味着您可以处理大于2 ^ 32的N,但会使其变慢。可以找到代码wheel。在经过sqrt(N)之后,你可以将天真筛子的结果输出并行化,因为在该点之后没有修改位数组;但是一旦你处理N足够大,因为数组大小对于整数来说太大了。

答案 4 :(得分:0)

您还应该考虑algorithms的可能更改。

考虑到将元素添加到列表中可能会更便宜,就像找到它们一样。

也许为您的列表预先分配空间会使构建/填充更便宜。

答案 5 :(得分:0)

你想找到新的素数吗?这可能听起来很愚蠢,但您可能能够加载某种具有已知素数的数据结构。我相信那里有人有一个清单。找到计算新数字的现有数字可能是一个更容易的问题。

您还可以查看Microsofts Parallel FX Library,以使您的现有代码具有多线程以利用多核系统。通过最少的代码更改,您可以使循环多线程。

答案 6 :(得分:0)

有一篇关于Eratosthenes筛选的非常好的文章:The Genuine Sieve of Eratosthenes

它处于功能设置中,但大多数opimization也适用于C#中的过程实现。

两个最重要的优化是在P ^ 2而不是2 * P开始交叉,并使用轮子作为下一个素数。

对于并发性,你可以处理所有数字,直到P ^ 2与P并行而不做任何不必要的工作。

答案 7 :(得分:0)

    void PrimeNumber(long number)
    {
        bool IsprimeNumber = true;
        long  value = Convert.ToInt32(Math.Sqrt(number));
        if (number % 2 == 0)
        {
            IsprimeNumber = false;
            MessageBox.Show("No It is not a Prime NUmber");
            return;
        }
        for (long i = 3; i <= value; i=i+2)
        {             
           if (number % i == 0)
            {

                MessageBox.Show("It is divisible by" + i);
                IsprimeNumber = false;
                break;
            }

        }
        if (IsprimeNumber)
        {
            MessageBox.Show("Yes Prime NUmber");
        }
        else
        {
            MessageBox.Show("No It is not a Prime NUmber");
        }
    }