Научно-образовательный IT-форум

Объявление

На форуме имеется приватный контент. Условия получения доступа к нему см. по ссылке

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Научно-образовательный IT-форум » Задачи и вопросы » [+] Многопоточная работа с Dictionary на C#


[+] Многопоточная работа с Dictionary на C#

Сообщений 1 страница 3 из 3

1

Требуется модифицировать программу таким образом, чтобы обеспечить работу с Dictionary параллельно. Для решения предлагается использовать классы Parallel и ConcurrentDictionary.

Код:
using System;
using System.Collections.Generic;
namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var cd = new Dictionary<char, int>();
            for(int i = 0; i < 26; i++)
            {
                char key = (char)((int)'a'+i);
                cd.Add(key, i);
            }
            Console.WriteLine("Содержание словаря после добавлений:");
            foreach (var dic in cd)
                Console.WriteLine("Key = {0}, Value = {1}", dic.Key, dic.Value);
            Console.WriteLine();
            for(int i = 0; i < 26; i++)
            {
                char key = (char)((int)'a'+i);
                int val;
                cd.TryGetValue(key, out val);
                cd.Remove(key);
                cd.Add(key, val*val);
            }
            Console.WriteLine("Содержание словаря после обновлений:");
            foreach (var dic in cd)
                Console.WriteLine("Key = {0}, Value = {1}", dic.Key, dic.Value);
        }
    }
}

2

РЕШЕНИЕ:

Код:
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var cd = new ConcurrentDictionary<char, int>();
            Parallel.For(0, 26, (i) =>
            {
                char key = (char)((int)'a'+i);
                cd.TryAdd(key, i);
            });
            Console.WriteLine("Содержание словаря после добавлений:");
            foreach (var dic in cd)
                Console.WriteLine("Key = {0}, Value = {1}", dic.Key, dic.Value);
            Console.WriteLine();
            Parallel.For(0, 26, (i) =>
            {
                char key = (char)((int)'a' + i);
                int val;
                cd.TryGetValue(key, out val);
                cd.TryUpdate(key, val * val, val);    
                //Альтернативный способ cd.AddOrUpdate(key, 0, (k, v) => v*v);
            });
            Console.WriteLine("Содержание словаря после обновлений:");
            foreach (var dic in cd)
                Console.WriteLine("Key = {0}, Value = {1}", dic.Key, dic.Value);
        }
    }
}


3

Илья Руденко <web-ilya29@yandex.ru> написал(а):

Добрый день!
Я посмотрел ваше видео https://www.youtube.com/watch?v=0WV3nLFE5DU
И хотел бы уточнить. Как правильней тестировать Parallel и обычный foreach/for, чтобы узнать, что выгодней использовать.
Я пробовал банальными средствами через Stopwatch, но Parallel обычно проигрывала. Вот и думаю, как правильней тестировать


Главная особенность метода For() состоит в том, что он позволяет, когда такая возможность имеется, распараллелить исполнение кода в цикле. А это, в свою очередь, может привести к повышению производительности. Например, процесс преобразования массива в цикле может быть разделен на части таким образом, чтобы разные части массива преобразовывались одновременно. Следует, однако, иметь в виду, что повышение производительности не гарантируется из-за отличий в количестве доступных процессоров в разных средах выполнения, а также из-за того, что распараллеливание мелких циклов может составить издержки, которые превышают сэкономленное время.

В приведенном ниже примере программы демонстрируется применение метода For() на практике. В начале этой программы создается массив data, состоящий из 1000000000 целых значений. Затем вызывается метод For(), которому в качестве "тела" цикла передается метод MyTransform(). Этот метод состоит из ряда операторов, выполняющих произвольные преобразования в массиве data. Его назначение – сымитировать конкретную операцию. Как будет подробнее пояснено несколько ниже, выполняемая операция должна быть нетривиальной, чтобы параллелизм данных принес какой-то положительный эффект. В противном случае последовательное выполнение цикла может завершиться быстрее:

Код:
using System;
using System.Threading.Tasks;

namespace ConsoleApplication12_Parallel_For__
{
    class Program
    {
        static int[] data;

        static void MyTransform(int i)
        {
            data[i] = data[i] / 10;

            if (data[i] < 10000) data[i] = 0;
            if (data[i] >= 10000) data[i] = 100;
            if (data[i] > 20000) data[i] = 200;
            if (data[i] > 30000) data[i] = 300;
        }

        static void Main()
        {
            Console.WriteLine("Основной поток запущен");

            data = new int[100000000];

            for (int i = 0; i < data.Length; i++)
                data[i] = i;

            // Распараллелить цикл методом For()
            Parallel.For(0, data.Length, MyTransform);

            Console.WriteLine("Основной поток завершен");
            Console.ReadLine();
        }
    }
}


