Суммирование каждого элемента в байтовом массиве - PullRequest
0 голосов
/ 27 августа 2018

Теперь я новичок в многопоточности, асинхронном / синхронном программировании и во всем этом.Итак, я тренировался и увидел эту проблему на YouTube.Проблема заключалась в суммировании каждого содержимого байтового массива.Это был канал под названием Джейми Кинг .Он сделал это с потоками.Я решил сделать это с задачей.Я сделал это асинхронным, и это было медленнее, чем синхронный.Разница между ними составляла 360 миллисекунд!Интересно, сможет ли кто-нибудь из вас сделать это быстрее в асинхронном режиме?Если так, пожалуйста отправьте это!Вот мое:

    static Random Random = new Random(999);

    static byte[] byteArr = new byte[100_000_000];
    static byte TaskCount = (byte)Environment.ProcessorCount;
    static int readingLength;

    static void Main(string[] args)
    {
        for (int i = 0; i < byteArr.Length; i++)
        {
            byteArr[i] = (byte)Random.Next(11);
        }

        SumAsync(byteArr);
    }

    static async void SumAsync(byte[] bytes)
    {
        readingLength = bytes.Length / TaskCount;
        int sum = 0;
        Console.WriteLine("Running...");

        Stopwatch watch = new Stopwatch();

        watch.Start();
        for (int i = 0; i < TaskCount; i++)
        {
            Task<int> task = SumPortion(bytes.SubArray(i * readingLength, readingLength));
            int result = await task;
            sum += result;
        }

        watch.Stop();

        Console.WriteLine("Done! Time took: {0}, Result: {1}", watch.ElapsedMilliseconds, sum);

    }

    static async Task<int> SumPortion(byte[] bytes)
    {
        Task<int> task = Task.Run(() =>
        {
            int sum = 0;
            foreach (byte b in bytes)
            {
                sum += b;
            }
            return sum;
        });

        int result = await task;

        return result;
    }

Обратите внимание, что bytes.SubArray - это метод расширения.У меня есть один вопрос.Является ли асинхронное программирование медленнее, чем синхронное программирование?Пожалуйста, укажите на мои ошибки.

Спасибо за ваше время!

Ответы [ 3 ]

0 голосов
/ 27 августа 2018

асинхронный явно не медленнее - он работает в фоновом режиме (например, ожидает установления соединения с веб-сайтом) - так что основной поток не останавливается в течение времени, пока он ожидает, что что-то произойдет.

0 голосов
/ 27 августа 2018

Самым быстрым способом сделать это, вероятно, будет ручное вращение петли Parallel.ForEach().

Plinq может даже не дать вам ускорение по сравнению с однопоточным подходом, и, конечно, оно не будет таким быстрым, как Parallel.ForEach().

Вот пример кода синхронизации. Когда вы попробуете это, убедитесь, что это сборка RELEASE и вы не запускаете ее под отладчиком (который отключит оптимизатор JIT, даже если это сборка RELEASE):

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    static class Program
    {
        static void Main()
        {
            // Create some random bytes (using a seed to ensure it's the same bytes each time).

            var rng = new Random(12345);
            byte[] byteArr = new byte[500_000_000];
            rng.NextBytes(byteArr);

            // Time single-threaded Linq.

            var sw = Stopwatch.StartNew();
            long sum = byteArr.Sum(x => (long)x);
            Console.WriteLine($"Single-threaded Linq took {sw.Elapsed} to calculate sum as {sum}");

            // Time single-threaded loop;

            sw.Restart();
            sum = 0;

            foreach (var n in byteArr)
                sum += n;

            Console.WriteLine($"Single-threaded took {sw.Elapsed} to calculate sum as {sum}");

            // Time Plinq

            sw.Restart();
            sum = byteArr.AsParallel().Sum(x => (long)x);
            Console.WriteLine($"Plinq took {sw.Elapsed} to calculate sum as {sum}");

            // Time Parallel.ForEach() with partitioner.

            sw.Restart();
            sum = 0;

            Parallel.ForEach
            (
                Partitioner.Create(0, byteArr.Length),

                () => 0L,

                (subRange, loopState, threadLocalState) =>
                {
                    for (int i = subRange.Item1; i < subRange.Item2; i++)
                        threadLocalState += byteArr[i];

                    return threadLocalState;
                },

                finalThreadLocalState =>
                {
                    Interlocked.Add(ref sum, finalThreadLocalState);
                }
            );

            Console.WriteLine($"Parallel.ForEach with partioner took {sw.Elapsed} to calculate sum as {sum}");
        }
    }
}

Результаты, полученные при сборке x64 на моем octo-core PC:

  • Однопоточному Linq потребовалось 00: 00: 03.1160235, чтобы вычислить сумму как 63748717461
  • Для однопоточной обработки потребовалось 00: 00: 00.7596687, чтобы вычислить сумму как 63748717461
  • Plinq потребовалось 00: 00: 01.0305913, чтобы вычислить сумму как 63748717461
  • Parallel.ForEach с разделителем заняло 00: 00: 00.0839141, чтобы вычислить сумму как 63748717461

Результаты, полученные при сборке x86:

  • Однопоточному Linq потребовалось 00: 00: 02.6964067, чтобы вычислить сумму как 63748717461
  • Однопоточное заняло 00: 00: 00.8200462 для вычисления суммы как 63748717461
  • Plinq потребовалось 00: 00: 01.1251899, чтобы вычислить сумму как 63748717461
  • Parallel.ForEach с разделителем заняло 00: 00: 00.1084805, чтобы вычислить сумму как 63748717461

Как вы можете видеть, Parallel.ForEach() со сборкой x64 является самым быстрым (вероятно, потому, что он вычисляет всего long, а не из-за большего адресного пространства).

* * * * * * * * * * * * * * * * * * * *

примерно в три раза быстрее, чем решение для многопоточных систем Linq. *1040*

Parallel.ForEach() с разделителем работает более чем в 30 раз.

Но примечательно, что однопоточный код не linq работает быстрее, чем код Plinq. В этом случае использование Plinq бессмысленно; это делает вещи медленнее!

Это говорит нам о том, что ускорение связано не только с многопоточностью, но и с накладными расходами Linq и Plinq по сравнению с ручным циклом.

Вообще говоря, вы должны использовать Plinq, только если обработка каждого элемента занимает относительно много времени (а добавление байта к промежуточному итогу занимает очень короткое время).

Преимущество Plinq перед Parallel.ForEach() с разделителем состоит в том, что писать на намного проще - однако, если он оказывается медленнее, чем простой цикл foreach, его полезность сомнительна. Поэтому выбор времени перед выбором решения очень важен!

0 голосов
/ 27 августа 2018

Вам нужно использовать WhenAll() и вернуть все задачи в конце:

    static async void SumAsync(byte[] bytes)
    {
        readingLength = bytes.Length / TaskCount;
        int sum = 0;
        Console.WriteLine("Running...");

        Stopwatch watch = new Stopwatch();

        watch.Start();
        var results = new Task[TaskCount];
        for (int i = 0; i < TaskCount; i++)
        {
            Task<int> task = SumPortion(bytes.SubArray(i * readingLength, readingLength));
            results[i] = task
        }
        int[] result = await Task.WhenAll(results);
        watch.Stop();

        Console.WriteLine("Done! Time took: {0}, Result: {1}", watch.ElapsedMilliseconds, result.Sum());

    }

Когда вы используете метод WhenAll(), вы объединяете все результаты Taskтаким образом, задачи будут выполняться параллельно, что сэкономит вам много необходимого времени.

Подробнее об этом можно прочитать в docs.microsoft.com .

...