C# 大整数阶乘的并行计算

C# 大整数阶乘的并行计算,c#,.net-4.0,parallel-processing,biginteger,factorial,C#,.net 4.0,Parallel Processing,Biginteger,Factorial,作为BigDecimal库的一部分,我需要计算任何给定非负整数的阶乘。所以我使用.NET4.0的System.Numerics.biginger来存储大量的数字。下面是我正在使用的函数: private BigInteger Factorial(BigInteger x) { BigInteger res = x; x--; while (x > 1) { res *= x; x--; }

作为BigDecimal库的一部分,我需要计算任何给定非负整数的阶乘。所以我使用.NET4.0的
System.Numerics.biginger
来存储大量的数字。下面是我正在使用的函数:

private BigInteger Factorial(BigInteger x)
{
     BigInteger res = x;
     x--;
     while (x > 1)
     {
          res *= x;
          x--;
     }
     return res;
}
它正在工作,但尚未优化。现在我想使用并行计算,下面是我尝试过的:(我没有并行编程的经验)

奇怪的是,上面的函数对于像5这样的小数字非常有效!但是对于像1000这样的大数字不起作用!每次都会返回完全不同的结果。所以我意识到它不是线程安全的,问题在于变量
res
。我想知道正确的实现是什么?

如果我可以使用biginger而不是long来表示变量
x

那就更好了。您需要确保并行进程不共享任何状态

例如,对于阶乘,我将执行以下操作:

  • 设置并行度(DOP)-通常是计算机上用于计算绑定操作的处理器数量
  • 将问题分成独立的子集
  • 并行处理每个子集
  • 汇总从并行过程中获得的结果
这是一个简化的过程

问题在于将一组数字相乘。将该集合划分为子集的一种方法是对循环使用
N
parallel,其中每个循环从值
i
(其中
0
代码
话虽如此,以下是我在LINQPad工作的内容:

public static class Math
{
    // Sequential execution
    public static System.Numerics.BigInteger Factorial(System.Numerics.BigInteger x)
    {
        System.Numerics.BigInteger res = x;
        x--;
        while (x > 1)
        {
            res *= x;
            x--;
        }
        return res;
    }
    
    public static System.Numerics.BigInteger FactorialPar(System.Numerics.BigInteger x)
    {
        return NextBigInt().TakeWhile(i => i <= x).AsParallel().Aggregate((acc, item) => acc * item);
    }
    
    public static IEnumerable<System.Numerics.BigInteger> NextBigInt()
    {
        System.Numerics.BigInteger x = 0;
        while(true)
        {
            yield return (++x);
        }
    }
}
公共静态类数学
{
//顺序执行
公共静态System.Numerics.BigInteger阶乘(System.Numerics.BigInteger x)
{
System.Numerics.BigInteger res=x;
x--;
而(x>1)
{
res*=x;
x--;
}
返回res;
}
公共静态System.Numerics.BigInteger FactorialPar(System.Numerics.BigInteger x)
{
返回NextBigInt().TakeWhile(i=>i acc*item);
}
公共静态IEnumerable NextBigInt()
{
System.Numerics.BigInteger x=0;
while(true)
{
收益率(++x);
}
}
}
这对小的(5!=120,6!=720)和大的(~8000!)因子都有效。不过,正如我所提到的,大因子的速度会提高(快2-3倍),但对小因子的性能会有严重的损失(高达两个数量级)(LINQPad预热后的结果):

  • 6!x 20->串行平均滴答声/std dev:4.2/2.014,并行平均滴答声/std dev:102.6/39.599(并行执行速度慢25倍…)

  • 300!x 20->串行平均滴答声/std dev:104.35,并行平均滴答声/std dev:405.55/175.44(以顺序速度的1/4并行运行)

  • 1000!x 20->串行平均滴答声/std dev:2672.05/615.744,并行平均滴答声/std dev:3778.65/3197.308(以约70-90%的顺序速度并行运行)

  • 10000!x 20->串行平均滴答声/标准偏差:286774.95/13666.607,并行平均滴答声/标准偏差:144932.25/16671.931(并行速度快2倍)

拿那些有一点保留意见的人来说,你需要编译一个发布版本,并将其作为一个独立版本运行,以获得“真实”的结果,但有一个趋势值得考虑


100000!(包括打印和所有内容)在我的机器上花费了26秒,在LINQPad中并行执行。

尝试以下方法以获得更简单的解决方案:

Func<int, BigInteger> factorialAgg = n => n < 2 ? BigInteger.One 
                      : Enumerable.Range(2, n-1)
                        .AsParallel()
                        .Aggregate(BigInteger.One, (r, i) => r * i);

var result = factorialAgg(100000);
Func factorialAgg=n=>n<2?BigInteger.One
:可枚举范围(2,n-1)
.天冬酰胺()
.Aggregate(biginger.One,(r,i)=>r*i);
var结果=因子平均值(100000);

聪明的诀窍:i+=DegreeOfParallelism。你可以使用DegreeOfParallelism:
环境。ProcessorCount
@JeroenvanLangen太好了,我编辑了我的答案以包含你的建议谢谢,你的方法很完美,但代码在我的机器上产生了错误的结果。你的100000代码计算结果有456569位,while实际数字正好是456574位。这个问题与我的机器有关吗?@sepherm上次
for
循环中出现了一个off-by-one错误(
i
而不是
i@SepehrM在这种情况下,CPU是瓶颈,具有更高的DOP会导致在线程之间进行实际处理+上下文切换。这似乎是一种更快的方法,但我在计算1000000的测试中没有看到任何性能差异!因为两个答案在我的马赫数中都花费了6.5分钟只需计算并存储在变量中即可。对于更短的变量:
Func Factorial=n=>Enumerable.Range(1,n).AsParallel().Aggregate(BigInteger.one,(r,i)=>r*i)
@Kobor42您更新我的答案的版本会对1进行额外的乘法运算,这是不必要的……它也会进入0、1的生成范围。我认为它没有那么有效。最初我确实是这样做的,但在任何地方,您都会查找0、1的阶乘检查实现,并立即返回,然后您会重新执行st的范围大于1…这是有原因的…@GKA你是对的。我没有关注这一部分。我更关心的是额外的类型转换,以及添加并行性的可能性。正确实现的并行聚合确实可以提高速度。遗憾的是,在4.5.1上没有。我测量了它:-(无论如何应用了您的建议。@GKA您使用的
Aggregate
重载不能并行工作,因为它不知道如何组合累积结果。您应该使用重载而不使用
种子public static class Math
{
    // Sequential execution
    public static System.Numerics.BigInteger Factorial(System.Numerics.BigInteger x)
    {
        System.Numerics.BigInteger res = x;
        x--;
        while (x > 1)
        {
            res *= x;
            x--;
        }
        return res;
    }
    
    public static System.Numerics.BigInteger FactorialPar(System.Numerics.BigInteger x)
    {
        return NextBigInt().TakeWhile(i => i <= x).AsParallel().Aggregate((acc, item) => acc * item);
    }
    
    public static IEnumerable<System.Numerics.BigInteger> NextBigInt()
    {
        System.Numerics.BigInteger x = 0;
        while(true)
        {
            yield return (++x);
        }
    }
}
Func<int, BigInteger> factorialAgg = n => n < 2 ? BigInteger.One 
                      : Enumerable.Range(2, n-1)
                        .AsParallel()
                        .Aggregate(BigInteger.One, (r, i) => r * i);

var result = factorialAgg(100000);