Короткий огляд Parallel Extensions для. NET Framework

Зараз нікого не здивуєш наявністю декількох ядер в процесорі, будь то робоча станція, ноутбук, нетбук – неважливо. Далі – більше: "двоголові" процесори скоро облаштуються і в мобільних телефонах. Ось чому саме зараз тема розпаралелювання програм перестає бути академічної і набуває цілком практичний інтерес. Зрозуміло, при створенні такого ПЗ є ряд підводних каменів. Крім того, не всі і не завжди можна і потрібно распараллелівать – але це тема окремої розмови. Мета цієї статті – зробити екскурс в область інструментів від компанії Microsoft для створення розпаралелену керованих додатків.


Отже, Parallel Extensions, як можна здогадатися з назви, це якраз і є той самий набір інструментів, який покликаний підвищити ефективність застосування нових апаратних засобів. Вперше в загальному доступі бібліотека з'явилася в листопаді 2007 року і мала статус CTP. У червні 2008 року з'явилася нова preview-версія. Обидві вони використали. NET 3.5 для своєї роботи, і постачалися у вигляді окремої збірки. Однак восени минулого року з виходом Visual Studio 2010 CTP цей інструментарій переїхав в збірку mscorlib і став використовувати. NET 4. З тих пір нічого не змінилося, і нових версій для. NET 3.5 не випускалося. Протягом усього цього часу змінювався склад бібліотеки, її API. Найбільш актуальна версія не так давно випущена у складі. NET 4 beta 2. Ви можете завантажити Visual Studio 2010 beta 2 і спробувати бібліотеку своїми руками.


Схематично склад Parallel Extensions можна зобразити наступним чином:


. NET 4 несе вагоме кількість змін. Вони торкнулися в тому числі і принципу роботи пулу потоків. Його оптимізували для управління великою кількістю робочих потоків, і поміняли реалізацію черги завдання для кожного з них. Тепер крім глобальної черги завдань кожен потік має свою локальну чергу. Це дозволяє більш ефективно керувати завданнями. Наприклад, перший потік виконує чергове завдання, а другий встиг спустошити свою чергу. Тоді він може "вкрасти" завдання з черги першого потоку, тим самим розподіливши навантаження. При цьому не буде звернення до глобальної черги завдань, що зменшить навантаження на неї. Ці дії будуть прозорі для прикладного програміста.


Якщо уважно придивитися до попереднього абзацу, можна помітити ключове слово "завдання". Воно знаменує один з підходів до спрощення розпаралелювання програм. Бібліотека Parallel Extensions пропонує зосередити увагу на реалізується бізнес-сценарії. Слово "потік" відноситься до технічної реалізації. "Завдання" ж являє собою деяке атомарну дію, виконання якої дозволить отримати результат. При цьому не розглядається, ким і де буде виконана задача. Важливіше те, як вона реалізується і як співвідноситься з іншими завданнями. Така концепція, покладена в основі Task Parallel Library (Коротко TPL) – набору інструментів для створення, управління та виконання завдань. При цьому TPL використовує механізми оновленого пулу потоків для запуску і завершення завдань. Фактично, завдання становить собою якусь асинхронну операцію в контексті нашої програми. Звичайно, в. NET 2.0 були QueueUserWorkItem (), BackgroundWorker та ін кошти. З їх допомогою можна було досягти потрібного функціонала, що всі ми з вами успішно робили. Але TPL надає вже готовий, до деякої міри універсальний підхід. Це зменшує витрати на розпаралелювання, що в кінцевому рахунку робить проект дешевше з економічних і тимчасовими показниками.

На основі Task Parallel Library і концепції "завдань" працюють прикладні засоби по розпаралелюванню. Серед них – PLINQ, тобто Parallel LINQ. Ця частина бібліотеки Parallel Extensions, що дозволяє виконувати LINQ запити одночасно в декількох потоках. Родзинка її полягає в дуже простому способі інтеграції із уже наявними кодом. Не доведеться знову переписувати купу запитів на новий лад. Можна просто викликати метод-розширення AsParallel (), і тепер LINQ запит буде виконаний паралельно:















Послідовна версія  Паралельна версія 
from i in collection from i in collection.AsParallel()
where IsValid(i) where IsValid(i)
select i; select i;

