Основы многопоточного и распределенного программирования

       

Определите характеристики доступных вам многопроцессорных


1.1.    Определите характеристики доступных вам многопроцессорных машин. Сколько про­цессоров в каждой машине и каковы их рабочие частоты? Насколько велик размер их кэш-памяти, как она организована? Каково время доступа? Какой используется прото­кол согласования памяти? Как организована связующая сеть? Каково время удаленного доступа к памяти или передачи сообщения?
1.2.    Многие задачи можно решить более эффективно с помощью параллельной, а не после­довательной программы (конечно, при наличии соответствующего аппаратного обеспе­чения). Рассмотрите программы, которые вы писали раньше, и выберите две из них, ко­торые можно переписать как параллельные. Одна из них должна быть итеративной, а другая— рекурсивной. Затем: а) запишите кратко условия задач и б) разработайте псевдокод параллельных программ, решающих поставленные задачи.
1.3.    Рассмотрите умножение матриц в разделе 1.4:
а)   напишите последовательную программу для решения этой задачи. Размер мат­рицы п должен быть аргументом командной строки. Инициализируйте каждый элемент матриц а и Ь значением 1. О (тогда значение каждого элемента резуль­тирующей матрицы с будет п);
б)  напишите параллельную программу для решения этой задачи. Вычислите полосы результата параллельно, используя Р рабочих процессов. Размер матрицы п и число рабочих процессов Р должны быть аргументами командной строки. Вновь инициа­лизируйте каждый элемент матриц а и b значением 1.0;
в)  сравните производительность ваших программ. Поэкспериментируйте с разны­ми значениями параметров n и р. Подготовьте график результатов и объясните, что вы заметили;
44                                                                  Глава 1. Обзор области параллельных вычислений
г) преобразуйте ваши программы для умножения прямоугольных матриц. Размер мат­рицы а должен быть pxq, а матрицы b — qxr. Тогда размер результирующей мат­рицы будет рхг. Повторите часть в данного упражнения для новых программ.
1.4.
2.1.    Разберите эскиз программы в листинге 2.1, выводящей все строки с шаблоном pat­tern в файл:
а)   разработайте недостающий код для синхронизации доступа к буферу buffer. Для программирования синхронизации используйте оператор await;
б)  расширьте программу так, чтобы она читала два файла и выводила все строки, содер­жащие шаблон pattern. Определите независимые операции и используйте отдельный процесс для каждой из них. Напишите весь необходимый код синхронизации.
2.2.   Рассмотрите решение задачи копирования массива в листинге 2.2. Измените код так, чтобы переменная р была локальной для процесса-производителя, а с — для потребите­ля. Следовательно, эти переменные нельзя использовать для синхронизации доступа к буферу buf. Вместо них используйте для синхронизации процессов две новые булевы переменные empty и full. В начальном состоянии переменная empty имеет значение true, full — false. Добавьте новый код для процессов потребителя и производителя. Для программирования синхронизации используйте оператор await.
2.3.    Команда ОС Unix tee вызывается выполнением:
tee filename


Эта команда читает стандартный ввод и записывает его в стандартный вывод и в файл filename, т.е. создает две копии ввода:
а)   запишите последовательную программу для реализации этой команды;
б)  распараллельте вашу последовательную программу, чтобы она использовала три процесса: чтения из стандартного ввода, записи в стандартный вывод и записи в файл filename. Используйте стиль "со внутри while";
80                                                 Часть 1. Программирование с разделяемыми переменными
в) измените свое решение из пункта 6, используя стиль "while внутри со". В частно­сти, создайте процессы один раз. Используйте двойную буферизацию, чтобы можно было параллельно читать и писать. Для синхронизации доступа к буферам исполь­зуйте оператор await.
2.4.   Рассмотрите упрощенную версию команды ОС Unix di ff для сравнения двух текстовых файлов.


