Параллельный.Для в C#

#c# #.net #parallel-processing

#c# #.net #параллельная обработка

Вопрос:

Я пытаюсь преобразовать следующий алгоритм сопоставления гипотез из:

  public class CollatzConjexture
    {
        public static int Calculate(int StartIndex, int MaxSequence)
        {
            int ChainLength = 0;
            int key = 0;
            bool ContinuteCalulating = true;
            int LongestChain = 0;
            Int64 Remainder = 0;

            for (int i = StartIndex; i <= MaxSequence; i  )
            {
                ChainLength = 1;
                Remainder = i;
                ContinuteCalulating = true;

                while (ContinuteCalulating)
                {
                    Remainder = CalculateCollatzConjecture(Remainder);
                    if (Remainder == 1)
                        ContinuteCalulating = false;

                    ChainLength  = 1;
                }

                if (ChainLength > LongestChain)
                {
                    LongestChain = ChainLength;
                    key = i;
                }
            }

            return key;
        }

        private static Int64 CalculateCollatzConjecture(Int64 Number)
        {
            if (Number % 2 == 0)
                return Number / 2;
            else
                return (3 * Number)   1;
        }
    } 
  

Вместо этого использовать .NET 4.0 Parallel.Для :

 int ChainLength = 0;
            int key = 0;
            bool ContinuteCalulating = true;
            int LongestChain = 0;
            Int64 Remainder = 0;

            int[] nums = Enumerable.Range(1, 1500000).ToArray();
            long total = 0;

            // Use type parameter to make subtotal a long, not an int
            Parallel.For<int>(1, nums.Length, () => 1, (j, loop, subtotal) =>
            {
                Remainder = j;

                while (ContinuteCalulating)
                {
                    Remainder = CalculateCollatzConjecture(Remainder);
                    if (Remainder == 1)
                        ContinuteCalulating = false;

                    ChainLength  = 1;
                }

                if (ChainLength > LongestChain)
                {
                    LongestChain = ChainLength;
                    key = j;
                }
                return key;


            },
                (x) => Interlocked.Add(ref key, x)
            );
  

У меня такое чувство, что я не так уж далек от этого, знаменитые последние слова.

Заранее спасибо.

Ответ №1:

Ваша проблема в том, что вы не хотите использовать Parallel.For в этом экземпляре, потому что у вас уже есть массив ( nums ) для перебора, который требует Parallel.ForEach . Однако ваш массив создается с помощью Enumerable.Range , и вы фактически ни для чего его не используете, поэтому лучший способ сделать это — с помощью AsParallel и LINQ (PLINQ):

 public static class CollatzConjexture2
{
    public static int Calculate(int StartIndex, int MaxSequence)
    {
        var nums = Enumerable.Range(StartIndex, MaxSequence);
        return nums.AsParallel()
                    // compute length of chain for each number
                   .Select(n => new { key = n, len = CollatzChainLength(n) })
                    // find longest chain
                   .Aggregate((max, cur) => cur.len > max.len ||
                    // make sure we have lowest key for longest chain
                      max.len == cur.len amp;amp; cur.key < max.key ? cur : max)
                    // get number that produced longest chain
                   .key;
    }

    private static int CollatzChainLength(Int64 Number)
    {
        int chainLength;
        for (chainLength = 1; Number != 1; chainLength  )
            Number = (Number amp; 1) == 0 ? Number >> 1 : Number * 3   1;
        return chainLength;
    }
}
  

Этот метод примерно в два раза быстрее на моем компьютере, чем последовательная реализация. Также обратите внимание, что я оптимизировал основной цикл таким образом, чтобы он выполнял вычисления встроенным образом, а не вызывал функцию, и использует побитовую математику вместо умножения и деления. Это снова сделало процесс примерно в два раза быстрее. Его компиляция для x64 вместо x86 также сделала его более чем в два раза быстрее.

Комментарии:

1. Когда я запускаю это, я не обязательно получаю наименьший индекс, который дает самую длинную цепочку Collatz (т. Е. От 1 до 1500000 последовательный метод возвращает 1117065, а метод LINQ 1126015, оба из которых имеют длину цепочки 528). Поскольку я только изучаю LINQ, есть ли простой способ изменить .Aggregate вызов, чтобы исправить это?

2. Каким-то образом я получаю оба ответа, когда я отлаживаю, я получаю (1117065, 1126015) в отдельных случаях. В идеале я хотел бы минимальный индекс. Заранее спасибо.

3. Немного поиграв с этим, я думаю, вам просто нужно изменить оператор condition в .Aggregate . т. е. max.len < cur.len должно быть (max.len < cur.len) || (max.len == cur.len amp;amp; max.key > cur.key)

4. chezy525: Спасибо, что подняли этот вопрос. Так уж получилось, что я всегда получал 1117065, но, конечно, это была чистая удача.

5. Ответы высочайшего качества. Спасибо вам обоим за помощь. LINQ и распараллеливание, вот я и пришел..