2

インターロックと他のいくつかの代替案をテストしました。結果は以下のとおりです

ForSum: 16145,47 ticks
ForeachSum: 17702,01 ticks
ForEachSum: 66530,06 ticks
ParallelInterlockedForEachSum: 484235,95 ticks
ParallelLockingForeachSum: 965239,91 ticks
LinqSum: 97682,97 ticks
ParallelLinqSum: 23436,28 ticks
ManualParallelSum: 5959,83 ticks

そのため、interlocked は、非並列 linq よりも 5 倍遅く、parallelLinq よりも 20 倍遅くなります。そして、「遅くて醜いlinq」と比較されます。手動の方法はそれよりも数桁高速であり、それらを比較する意味がありません。それはどのように可能ですか?それが本当なら、手動/Linq 並列加算の代わりにこのクラスを使用する必要があるのはなぜですか? 特に、Linq を使用することで、連動する代わりにすべてを行うことができ、悲惨な量のメソッドを使用することができます。

したがって、ベンチコードは次のとおりです。

using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace InterlockedTest
{
    internal static class Program
    {
        private static void Main()
        {
            DoBenchmark();
            Console.ReadKey();
        }

        private static void DoBenchmark()
        {
            Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;
            DisableGC();

            var arr = Enumerable.Repeat(6, 1005000*6).ToArray();
            int correctAnswer = 6*arr.Length;

            var methods = new Func<int[], int>[]
                          {
                              ForSum, ForeachSum, ForEachSum, ParallelInterlockedForEachSum, ParallelLockingForeachSum,
                              LinqSum, ParallelLinqSum, ManualParallelSum
                          };

            foreach (var method in methods)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();

                var result = new long[100];

                for (int i = 0; i < result.Length; ++i)
                {
                    result[i] = TestMethod(method, arr, correctAnswer);
                }

                Console.WriteLine("{0}: {1} ticks", method.GetMethodInfo().Name, result.Average());
            }
        }

        private static void DisableGC()
        {
            GCLatencyMode oldMode = GCSettings.LatencyMode;

            // Make sure we can always go to the catch block, 
            // so we can set the latency mode back to `oldMode`
            RuntimeHelpers.PrepareConstrainedRegions();

            GCSettings.LatencyMode = GCLatencyMode.LowLatency;
        }

        private static long TestMethod(Func<int[], int> foo, int[] arr, int correctAnswer)
        {
            var watch = Stopwatch.StartNew();

            if (foo(arr) != correctAnswer)
            {
                return -1;
            }

            watch.Stop();
            return watch.ElapsedTicks;
        }

        private static int ForSum(int[] arr)
        {
            int res = 0;

            for (int i = 0; i < arr.Length; ++i)
            {
                res += arr[i];
            }

            return res;
        }

        private static int ForeachSum(int[] arr)
        {
            int res = 0;

            foreach (var x in arr)
            {
                res += x;
            }

            return res;
        }

        private static int ForEachSum(int[] arr)
        {
            int res = 0;

            Array.ForEach(arr, x => res += x);

            return res;
        }

        private static int ParallelInterlockedForEachSum(int[] arr)
        {
            int res = 0;

            Parallel.ForEach(arr, x => Interlocked.Add(ref res, x));

            return res;
        }

        private static int ParallelLockingForeachSum(int[] arr)
        {
            int res = 0;
            object syncroot = new object();
            Parallel.ForEach(arr, i =>
                                  {
                                      lock (syncroot)
                                      {
                                          res += i;
                                      }
                                  });
            return res;
        }

        private static int LinqSum(int[] arr)
        {
            return arr.Sum();
        }

        private static int ParallelLinqSum(int[] arr)
        {
            return arr.AsParallel().Sum();
        }

        static int ManualParallelSum(int[] arr)
        {
            int blockSize = arr.Length / Environment.ProcessorCount;

            int blockCount = arr.Length / blockSize + arr.Length % blockSize;

            var wHandlers = new ManualResetEvent[blockCount];

            int[] tempResults = new int[blockCount];

            for (int i = 0; i < blockCount; i++)
            {
                ManualResetEvent handler = (wHandlers[i] = new ManualResetEvent(false));

                ThreadPool.UnsafeQueueUserWorkItem(param =>
                {
                    int subResult = 0;
                    int blockIndex = (int)param;
                    int endBlock = Math.Min(arr.Length, blockSize * blockIndex + blockSize);
                    for (int j = blockIndex * blockSize; j < endBlock; j++)
                    {
                        subResult += arr[j];
                    }
                    tempResults[blockIndex] = subResult;

                    handler.Set();
                }, i);
            }

            int res = 0;

            for (int block = 0; block < blockCount; ++block)
            {
                wHandlers[block].WaitOne();
                res += tempResults[block];
            }

            return res;
        }
    }
}
4

2 に答える 2

0

競合が発生しない場合、インターロックとロックは高速な操作です。
サンプルでは多くの競合があり、オーバーヘッドは基礎となる操作 (非常に小さなもの) よりもはるかに重要になります。

Interlocked.Add は、並列処理がなくてもわずかなオーバーヘッドを追加しますが、それほど多くはありません。

private static int InterlockedSum(int[] arr)
{
    int res = 0;

    for (int i = 0; i < arr.Length; ++i)
    {
        Interlocked.Add(ref res, arr[i]);
    }

    return res;
}

結果: ForSum: 6682.45 ティック
InterlockedSum: 15309.63 ティック

操作の性質を知っているため、操作をブロックに分割するため、手動実装との比較は公正に見えません。他の実装ではそれを想定できません。

于 2013-08-22T12:35:12.303 に答える