Эта программа состоит из двух циклов. В первом, стандартном, цикле for инициализируется массив data. А во втором цикле, выполняемом параллельно методом For(), над каждым элементом массива data производится преобразование. Как упоминалось выше, это преобразование носит произвольный характер и выбрано лишь для целей демонстрации. Метод For() автоматически разбивает вызовы метода MyTransform() на части для параллельной обработки отдельных порций данных, хранящихся в массиве. Следовательно, если запустить данную программу на компьютере с двумя доступными процессорами или больше, то цикл преобразования данных в массиве может быть выполнен методом For() параллельно.

Следует, однако, иметь в виду, что далеко не все циклы могут выполняться эффективно, когда они распараллеливаются. Как правило, мелкие циклы, а также циклы, состоящие из очень простых операций, выполняются быстрее последовательным способом, чем параллельным. Именно поэтому цикл for инициализации массива данных не распараллеливается методом For() в рассматриваемой здесь программе.

Распараллеливание мелких и очень простых циклов может оказаться неэффективным потому, что время, требующееся для организации параллельных задач, а также время, расходуемое на переключение контекста, превышает время, экономящееся благодаря параллелизму. В подтверждение этого факта в приведенном ниже примере программы создаются последовательный и параллельный варианты цикла for, а для сравнения на экран выводится время выполнения каждого из них:

Код:
using System;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ConsoleApplication12_Parallel_For__
{
    class Program
    {
        static int[] data;

        static void MyTransform(int i)
        {
            data[i] = data[i] / 10;

            if (data[i] < 10000) data[i] = 0;
            if (data[i] >= 10000) data[i] = 100;
            if (data[i] > 20000) data[i] = 200;
            if (data[i] > 30000) data[i] = 300;
        }

        static void Main()
        {
            Console.WriteLine("Основной поток запущен");

            // Время выполнения цикла
            Stopwatch sw = new Stopwatch();

            data = new int[100000000];

            sw.Start();

            // Параллельный вариант инициализации массива в цикле
            Parallel.For(0, data.Length, (i) => data[i] = i);

            sw.Stop();
            Console.WriteLine("|| исполнение цикла: {0} секунд",sw.Elapsed.TotalSeconds);
            sw.Reset();

            sw.Start();
            for (int i = 0; i < data.Length; i++)
                data[i] = i;
            sw.Stop();
            Console.WriteLine("Последовательное исполнение цикла: {0} секунд", sw.Elapsed.TotalSeconds);
            sw.Reset();
            Console.WriteLine();

            sw.Start();
            // Распараллелить цикл методом For()
            Parallel.For(0, data.Length, MyTransform);
            sw.Stop();
            Console.WriteLine("|| преобразование данных в цикле: {0} секунд",
                sw.Elapsed.TotalSeconds);
            sw.Reset();

            sw.Start();
            for (int i = 0; i < data.Length; i++)
                MyTransform(i);
            sw.Stop();
            Console.WriteLine("Последовательное преобразование данных в цикле: {0} секунд",
                sw.Elapsed.TotalSeconds);

            Console.WriteLine("Основной поток завершен");
            Console.ReadLine();
        }
    }
}


https://bitbucket.org/landwatersun/forum/downloads/201904191546.jpg

Прежде всего, обратите внимание на то, что параллельный вариант цикла инициализации массива данных выполняется приблизительно в два раза медленнее, чем последовательный. Дело в том, что в данном случае на операцию присваивания расходуется так мало времени, что издержки на дополнительно организуемое распараллеливание превышают экономию, которую оно дает.

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

Как правило, в отношении преимуществ, которые дает распараллеливание различных видов циклов, следует руководствоваться текущими рекомендациями корпорации Microsoft. Кроме того, необходимо убедиться в том, что распараллеливание цикла действительно приводит к повышению производительности, прежде чем использовать такой цикл в окончательно выпускаемом прикладном коде.

Что касается приведенной выше программы, то необходимо упомянуть о двух других ее особенностях. Во-первых, обратите внимание на то, что в параллельно выполняемом цикле для инициализации данных применяется лямбда-выражение. Здесь "тело" цикла указывается в лямбда-выражении. (Напомним, что в лямбда-выражении создается анонимный метод.) Следовательно, для параллельного выполнения методом For() совсем не обязательно указывать именованный метод.

И, во-вторых, обратите внимание на применение класса Stopwatch для вычисления времени выполнения цикла. Этот класс находится в пространстве имен System.Diagnostics. Для того чтобы воспользоваться им, достаточно создать экземпляр его объекта, а затем вызвать метод Start(), начинающий отчет времени, и далее – метод Stop(), завершающий отсчет времени. А с помощью метода Reset() отсчет времени сбрасывается в исходное состояние.

Продолжительность выполнения можно получить различными способами. В рассматриваемой здесь программе для этой цели использовано свойство Elapsed, возвращающее объект типа TimeSpan. С помощью этого объекта и свойства TotalSeconds время отображается в секундах, включая и доли секунды. Как показывает пример рассматриваемой здесь программы, класс Stopwatch оказывается весьма полезным при разработке параллельно исполняемого кода.


Вы здесь » Научно-образовательный IT-форум » Задачи и вопросы » [+] Многопоточная работа с Dictionary на C#