Зрозуміло, виникає багато цікавих моментів – наприклад, у нашому прикладі ніхто не знає, в якому порядку елементи будуть оброблені при паралельному виконанні. Є свої особливості, пов'язані з обробкою винятків. Знову ж таки, не всякий LINQ-запит є сенс распараллелівать. Як завжди, потрібно правильно оцінювати ситуацію і діяти за обставинами. Але наявність такої легкої можливості распараллеліть процес виконання, безумовно, радує. Ще однією перевагою PLINQ є масштабованість. Поясню: запит буде виконаний настільки швидко, наскільки це можна в даній конкретній середовищі. Наша програма без перекомпіляції і без внесення будь-яких змін буде працювати з ефективністю 1х на одноядерной машині, 1,8 х-1, 9х на двоядерний, 3,7 х-3, 8х на чотирьохядерний і т.д. (Числа наведені приблизні, для порівняння; реальна різниця, звичайно, залежить від завдання і способу її реалізації). Тобто чим більш потужне обладнання буде використано, тим швидше виконається запит. PLINQ фізично є набором методів-розширень, що працюють з System.Linq.ParallelQuery <T>.

Крім того, Parallel Extensions містить кілька конструкцій, що полегшують створення розпаралелену циклів і запуск безлічі делегатів одночасно. Це набір статичних методів класу Parallel. Їх багато, але більшість є перевантаженими версіями наступних трьох:

Різні версії цих методів містять додаткові параметри, за допомогою яких можна вказати, наприклад, ступінь паралелізму, маркер відміни і т.д. Давайте розглянемо декілька простих прикладів. Припустимо, Тобто наступний цикл foreach:

Код:
IEnumerable<string> collection =
new [] {"The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"};

foreach (var word in collection)
{
    ProcessString(word);
}

Щоб перевести цей цикл на "паралельні рейки", досить замінити його викликом:

Код:
Parallel.ForEach (collection, (word) => {ProcessString (word);});

Тепер обробка слів буде виконана паралельно. Кожен виклик Parallel.For () і Parallel.ForEach () генерує певну кількість завдань, в залежності від поточного оточення, після чого передає їх для виконання робочим потокам. Т.ч., цикл буде виконаний настільки швидко, наскільки це можливо.

А ось так можна обмежити ступінь паралелізму:

Код:
ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism = 4;

Parallel.For(0, 100, options, (i) => { ProcessItem(i); });

Як бачите, використовується перевантажена версія Parallel.For (), приймаюча налаштування (ParallelOptions).


У доважок приклад обходу дерева такого вигляду:

Код:
class Tree<T>
{
    public T Data;
    public Tree<T> Left, Right;
   // …
}

Послідовно це можна зробити наступним чином:

Код:
static void WalkTree <T> (Tree <T> tree, Action <T> func)
{
    if (tree == null) return;
    WalkTree(tree.Left, func);
    WalkTree(tree.Right, func);
    func(tree.Data);
}

Однак, якщо обчислення не залежать від порядку обробки вузлів, то можна легко распараллеліть процес:

Код:
static void WalkTree <T> (Tree <T> tree, Action <T> func)
{
    if (tree == null) return;
    Parallel.Invoke(
        () => WalkTree(tree.Left, func),
        () => WalkTree(tree.Right, func),
        () => func(tree.Data));
}

Вказуємо набір делегатів, а саме прохід по лівому і правому нащадкам поточного вузла, і обробку поточного в якості параметрів Parallel.Invoke () і радіємо зрослої продуктивності. Треба відзначити, що Parallel.Invoke () також підтримує вказівку ParallelOptions, для цього є відповідний перевантажений метод.

У додаток до цього Parallel Extensions надає вельми зручні сховища даних з можливістю конкурентного доступу і набір примітивів синхронізації. Це виглядає абсолютно логічно: виконуючи дії в декількох потоках, непогано було б мати де-небудь результат роботи. У мене є 2 великі статті, в яких проводиться огляд цих структур даних, пропоную усім зацікавленим їх прочитати:


частина 1


і


частина 2


. Тут я не стану докладно на них зупинятися.

Отже, підіб'ємо підсумок. Те, що компанія Microsoft приділила велику увагу питанням паралельного виконання коду, дуже радує. Інструментарій, на мій погляд, вийшов досить зручним у використанні і досить простим. Взагалі, малий вхідний поріг – один з козирів Parallel Extensions. Концепція і API прозорі і зрозумілі. Ми отримали (я говорю саме "отримали", оскільки вже зараз вийшла GoLive ліцензія на VS 2010 beta 2, що дозволяє будувати комерційні додатки на її основі) зручну бібліотеку, яка дозволить мінімізувати витрати на розпаралелювання.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*