Задачи
- Выполните анализ загруженности вычислительной системы при разном режиме ожидания потока.
- Выполните анализ автоматического распараллеливания циклической обработки шаблоном Parallel.For.
- Выполните анализ распараллеливания PLINQ-запросов.
Методические указания
Среда разработки Visual Studio 12 содержит полезный инструмент для анализа эффективности выполнения параллельной программы – "Визуализатор параллелизма". Для запуска инструмента на вкладке "Анализ" запускаем "Визуализатор параллелизма" -> "Выполнить анализ с текущим процессом". Инструмент запускает программу и собирает информацию о её фактическом исполнении на данной вычислительной системе. Основные вкладки результатов: "Использование", "Потоки", "Ядра".
Использование ЦП
Вкладка содержит информацию об общей загрузке ЦП приложением в течение всего интервала выполнения.
Потоки
Вкладка "Потоки" выводит информацию о выполнении потоков, как пользовательских, так и рабочих потоков пула. Если в программе не используются объекты ThreadPool, Parallel, Task, то рабочие потоки простаивают. Для удобства восприятия информацию об отдельных потоках, например, простаивающих, можно скрыть. График "Сводка по потокам" суммирует время нахождения потоков в том или ином состоянии.
Ядра
Вкладка "Ядра" позволяет проанализировать перемещение потоков по фактическим исполнителям - ядрам процессора.
Анализ блокировки потоков
Применение "Визуализатора" позволяет исследовать особенности разных средств синхронизации.
Следующие фрагменты осуществляют блокировку основного потока с помощью цикла ожидания (активная блокировка) и с помощью встроенного механизма Join с выгружением контекста потока.
Thread t = new Thread(() => {
for (int i=0; i<10000; i++)
for(int j =0; j < 100; j++)
a[i] += Math.Sin(j) + i;
});
t.Start();
// Цикл ожидания
while(t.IsAlive) ;
// Фрагмент 2
Thread t = new Thread(() => {
for (int i=0; i<10000; i++)
for(int j =0; j < 100; j++)
a[i] += Math.Sin(j) + i;
});
t.Start();
t.Join();
Окно "Использование ЦП" иллюстрирует основной недостаток циклической блокировки – полезную работу осуществляет один поток, а вычислительные ресурсы заняты двумя потоками
Циклическая блокировка
Блокировка Join
При Join-блокировке основной поток №4808 почти все время находится в состоянии "Синхронизация", ожидая завершения потока №4848.
Вкладка "ядра" позволяет зафиксировать интересную особенность блокировок. При циклическом ожидании работают два потока (рабочий и основной), на двуядерной системе потоки практически не перемещаются между ядрами.
При блокировке основного потока методом Join, вычислительная система с двумя ядрами полностью предоставлена одному рабочему потоку. Операционная система активно перемещает поток с одного ядра на другое.
При таком выполнении существуют дополнительные накладные расходы, связанные с переключением контекста между ядрами (процент переключений между ядрами 48.16% от общего числа переключений).
При выполнении полезной обработки двумя и большим числом потоков загруженность системы увеличится, и доля переключений между ядрами будет минимальной.
Блокировка SpinWait комбинирует два типа ожидания.
Thread t = new Thread(() => {
for (int i=0; i<10000; i++)
for(int j =0; j < 100; j++)
a[i] += Math.Sin(j) + i;
});
t.Start();
// Цикл ожидания
SpinWait.SpinUntil(() => !t.IsAlive);
В начале цикла ожидания выполняется несколько прокруток, после чего ожидающий поток выгружается, освобождая вычислительные ресурсы.
Анализ выполнения объектов библиотеки TPL
Применение объектов TPL для распараллеливания скрывает от программиста работу по созданию и синхронизации потоков. "Визуализатор параллелизма" позволяет получить основную информацию о действиях среды выполнения по распараллеливанию задач.
Метод Parallel.For автоматически распределяет итерации цикла по рабочим потокам. Вкладка "Потоки" раскрывает особенности выполнения параллельной циклической обработки.
Специальная строка System.Threading.Tasks указывает на вызов и работу объекта TPL. В окне "Сводка" отображается итоговая информация о выполнении цикла. Рассматривая работу потоков, видим, что в каждый момент времени работают только два потока, другие потоки вытесняются. Основной поток также участвует в обработке цикла. Сначала для обработки используются два дополнительных рабочих потока, спустя какое-то время в работу включается еще один поток.