6.1.    В многопроцессорном ядре, описанном в разделе 6.2, процессор выполняет процесс Idle (см. листинг 6.2), когда определяет, что список готовых к работе процессов пуст. На некоторых машинах есть бит в регистре (слове) состояния процессора. Его установка означает, что процессор должен бездействовать до возникновения прерывания (бит без­действия). В таких машинах есть и межпроцессорные прерывания, т.е. один процессор может быть прерван другим, в результате чего он входит в обработчик прерывания ядра на центральном процессоре.
• Измените многопроцессорное ядро в листинге 6.3 так, чтобы процессор сам устанавли­вал бит бездействия, когда список готовых к работе процессов пуст. Таким образом, для запуска процесса на одном процессоре потребуется выполнить команду на другом.
6.2.    Предположим, что планирование процессов ведется одним главным процессором, вы­полняющим только процесс-диспетчер. Другие процессоры выполняют обычные про­цессы и процедуры ядра.
Разработайте процесс-диспетчер и соответственно измените многопроцессорное ядро в листинге 6.3. Определите все необходимые структуры данных. Помните, что бездейст­вующий процессор не должен блокироваться внутри ядра, поскольку тогда в ядро не смогут войти остальные процессы.
6.3.    Предположим, что все процессоры мультипроцессорной системы имеют собственные списки готовых к работе процессов и выполняют процессы только из них. Как было от­мечено в тексте, возникает проблема балансировки нагрузки процессоров, поскольку новый процесс приходится назначать какому-нибудь из процессоров.
Существует много схем балансировки нагрузки процессоров, например, можно назна­чать новый процесс случайному процессору, "соседу" или поддерживать приблизитель­но одинаковую длину списков готовых к работе процессов. Выберите одну из схем, обоснуйте свой выбор и измените многопроцессорное ядро в листинге 6.3 так, чтобы в нем использовались несколько списков готовых к работе процессов и выбранная схема балансировки нагрузки процессоров.


10.1.   Рассмотрим распределенное ядро в листингах 10.2 и 10.3:
а)  расширьте реализацию, чтобы у канала могло быть несколько получателей. В част­ности, измените примитивы receiveChan и emptyChan, чтобы процесс на одной машине мог получать доступ к каналу, расположенному на другой;
б)  измените ядро, чтобы примитив sendChan стал полу синхронным, т.е., вызывая sendChan, процесс должен дождаться постановки сообщения в очередь канала (или передачи получателю), даже если канал расположен на другой машине;
в)   добавьте в ядро код определения окончания программы. Можно не учитывать ожи­дающий ввод-вывод. Вычисления завершены, когда все списки готовых к работе процессов пусты и сеть бездействует.
10.2.   Реализация синхронной передачи сообщений в листинге 10.4 предполагает, что и про­цесс-источник,  и  процесс-приемник именуют друг друга.  Обычно же  процесс-приемник должен либо указывать источник, либо принимать сообщения от любого ис-
Глава 10. Реализация языковых механизмов                                                                             401
точника. Предположим, что процессы пронумерованы от 1 до п и индекс 0 использует­ся процессом-приемником для указания, что он готов принять сообщение от любого источника. В последнем случае оператор ввода присваивает параметру source иденти­фикатор процесса, приславшего сообщение.
Измените протоколы взаимодействия в листинге 10.4, чтобы они обрабатывали описан­ную ситуацию. Как и в листинге, основой для реализации описанной выше формы син­хронной передачи сообщений должна служить асинхронная передача сообщений.
10.3.  Разработайте реализацию в ядре примитивов синхронной передачи сообщений synch_send и synch_receive, определенных в начале раздела 10.2. Сначала по­стройте однопроцессорное ядро. Затем разработайте распределенное ядро со струк­турой, показанной в листинге 10.1. Все необходимые процедуры можно брать из лис­тингов 10.2 и 10.3.
10.4.  Даны процессы Р[ 1:п], каждый из которых содержит значение элемента a[i] масси­ва из п значений.


