Українська
Декомпозиція даних та інтегрування
Декомпозиція векторів
Вектор з
Рис. 8.7. Розподіли вектора між потоками
- Блочний (block): потік
отримує суцільний діапазон індексів (ціле ділення). Власник елемента обчислюється приблизно як . Доступ до пам’яті послідовний, кожен потік працює зі своїми кеш-рядками. Це типовий розподілParallel.Forз діапазонами та програм MPI. - Циклічний (cyclic): потік
отримує елементи , тобто власник – . Добре вирівнює навантаження, коли «вага» елементів плавно змінюється вздовж вектора (наприклад, робота з рядком трикутної матриці), але сусідні елементи належать різним потокам: на спільній пам’яті кожен потік читає всі кеш-рядки, а у векторному коді SIMD неможливий. - Блочно-циклічний (block-cyclic) з розміром блоку
: блоки по елементів роздаються по колу, власник – . Поєднує переваги обох: доступ послідовний у межах блоку, а навантаження вирівнюється по всьому вектору. Так розподіляють матриці бібліотеки ScaLAPACK.
Для скалярного добутку Math.Max. У прикладі «Скалярний добуток і розподіли вектора» (вектори по 20 мільйонів double) блочний і блочно-циклічний розподіли дали прискорення 2,5 вже на 4 потоках, після чого воно зупинилося: 320 МБ даних читаються зі швидкістю приблизно 45 ГБ/с, і межею стає пропускна здатність пам’яті, як у темі 7. Циклічний розподіл на 8 і 16 потоках виявився повільнішим за послідовний цикл: кожен потік використовує лише одне з восьми чисел double кожного кеш-рядка, тому пам’яті та кешу доводиться передавати в
Недетермінованість сум з рухомою комою
Додавання чисел з рухомою комою не асоціативне:
- результат, що залежить від
, але однаковий між запусками (фіксований розподіл і додавання часткових сум у порядку номерів потоків), відтворюваний: його можна налагоджувати й порівнювати; - результат, що залежить від порядку завершення потоків (часткові суми додаються під
lockуlocalFinally,Interlocked-додавання дійсних чисел черезCompareExchange), змінюється від запуску до запуску навіть на тому самому комп’ютері.
У прикладі «Скалярний добуток і розподіли вектора» блочний розподіл на 4 потоках щоразу дає −111,15502539965507, а Parallel.For з локальними сумами та lock за п’ять запусків дав п’ять різних значень, що відрізняються в 13-й значущій цифрі. Для відтворюваних результатів:
- ділять дані на фіксовану кількість частин, яка не залежить від кількості потоків (у методі спряжених градієнтів лабораторної роботи – 64 частини), і додають часткові суми в порядку номерів частин: тоді результат однаковий для будь-якого
; - рекурсивну суму ділять навпіл за індексами (як у функції
Sumвище), а не за порядком виконання: дерево додавань залежить лише від даних; - порівнюють паралельний і послідовний результати з відносним допуском, а не оператором
==; - для дуже довгих сум використовують компенсоване додавання Кехена (Kahan summation), яке зменшує похибку округлення.
Декомпозиція матриць
Матрицю
- горизонтальні смуги (row-wise): потік отримує
суміжних рядків; - вертикальні смуги (column-wise): потік отримує
суміжних стовпців; - шахова схема (checkerboard, 2D block):
потоків утворюють решітку, і потік отримує блок з рядків і стовпців.
Рис. 8.8. Схеми декомпозиції матриці
Множення матриці на вектор
Для
Таблиця 8.4. Множення матриці на вектор за трьома схемами
| Схема | Обчислення потоку | Обміни (у розподіленій пам’яті) |
|---|---|---|
| горизонтальні смуги | кожен процес потребує весь вектор | |
| вертикальні смуги | частковий вектор | редукція часткових векторів: |
| шахова | частковий вектор довжини | розсилка частини |
Обсяг обчислень однаковий:
Приклад «Множення матриці на вектор» вимірює всі три схеми для двох розмірів матриці. Для матриці
Множення матриць
Добуток
Стрічковий алгоритм (striped algorithm). Процес
Алгоритм Фокса (Fox’s algorithm, 1987). Процеси утворюють решітку
- процес
, де , розсилає свій блок уздовж рядка решітки; - кожен процес рядка множить отриманий блок на свій поточний блок
і додає до ; - блоки
циклічно зсуваються на одну позицію вгору в межах стовпця.
Алгоритм Кеннона (Cannon’s algorithm, 1969) замінює розсилку циклічними зсувами (рис. 8.9). Спочатку виконується вирівнювання (skew): рядок
Рис. 8.9. Алгоритм Кеннона на решітці
Таблиця 8.5. Обчислення й обміни алгоритмів множення матриць
| Алгоритм | Обчислення | Обміни на процес | Пам’ять |
|---|---|---|---|
| стрічковий | |||
| Фокса | |||
| Кеннона |
Обсяг обмінів алгоритмів Фокса й Кеннона в Barrier. На спільній пам’яті переваги в обмінах немає, і смуги виявилися найшвидшими: усі потоки читають ту саму матрицю
Паралельне чисельне інтегрування
Визначений інтеграл
- прямокутників (середніх точок):
, похибка ; - трапецій:
, похибка ; - Сімпсона (
парне): , похибка .
Кожна формула – зважена сума значень MPI_Reduce, тема 12) – обмін лише одним числом.
Правило Рунге (Runge’s rule) оцінює похибку без точного значення. Якщо формула має порядок
Обчислення повторюють, подвоюючи
cs
static double Simpson(Func<double, double> f, double a, double b,
int n) // n – парне
{
double h = (b - a) / n;
double sum = 0;
object gate = new();
var ranges = Partitioner.Create(1, n, 100_000);
Parallel.ForEach(ranges, () => 0.0, (range, _, local) =>
{
for (int i = range.Item1; i < range.Item2; i++)
local += (i % 2 == 1 ? 4 : 2) * f(a + i * h);
return local;
}, local => { lock (gate) sum += local; });
return h / 3 * (f(a) + sum + f(b));
}
double i1 = Simpson(Math.Sin, 0, Math.PI, 100);
double i2 = Simpson(Math.Sin, 0, Math.PI, 200);
double runge = (i2 - i1) / 15; // оцінка I − I(2n)
Console.WriteLine($"I(n) = {i1:F12}, I(2n) = {i2:F12}");
Console.WriteLine($"Рунге: {runge:E2}, справжня похибка " +
$"{2 - i2:E2}");
Console.WriteLine($"Уточнене значення: {i2 + runge:F12}");Для
I(n) = 2,000000010825, I(2n) = 2,000000000676
Рунге: -6,77E-010, справжня похибка -6,76E-010
Уточнене значення: 2,000000000000Адаптивне інтегрування
Рівномірна сітка витрачає однаково багато обчислень і там, де функція майже стала, і там, де вона швидко змінюється. Адаптивне інтегрування (adaptive quadrature) ділить відрізок навпіл лише там, де оцінка похибки велика (рис. 8.10). Для формули Сімпсона на відрізку
Рис. 8.10. Адаптивне інтегрування: розбиття й дерево рекурсії
Адаптивний алгоритм – рекурсія «розділяй і володарюй», у якій обсяг роботи в різних частинах наперед невідомий. Статичний розподіл відрізка на Parallel.Invoke, а вільні потоки пулу забирають їх крадіжкою роботи. Щоб задачі не були надто дрібними, вводять поріг: до певної глибини рекурсії створюються задачі, глибше – звичайна послідовна рекурсія. За порогу
Кратні інтеграли та метод Монте-Карло
Кратний інтеграл Parallel.For, внутрішній – звичайна сума за формулою в одному напрямку, як двовимірна декомпозиція. Кількість вузлів зростає як
Метод Монте-Карло оцінює інтеграл середнім значенням функції у випадкових точках: Random не потокобезпечний, а для відтворюваності блоки фіксовані (тема 6). Наприклад, об’єм кулі радіуса 1 як частка 64 мільйонів випадкових точок куба
cs
const int Blocks = 64, PerBlock = 1_000_000;
long[] hits = new long[Blocks];
Parallel.For(0, Blocks, k =>
{
Random random = new(2026 + k); // свій генератор на блок
long inside = 0;
for (int i = 0; i < PerBlock; i++)
{
double x = random.NextDouble() * 2 - 1;
double y = random.NextDouble() * 2 - 1;
double z = random.NextDouble() * 2 - 1;
if (x * x + y * y + z * z <= 1) inside++;
}
hits[k] = inside;
});
double share = (double)hits.Sum() / ((long)Blocks * PerBlock);
double volume = 8 * share;
double sigma = 8 * Math.Sqrt(share * (1 - share)
/ ((long)Blocks * PerBlock));
Console.WriteLine($"V = {volume:F5} ± {1.96 * sigma:F5} " +
$"(точно {4 * Math.PI / 3:F5})");Результат містить 95-відсотковий довірчий інтервал
V = 4,18884 ± 0,00098 (точно 4,18879)