Розглянемо наступну дуже просту комп'ютерну програму:
for i = 1 to n:
y[i] = x[p[i]]
Тут і - -елементних масивів байтів, а - -елементний масив слів. Тут є великим, наприклад, (так що лише незначна частка даних вписується в будь-яку пам'ять кешу).
Припустимо, що складається з випадкових чисел , рівномірно розподілених між і .
З погляду сучасного обладнання, це повинно означати наступне:
- читання є дешевим (послідовне читання)
- читання є дуже дорогим (випадкові читання; майже всі читання - це пропуски кешу; нам доведеться вибирати кожен байт з основної пам'яті)
- писати - дешево (послідовне записування).
І це справді те, що я спостерігаю. Програма дуже повільна в порівнянні з програмою, яка робить тільки послідовне читання та запис. Чудово.
Тепер виникає питання: наскільки добре ця програма паралелізується на сучасних багатоядерних платформах?
Моя гіпотеза полягала в тому, що ця програма не є паралельною. Адже вузьке місце є основною пам’яттю. Одне ядро вже витрачає більшу частину свого часу, просто чекаючи деяких даних з головної пам'яті.
Однак це не те, що я спостерігав, коли я почав експериментувати з деякими алгоритмами, де вузьким місцем була така операція!
Я просто замінив наївний цикл на OpenMP паралельним for-loop (по суті, він просто розділить діапазон на більш дрібні частини і паралельно запустить ці частини на різних ядрах процесора).
На комп’ютерах низького класу прискорення дійсно були незначними. Але на платформах вищого класу мене здивувало, що я отримую відмінні майже лінійні скорочення. Деякі конкретні приклади (точні моменти часу можуть бути трохи відхилені, випадкових варіацій дуже багато; це були лише швидкі експерименти):
2 x 4-ядерний Xeon (загалом 8 ядер): коефіцієнт 5-8 прискорень порівняно з однопотоковою версією.
2 x 6-ядерний Xeon (загалом 12 ядер): прискорення з коефіцієнтом 8-14 порівняно з однопотоковою версією.
Тепер це було абсолютно несподівано. Запитання:
Саме чому така програма паралельно працює так добре ? Що відбувається з обладнанням? (Моя теперішня здогадка - це щось у цьому напрямку: випадкові читання з різних потоків є "конвеєрними", а середня швидкість отримання відповідей на них набагато вища, ніж у випадку з однією ниткою.)
Чи потрібно використовувати кілька потоків і декілька ядер, щоб отримати будь-які прискорення? Якщо якийсь конвеєрний процес дійсно має місце в інтерфейсі між основною пам'яттю та процесором, не вдалось би однопотоковому додатку повідомити головній пам'яті, що незабаром знадобиться , , ... і комп'ютер може почати вибирати відповідні рядки кешу з основної пам'яті? Якщо це можливо в принципі, як я це досягти на практиці?
Яку правильну теоретичну модель можна використати для аналізу такого роду програм (та правильних прогнозів ефективності)?
Редагувати: Зараз доступні деякі вихідні коди та результати порівняння: https://github.com/suomela/parallel-random-read
Деякі приклади фігур бального парку ( ):
- бл. 42 ns за ітерацію (випадкове зчитування) однією ниткою
- бл. 5 нс на ітерацію (випадкове зчитування) з 12 ядрами.