11.1.  В начале подраздела по последовательным итерациям Якоби представлена простая по­следовательная программа. Затем описаны четыре оптимизации, приводящие к про­грамме в листинге 11.1, и еще две оптимизации:
а)  постройте серию экспериментов для измерения индивидуального и совокупного эффекта от данных шести оптимизаций. Начните с измерения времени выполнения главного цикла вычислений в программе для метода итераций Якоби на сетках раз­личных размеров, например 64x64, 128x128 и 256x256. Подберите такие значения EPSILON и/или iters, чтобы вычисления занимали несколько минут. (Время должно быть достаточно большим, чтобы заметить эффект от улучшений.) Затем до­бавляйте в код по одной оптимизации и измеряйте повышение производительности. Составьте краткий отчет с результатами измерений и выводами;
б)   проведите эксперименты, позволяющие оценить все шесть оптимизаций по отдель­ности и в различных комбинациях друг с другом, в отличие от пункта а, где оптими­зации добавлялись одна за другой. Какие оптимизации оказались наиболее продук­тивными? Укажите, имеет ли значение порядок их применения;
в)   рассмотрите другие способы оптимизации программы. Ваша цель — получить мак­симально быструю программу. Опишите и измерьте эффект от каждой дополни­тельной оптимизации, которую вы придумали.
11.2.  Реализуйте программу для метода итераций Якоби с разделяемыми переменными (см. листинг 11.2) и измерьте ее производительность. Используйте разные размеры се­ток и количества процессоров. Оптимизируйте программу своим способом и измерьте производительность улучшенной программы. Составьте отчет, в котором будут описаны все проделанные изменения, представлены результаты измерений и выводы.
11.3.   Реализуйте неоптимизированную и оптимизированные программы для метода итера­ций Якоби с передачей сообщений (см. листинги 11.3 и 11.4) и измерьте их производи­тельность. Используйте сетки разных размеров и разное количество процессоров. Затем оптимизируйте программы своим способом и измерьте производительность улучшен­ных программ.


12.1.  В разделе 12. 1 представлены реализации метода итераций Якоби, в которых использу­ются библиотеки Pthreads, MPI и ОрепМР. С помощью библиотек Pthreads, MPI и ОрепМР составьте параллельные программы:
а)  для задачи п тел;
б)   для реализации LU-разложения.
Для программ с Pthreads и ОрепМР используйте разделяемые переменные, а с MPI — обмен сообщениями. Последовательные части своих программ напишите на С для Pthreads и на С или Фортране для MPI и ОрепМР.
12.2.  Рассмотрите последовательные программы для следующих задач:
а)   метод итераций Якоби (листинг 11.1);
б)   задача п тел (листинг 11.6);
в)   LU-разложение (листинг 11.11);
г)   прямой и обратный ход (листинг 11.12).
В каждой программе выделите все случаи: 1) потоковых зависимостей, 2) антизависимостей, 3) зависимостей по выходу. Укажите, какие из зависимостей на­ходятся в циклах, а какие создаются циклами.
12.3.  Рассмотрим следующие последовательные программы:
а)   метод итераций Якоби (листинг 11.1);
б)   задача п тел (листинг 11.6);
в)   LU-разложение (листинг 11.11);
г)   прямой и обратный ход (листинг 11.12).
На рис. 12.1 перечислены некоторые типы преобразований программ в распараллели­вающих компиляторах. Для каждого преобразования и каждой из перечисленных выше последовательных программ определите, можно ли применить преобразование к про­грамме, и, если можно, покажите, как это сделать. Каждое преобразование рассматри­вайте независимо от других.
12.4.  Повторите предыдущее упражнение, применяя в каждой программе несколько преоб­разований. Предположим, что программа пишется для машины с разделяемой памятью, имеющей восемь процессоров, а размер задачи п кратен восьми. Ваша цель — создать программу, которую совсем просто превратить в параллельную программу со сбаланси­рованной вычислительной нагрузкой, удачным распределением данных и небольшими накладными расходами синхронизации. Для каждой программы: 1) подберите последо­вательность преобразований, 2) покажите, как изменяется программа после каждого преобразования, и 3) объясните, почему вы считаете, что либо полученный код приве­дет к хорошей параллельной программе, либо из исходной программы получить хоро­шую параллельную программу нельзя.

Содержание раздела