Parallel.For与BigInteger计算输出不同于For循环

问题描述 投票:2回答:1

我有以下循环运行从base95到base10的转换。我正在使用几千位数字,因此需要BigIntegers。 inst是base95字符串。

Parallel.For(0, inst.Length, x => 
     {
        result += BigInteger.Pow(95, x) * (inst[x] - 32);
     });

如果我使用大约200个字符或更少的base95字符串,它完全正常工作并输出与正常for循环相同的结果。

但是,一旦我开始增加base95字符串的大小,就会抛出并行输出。我需要使用1500多个字符的base95字符串,甚至高达30000.一个常规的for循环可以很好地计算结果。

什么可能导致这个问题?有没有比Parallel.For循环更好的方法,它仍然比for循环更快?

c# biginteger parallel.for
1个回答
8
投票

它只是不是线程安全的。至于为什么它没有用较小的琴弦腐蚀,我不确定。可能TPL只是认为工作负载不值得额外的线程。虽然,我确实验证了你的结果,但它确实会产生与更大字符串不一致的结果。

唯一的解决方案是使其线程安全。一个廉价和讨厌的方法将是使用lock ...如果你可以使用另一个线程安全的方法,如Interlocked会更好,但是,它不会与BigInteger一起使用。

BigInteger result = 0;
object sync = new object();

Parallel.For(
   0,
   inst.Length,
   x =>
      {
         var temp = BigInteger.Pow(95, x) * (inst[x] - 32);
         lock (sync)
            result += temp;
      });

它不是完美的所有锁定,但它仍然比我的电脑上的常规for循环更快

另一种方法是使用for过载,这样每个线程只锁定一次。

Parallel.For(
   0,
   inst.Length,
   () => new BigInteger(0),
   (x, state, subTotal) => subTotal + BigInteger.Pow(95, x) * (inst[x] - 32),
   integer =>
      {
         lock (sync)
            result += integer;
      });

Benchmarks

所以我很无聊,这是你的替补

每次测试运行50次,每次测试前都运行GC.CollectGC.WaitForPendingFinalizers,以获得更清晰的结果。所有结果都相互测试,以证明它们是准确的。 Scale根据您的问题表示字符串的大小

建立

----------------------------------------------------------------------------
Mode             : Release (64Bit)
Test Framework   : .NET Framework 4.7.1 (CLR 4.0.30319.42000)
----------------------------------------------------------------------------
Operating System : Microsoft Windows 10 Pro
Version          : 10.0.17134
----------------------------------------------------------------------------
CPU Name         : Intel(R) Core(TM) i7-3770K CPU @ 3.50GHz
Description      : Intel64 Family 6 Model 58 Stepping 9
Cores (Threads)  : 4 (8)      : Architecture  : x64
Clock Speed      : 3901 MHz   : Bus Speed     : 100 MHz
L2Cache          : 1 MB       : L3Cache       : 8 MB
----------------------------------------------------------------------------

结果

--- Random characters -----------------------------------------------------------------
| Value          |    Average |    Fastest |    Cycles | Garbage | Test |        Gain |
--- Scale 10 ----------------------------------------------------------- Time 0.259 ---
| for            |   5.442 µs |   4.968 µs |  21.794 K | 0.000 B | Base |      0.00 % |
| ParallelResult |  32.451 µs |  30.397 µs | 116.808 K | 0.000 B | Pass |   -496.25 % |
| ParallelLock   |  35.551 µs |  32.443 µs | 127.966 K | 0.000 B | Pass |   -553.22 % |
| AsParallel     | 141.457 µs | 118.959 µs | 398.676 K | 0.000 B | Pass | -2,499.13 % |
--- Scale 100 ---------------------------------------------------------- Time 0.298 ---
| ParallelResult |  93.261 µs |  80.085 µs | 329.450 K | 0.000 B | Pass |     11.36 % |
| ParallelLock   | 103.912 µs |  84.470 µs | 366.599 K | 0.000 B | Pass |      1.23 % |
| for            | 105.210 µs |  93.823 µs | 371.025 K | 0.000 B | Base |      0.00 % |
| AsParallel     | 183.538 µs | 159.002 µs | 488.534 K | 0.000 B | Pass |    -74.45 % |
--- Scale 1,000 -------------------------------------------------------- Time 4.191 ---
| AsParallel     |   5.701 ms |   4.932 ms |  15.479 M | 0.000 B | Pass |     65.83 % |
| ParallelResult |   6.510 ms |   5.701 ms |  18.166 M | 0.000 B | Pass |     60.98 % |
| ParallelLock   |   6.734 ms |   5.303 ms |  17.314 M | 0.000 B | Pass |     59.64 % |
| for            |  16.685 ms |  15.640 ms |  58.183 M | 0.000 B | Base |      0.00 % |
--- Scale 10,000 ------------------------------------------------------ Time 34.805 ---
| AsParallel     |    6.205 s |    4.767 s |  19.202 B | 0.000 B | Pass |     47.20 % |
| ParallelResult |    6.286 s |    5.891 s |  14.752 B | 0.000 B | Pass |     46.51 % |
| ParallelLock   |    6.290 s |    5.202 s |   9.982 B | 0.000 B | Pass |     46.48 % |
| for            |   11.752 s |   11.436 s |  41.136 B | 0.000 B | Base |      0.00 % |
---------------------------------------------------------------------------------------

ParallelLock

[Test("ParallelLock", "", true)]
public BigInteger Test1(string input, int scale)
{
   BigInteger result = 0;
   object sync = new object();

   Parallel.For(
      0,
      input.Length,
      x =>
         {
            var temp = BigInteger.Pow(95, x) * (input[x] - 32);
            lock (sync)
               result += temp;
         });

   return result;
}

ParallelResult

[Test("ParallelResult", "", false)]
public BigInteger Test2(string input, int scale)
{
   BigInteger result = 0;
   object sync = new object();
   Parallel.For(
      0,
      input.Length,
      () => new BigInteger(0),
      (x, state, subTotal) => subTotal + BigInteger.Pow(95, x) * (input[x] - 32),
      integer =>
         {
            lock (sync)
               result += integer;
         });
   return result;
}

Asparallel由gdir招标

[Test("AsParallel", "", false)]
public BigInteger Test4(string input, int scale)
{
   return Enumerable.Range(0, input.Length)
                    .AsParallel()
                    .Aggregate(
                        new BigInteger(0),
                        (subtotal, x) => subtotal + BigInteger.Pow(95, x) * (input[x] - 32),
                        (total, thisThread) => total + thisThread,
                        (finalSum) => finalSum);;
}

对于

[Test("for", "", false)]
public BigInteger Test3(string input, int scale)
{       
   BigInteger result = 0;
   for (int i = 0; i < input.Length; i++)
   {
      result += BigInteger.Pow(95, i) * (input[i] - 32);
   }
   return result;
}

输入

public static string StringOfChar(int scale)
{
   var list = Enumerable.Range(1, scale)
                        .Select(x => (char)(_rand.Next(32)+32))
                        .ToArray();
   return string.Join("", list);
} 

验证

private static bool Validation(BigInteger result, BigInteger baseLine)
{
   return result == baseLine;
}

Summary

并行将为您提供性能提升,理论上锁定越少越好,但是可能有许多因素导致结果以他们的方式发挥作用。它似乎结果重载似乎运行良好,但与更大的工作负载非常相似,我不确定为什么。注意我没有使用并行选项,您可能可以为您的解决方案稍微调整一下

无论如何,祝你好运

© www.soinside.com 2019 - 2024. All rights reserved.