Федеральное агентство по образованию Государственное образовательное учреждение высшего профессионального образования ТОМСКИЙ ПОЛИТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ Лаборатория мультифизического моделирования на базе вычислительного кластера «СКИФ-политех» Программирование, компиляция и запуск программ с использованием вычислительного кластера «СКИФ-политех» Томск 2009 Содержание Содержание ............................................................................................................ 2 Удалённый доступ ................................................................................................. 3 Введение в ОС UNIX ............................................................................................ 5 Документация ..................................................................................................... 7 Некоторые другие базовые команды ОС Unix ............................................. 10 Обмен данными с кластером .............................................................................. 12 Перекодировка файлов .................................................................................... 12 Передача файлов .............................................................................................. 12 Первая программа в Unix ................................................................................... 15 Создание и редактирование файлов .............................................................. 15 Компиляция и запуск программ ..................................................................... 19 Программирование с использованием MPI ...................................................... 22 Отправка сообщения другому процессу: MPI_Send() ............................. 23 Прием сообщения от другого процесса: MPI_Recv() .............................. 24 Первая программа MPI: скалярное произведение векторов ....................... 24 Запуск параллельных приложений на кластере ............................................... 29 Компиляция программ MPI ............................................................................ 29 Постановка задачи в очередь .......................................................................... 30 Пояснение к системе приоритетов ................................................................. 31 Просмотр результатов ..................................................................................... 31 Просмотр состояния очереди ......................................................................... 37 Удаление задачи ............................................................................................... 37 2 Удалённый доступ Удаленный доступ к вычислительному кластеру осуществляется через головную машину комплекса (master.hpc.cctpu.edu.ru, IP-адрес: 109.123.146.178) и только с тех компьютеров, IP-адреса которых были указаны в заявке при регистрации (при попытке зайти с неуказанного адреса программа будет долго ждать и сообщит о потере связи по таймауту). Непосредственный терминальный доступ на узлы кластера не допускается. В настоящий момент удаленный терминальный доступ на кластер осуществляется по протоколу SSH версии 2. На Unix-машинах этот протокол поддерживается стандартной командой ssh. Для Windows рекомендуется программа PuTTY. Другие клиентские программы для Windows, поддерживающие протокол SSH 2 - это SSH Secure Shell Client и Teraterm последних версий. Рис. 1. Внешний вид окна настройки программы Putty. После нажатия кнопки Open откроется окно удалённой командной оболочки, с приглашением ввода логина и пароля. 3 Рис. 2. Окно приглашения удалённой командной строки. Обратите внимание, что в Unix-системах вводимые пароли, в отличие от Windows, не отображаются на экране даже в виде звездочек (клавиша Backspace, тем не менее, работает), этого не следует пугаться, просто нажмите Enter по завершению ввода, как обычно. Так сделано затем, чтобы нельзя было узнать хотя бы длину пароля (даже стуком клавиш можно обмануть, стирая лишние символы). Программа Putty работает как эмулятор терминала, и поэтому выступает как комбинация клавиатуры и монитора для удаленной машины. Поэтому многие привычные комбинации кнопок работать не будут – например, сочетание Ctrl-C будет передано на удаленную сторону и скорее всего вызовет прерывание работы текущей программы, а не копирование в буфер обмена, о котором удаленная сторона ничего не знает, поскольку он находится исключительно на вашей машине. Если вы выделите мышью на экране Putty текст, он будет скопирован в буфер обмена Windows сразу, автоматически, без дополнительных нажатий клавиш (точно так же ведут себя и все графические программы в Unix). Если Вам нужно скопировать прямоугольный блок, при выделении зажмите кнопку Alt, если какая-либо программа захватила управление мышью и выделения не происходит, зажмите кнопку Shift. Изменить область выделения можно (например, расширить) можно щелчком средней кнопки мыши. Вставить текст из буфера обмена Windows в окно Putty можно нажатием правой кнопки мыши (при этом удаленная сторона по-прежнему ничего не знает о буфере обмена, и считает, что текст был набран). Следует отметить, что в графических программах Unix наоборот, правая кнопка мыши используется для расширения выделения, а средняя – для вставки. Изменить данное поведение можно в категории Selection настроек Putty. 4 ВВЕДЕНИЕ В ОС UNIX Установленная на головной машине и всех узлах кластера ОС SUSE Linux Enterprise Server относится к семейству Unix-подобных систем, берущих начало от оригинальной версии Unix 1969 года. Предполагается знакомство читателя с командной строкой DOS или Windows, поскольку основным интерфейсом управления Unix-системами, достаточным для редактирования текста и запуска программ, является командная строка. Графический интерфейс предъявляет повышенные требования к пропускной способности сети для нормальной работы, используется на нашем кластере только для сторонних пакетов типа Wolfram Mathematica или Mathlab, и в данном руководстве не рассматривается. Несмотря на общие корни, командная строка в Unix эволюционировала более быстро, и кроме имён команд, отличается также в некоторых принципах. Так, Unix имеет более чем одну консоль, т.е. комбинацию монитор+клавиатура (устройство con из DOS/Windows), здесь они называются терминалами. Когда Вы заходите на кластер через сеть, создается виртуальный псевдотерминал (pseudo tty), поскольку программа PuTTY здесь эмулирует терминал (клавиатуру и монитор) через сеть. После входа вы попадаете в командный интерпретатор, так называемую оболочку (shell), которая является родителем всех своих дочерних процессов, после выхода из него терминал уничтожаются, и все привязанным к нему программам будет послан сигнал завершения, если они еще не успели сделать это до того. Здесь более широко, чем в DOS/Windows, используются понятия стандартных файлов ввода (stdin), вывода (stdout) и стандартного файла ошибок (stderr), поскольку программы не имеют прямого доступа к монитору, поэтому более широко используются перенаправления ввода-вывода, например cmd > file.txt или cmd1 | cmd2, при этом, в силу многозадачности, программы (процессы) работают одновременно. В дальнейших примерах мы будем показывать вводимый пользователем текст полужирным шрифтом. Аналогично выводу в DOS строки типа C:\> перед каждой командой, оболочка Unix тоже показывает своё приглашение: test@master:~/my_progs> ls scalar scalar.out-212 scalar.out-220 scalar.rep-208 scalar.c scalar.out-213 scalar.out-274 scalar.rep-212 scalar.c~ scalar.out-214 scalar.out-276 scalar.rep-213 scalar.o scalar.out-215 scalar.out-279 scalar.rep-214 scalar.out-205 scalar.out-216 scalar.out-287 scalar.rep-215 scalar.out-206 scalar.out-217 scalar.rep-205 scalar.rep-216 scalar.out-207 scalar.out-218 scalar.rep-206 scalar.rep-217 scalar.out-208 scalar.out-219 scalar.rep-207 scalar.rep-218 test@master:~/my_progs> pwd /home/test/my_progs test@master:~/my_progs> cd .. test@master:~> pwd /home/test test@master:~> cd my_progs test@master:~/my_progs> ls -l total 212 -rwxr-xr-x 1 test users 13753 Mar 20 2007 scalar -rw-r--r-- 1 test users 2257 Mar 20 2007 scalar.c scalar.rep-219 scalar.rep-220 scalar.rep-274 scalar.rep-276 scalar.rep-279 scalar.rep-287 5 -rw-r--r--rw-r--r--rw-r--r--rw-r--r-- 1 1 1 1 test test test test users users users users 2260 5464 0 0 Mar Mar Mar Mar 20 20 20 20 2007 2007 2007 2007 scalar.c~ scalar.o Makefile mAKEFILe Из этого примера уже можно видеть несколько базовых команд Unix: ls – показать список файлов в текущем каталоге pwd – показать полный путь текущего каталога (print working directory), полезно, если Вы забыли где находитесь (приглашение оболочки не всегда бывает настроено так, чтобы отображать текущий каталог) cd или chdir – сменить текущий каталог (как и в DOS) Кроме того, видны и особенности системы, например, что в приглашении отображается имя пользователя (test) на головной машине (master), и текущий путь относительно домашнего каталога пользователя, который в Unix по соглашению часто обозначают одним символом тильды (~). Видно также то, что в Unix опции команд указывают через минус: ls -l показывает список в длинном (long) формате, включающем размеры файлов, даты модификации и т.д. Еще можно заметить, что в Unix имеет значение регистр имени файла – в Windows бы регистр имени файла Makefile бы сохранился, в отличие от DOS, но создать файл mAKEFILe система бы уже не дала. Однако наиболее важным видным здесь отличием является то, что в Unix нет отдельных дисков и их букв (C:, D:, …), а есть единый корневой каталог, обозначаемый просто /, и все пути отсчитываются от него, разделителем каталогов является тоже прямой слэш (/) вместо обратного (\) из DOS и Windows. Различные физические диски просто монтируются (mount) в произвольную точку общего дерева каталогов, что заметно, например, в выводе команды отображения свободного места на дисках (по умолчанию в килобайтах): test@master:~/my_progs> df Filesystem 1K-blocks Used Available /dev/sda2 165140368 91625316 65126312 udev 4089704 132 4089572 /dev/sda4 30211520 15328 30196192 panfs://192.168.10.252/data 4271186424 3269816068 1001370356 panfs://192.168.10.252/khav 488281248 3508 88277740 Use% 59% 1% 1% 77% 1% Mounted on / /dev /boot /panasas /share/khav Другим важным отличием командной оболочки Unix является то, что shell на самом деле представляет собой полноценный язык программирования, и имеет свои синтаксические особенности и предобработку, не выполняя набранное просто «как есть». Так, символы шаблонов ? и * здесь обрабатывает сама оболочка, и когда Вы в нижеследующем примере набираете один аргумент, в программу на самом деле будут переданы три, что следует иметь в виду при программировании (однако, это избавляет Вас от работы по ручной обработке шаблонов): test@master:~/my_progs> ls -l -rw-r--r-- 1 test 600 270 Mar -rw-r--r-- 1 test 600 269 Mar -rw-r--r-- 1 test 600 270 Mar scalar.rep-27* 26 2007 scalar.rep-274 26 2007 scalar.rep-276 26 2007 scalar.rep-279 6 test@master:~/my_progs> echo ~ /home/test test@master:~/my_progs> ls -l scalar.rep-27[46] -rw-r--r-- 1 test 600 270 Mar 26 2007 scalar.rep-274 -rw-r--r-- 1 test 600 269 Mar 26 2007 scalar.rep-276 test@master:~/my_progs> ls -l scalar.[ro][eu][pt]-20* -rw------- 1 test 600 13956 Mar 20 2007 scalar.out-205 -rw------- 1 test 600 2372 Mar 20 2007 scalar.out-206 -rw------- 1 test 600 10200 Mar 20 2007 scalar.out-207 -rw------- 1 test 600 804 Mar 20 2007 scalar.out-208 -rw-r--r-- 1 test 600 1183 Mar 20 2007 scalar.rep-205 -rw-r--r-- 1 test 600 411 Mar 20 2007 scalar.rep-206 -rw-r--r-- 1 test 600 935 Mar 20 2007 scalar.rep-207 -rw-r--r-- 1 test 600 355 Mar 20 2007 scalar.rep-208 test@master:~/my_progs> ls -l scalar.out-21? -rw------- 1 test 600 2808 Mar 20 2007 scalar.out-212 -rw------- 1 test 600 2527 Mar 20 2007 scalar.out-213 -rw------- 1 test 600 1956 Mar 20 2007 scalar.out-214 -rw------- 1 test 600 2517 Mar 20 2007 scalar.out-215 -rw------- 1 test 600 1710 Mar 20 2007 scalar.out-216 -rw------- 1 test 600 2515 Mar 20 2007 scalar.out-217 -rw------- 1 test 600 1651 Mar 20 2007 scalar.out-218 -rw------- 1 test 600 13461 Mar 20 2007 scalar.out-219 test@master:~/my_progs> ls -l scalar.out-21[4-8] -rw------- 1 test 600 1956 Mar 20 2007 scalar.out-214 -rw------- 1 test 600 2517 Mar 20 2007 scalar.out-215 -rw------- 1 test 600 1710 Mar 20 2007 scalar.out-216 -rw------- 1 test 600 2515 Mar 20 2007 scalar.out-217 -rw------- 1 test 600 1651 Mar 20 2007 scalar.out-218 test@master:~/my_progs> ls -l scalar.out-21[^4-8] -rw------- 1 test 600 2808 Mar 20 2007 scalar.out-212 -rw------- 1 test 600 2527 Mar 20 2007 scalar.out-213 -rw------- 1 test 600 13461 Mar 20 2007 scalar.out-219 В этом примере можно увидеть, что оболочка в Unix умеет также еще один вид символов шаблонов – перечисление символов в квадратных скобках. Работает это аналогично символу ?, означающему «любой символ», но здесь символ будет не абсолютно любым, а ограничен набором из перечисленных. Как видно из примера, перечисления можно задавать через диапазон, можно задавать и отрицание символом ^ в начале перечисления, означающим «любой символ не из указанного набора». Документация Unix предоставляет систему документации, доступную непосредственно в командной строке, так называемый online manual, оперативный справочник. Документация по каждой команде, а зачастую и не только команде, доступна в соответствующей так называемой man-странице (сокращение от manual, руководство), странице справочника. Вызывается он командой man с указанием имени страницы, которую следует показать. Следует заметить, что это именно справочник, а не учебник, и он предполагает, что знание фундаментальных вещей уже присутствует. В документации по Unix приняты несколько соглашений. Рассмотрим их на примере первых строк двух страниц справочника: 7 test@master:~> man free FREE(1) Linux User's Manual FREE(1) NAME free - Display amount of free and used memory in the system SYNOPSIS free [-b | -k | -m] [-o] [-s delay ] [-t] [-V] DESCRIPTION free displays the total amount of free and used physical and swap memory in the system, as well as the buffers used by the kernel. ... test@master:~> man gzip GZIP(1) GZIP(1) NAME gzip, gunzip, zcat - compress or expand files SYNOPSIS gzip [ -acdfhlLnNrtvV19 ] [-S suffix] [ name ... ] gunzip [ -acfhlLnNrtvV ] [-S suffix] [ name ... ] zcat [ -fhLV ] [ name ... ] DESCRIPTION Gzip reduces the size of the named files using Lempel-Ziv coding ... SEE ALSO znew(1), zcmp(1), zmore(1), zforce(1), gzexe(1), zip(1), unzip(1), compress(1), pack(1), compact(1) ... Здесь в самом начале и в конце печатается имя страницы справочника и к какому разделу она относится, затем любая страницы справочника начинается всегда с одних и тех же разделов: NAME – краткое однострочное описание (по нему можно делать поиск) SYNOPSIS – краткое описание синтаксиса вызова DESCRIPTION – собственно подробное описание команды После этого может идти разное количество разделов, в зависимости от сложности той или иной команды. Часто включают раздел EXAMPLES с примерами запуска для реальных ситуаций, раздел SEE ALSO (“см. также”) перечисляет ссылки на другие страницы справочника, которые могут быть интересны по этой теме. В примерах можно видеть основные принятые соглашения по записи синтаксиса (их будем придерживаться далее и мы): В квадратных скобках перечисляются опциональные элементы, то есть то, что можно опустить. Во многих случаях в документации обязательные аргументы указываются в угловых скобках, вида cmd <arg1> <arg2> - здесь два обязательных элемента, причем сами угловые скобки писать не надо. Следует внимательно смотреть на точную форму записи, потому что если угловые скобки отделены пробелами – это указывает уже на использование символов перенаправления ввода-вывода оболочки, а не типографское соглашение. Страницы справочника используют это 8 соглашение редко (обязательные параметры показаны просто как есть), но оно может попасться в справке самих команд или иной документации. Если какие-то элементы взаимоисключают друг друга, они будут указаны через символ | вертикальной черты. Однобуквенные опции-флаги без аргументов, в отличие от документации DOS, могут быть сокращены до перечисления всех опций разом, то есть вместо [-a] [-b] [-c] будет написано [-abc]. Троеточие означает неограниченное повторение элемента, т.е. [filename] означает одно имя файла, а [filename ...] – любое количество имён файлов (квадратные скобки указывают, что в обоих случаях их может быть нулевое количество – т.е. они опциональны). Одна man-страница может описывать сразу несколько команд, тогда в разделе SYNOPSIS будет описано несколько вариантов запуска, для каждой из команд, либо взаимоисключающие варианты запуска одной команды. Система страниц справочника предоставляет возможность поиска по подстроке в однострочных описаниях страниц командой apropos: test@master:~> apropos concat zcat (1p) - expand and concatenate data tac (1) - concatenate and print files in reverse wcscat (3p) - concatenate two wide-character strings ntfscat (8) - concatenate files and print them on the standard output cat (1) - concatenate files and print on the standard output wcscat (3) - concatenate two wide-character strings strcat (3p) - concatenate two strings wcsncat (3p) - concatenate a wide-character string with part of another strcat (3) - concatenate two strings cat (1p) - concatenate and print files wcsncat (3) - concatenate two wide-character strings strncat (3) - concatenate two strings Можно видеть, что ссылки на страницы справочника всегда пишутся как имя страницы и номер в скобках – это номер раздела справочника. В разных разделах справочника могут быть страницы с одинаковыми именами, в этом случае для вызова нужной страницы нужно указать номер раздела перед именем через пробел (по умолчанию показывается первая найденная страница): test@master:~> man kill KILL(1) User Commands KILL(1) NAME kill - send signals to processes, or list signals SYNOPSIS kill [-s SIGNAL | -SIGNAL] PID... kill -l [SIGNAL]... kill -t [SIGNAL]... DESCRIPTION Send signals to processes, or list signals. ... 9 test@master:~> man 2 kill KILL(2) Linux Programmer's Manual NAME kill - send signal to a process KILL(2) SYNOPSIS #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); DESCRIPTION The kill() system group or process. ... call can be used to send any signal to any process Из этого примера видно, что страницы справочника существуют не только для команд оболочки, но и функций установленных библиотек программирования, и даже совершеннно не про команды (например, страница regex(7) расскажет вам о синтаксисе регулярных выражений, применяемых во многих программах Unix). Дальнейшее вы можете узнать с помощью команд: man man (справка по самому справочнику) man 1 intro man 2 intro и т.д. по аналогии и ссылкам из прочитанных страниц. Некоторые другие базовые команды ОС Unix Помимо уже рассмотренных выше команд, для начальной работы могут быть необходимы еще несколько: cp – копировать файлы; mv – переместить (переименовать) файл; rm – удалить файл; chmod – изменить права доступа к файлу (например, сделать исполнимым); mkdir – создать каталог; rmdir – удалить (пустой) каталог; du – посчитать занимаемое подкаталогом (его файлами) место на диске; touch – создать пустой файл или обновить время модификации файла; cat – показать файлы на стандартный вывод, можно склеивать файлы; split – разрезать файлы; less – пролистать содержимое файла (можно проматывать назад); head – показать первые несколько строк файла (по умолчанию 10); tail – показать последние несколько строк файла (по умолчанию 10); grep – вывести строки из файла, в которых встречается подстрока; diff – вывести различия между двумя текстовыми файлами; sort – отсортировать текстовый файл; bc –калькулятор с произвольной точностью (но медленный); find – найти файлы по критерию (можно задавать сложные условия); gzip – сжать файл; 10 tar – создать/распаковать архив нескольких файлов; ftp – клиент командной строки, позволяющий работать с ftp-сервером; joe – простой редактор файлов; vi или vim – мощный редактор файлов, рассчитан на программиста; exit или logout – выйти из оболочки (отключиться от машины). Напоминаем, что справку по любой команде можно получить командой man имя_команды. Кроме того, на кластере установлен также файловый менеджер mc, напоминающий Norton Commander или Far Manager, однако обладающий существенно меньшими возможностями. Он пригоден на начальных этапах освоения системы (обладает также простым встроенным редактором файлов), однако для сложных случаев (например, написания скриптов, автоматизирующих какие-либо задачи) всё равно потребуется знание командной строки и её утилит. 11 ОБМЕН ДАННЫМИ С КЛАСТЕРОМ Лишь в редких случаях (при малом объеме) расчетная задача на кластере позволяет создать программный файл целиком с нуля на самом кластере, и просмотреть результаты на нём же. Скорее всего, Вы захотите передать на кластер уже имеющиеся у Вас исходные тексты программ расчетов, потом отредактировать их так, чтобы они могли запускаться в Unix и использовать многопроцессорную среду, а затем, возможно, забрать с кластера результаты расчетов для анализа. Перекодировка файлов При передаче текстовых файлов из среды Windows следует помнить, что на Unix используется свое соглашение по символам перевода строки (один символ с кодом 10, LF), отличное от соглашения в среде DOS/Windows (два символа с кодами 13, CR, и 10, LF). Если Вы будете компилировать исходные тексты программ, которые записаны в соглашении DOS/Windows, то Вы можете получить множество предупреждений или ошибок, видеть в редакторе непонятные вещи в конце строк (типа ^M), и т.д. На головной машине кластера установлена утилита перекодировки из формата в DOS в формат Unix – dos2unix. Верно и обратное – при передаче файлов с Unix на Windows большинство программ так же не воспримут переводы строк в другом соглашении (например, «Блокнот» Windows покажет всё в одну строку). В то же время многие программы Windows умеют открывать такие файлы (например, Wordpad), а редактор из состава Far Manager делает это автоматически, умея даже сохранять файлы с переводом строк стиля Unix. В среде Unix традиционной для русских символов является кодировка KOI8, тогда как в средах DOS и Windows используются свои кодировки (866, 1251, иногда другие). Для выполнения перекодировки файлов с русскими буквами из кодировок DOS и Windows в KOI8-R и обратно можно воспользоваться утилитой iconv, умеющей большое количество разных кодировок, например, команда: test@master:~/my_progs> iconv -f cp1251 -t koi8-r win-rus.txt > koi-rus.txt прочитает файл win-rus.txt с русским текстом в кодировке Windows-1251, и выведет его перекодированным на стандартный вывод, где перенаправление оболочки запишет его в файл koi-rus.txt (можно перекодировать и в обратном направлении). Передача файлов Передача файлов может осуществляться как с помощью обычного FTP, так и по протоколу sftp, или копированию поверх протокола ssh. В случае использования обычного протокола FTP – Вы кладёте файлы на ftp-сервер, и заходите на него с кластера интерактивным клиентом командной строки ftp, позволяющим как скачивать, так и закачивать на ftp-сервер файлы (для автоматизированной скачки с ftp-сервера на кластер большого количества 12 файлов можно также воспользоваться установленной на кластере утилитой wget). Пользоваться клиентом достаточно просто: он поддерживает команду help, набрав которую можно получить список его собственных команд; для соединения с сервером следует воспользоваться командой open, для передачи файлов – get и put (есть и другие, обратитесь к странице справочника). Кроме того, ftp-клиент встроен в файловый менеджер mc. Если же у Вас нет доступного ftp-сервера для временного переброса данных, придется воспользоваться передачей файлов поверх такого же sshсоединения, что используется и для обычной работы на кластере (что, впрочем, имеет преимущества в виде шифрования данных, которое, правда, несколько замедляет их передачу). В пакет установки ssh-клиента Putty входят программы pscp и psftp, которые реализуют функции передачи данных, это аналоги команд scp и sftp из Unix (таким образом, кроме справки Putty, можно воспользоваться страницами справочника Unix). Заметьте, что в данном случае Вы будете работать в командной строке своей машины с Windows, а не на кластере! Для копирования нескольких файлов необходимо запустить программу psftp, далее команды аналогичны командам стандартного ftp-клиента для Linux: Рис. 3. Окно программы psftp Для использования программы pscp необходимо перейти в каталог, в котором находится программа pscp (либо убедиться, что каталог Putty доступен в PATH) и выполнить в командной строке Windows: 13 Для загрузки с кластера: pscp.exe <user>@<host>:<source> <target> Для копирования на кластер: pscp.exe <source> <user>@<host>:<target> То есть, здесь, как и в обычной команде cp, сначала указывается, что копировать (откуда), а потом – куда. Например, если у нас логин test, и нам надо скопировать файл D:\progs\myprog.cpp на кластер в каталог /home/test/my_progs, то команда будет выглядеть так: C:\Putty>pscp.exe D:\progs\myprog.cpp test@195.208.174.178:/home/test/my_progs/ после чего команда pscp запросит пароль и затем покажет, как идёт процесс копирования файла. Имейте ввиду, что Вы также можете воспользоваться перетаскиванием файла мышью на окно командной строки Windows, вместо полного набора пути и имени вручную (либо использовать сочетания клавиш Far Manager, если команда набирается в нём). 14 ПЕРВАЯ ПРОГРАММА В UNIX Перед тем, как приступать к программированию настоящих параллельных программ, ознакомимся сначала с созданием обычных однозадачных программ. Если Вы уже знакомы с программированием в Unix, этот раздел можно смело пропустить. Создание и редактирование файлов Пользовательские директории (вида /home/<имя_пользователя>), физически размещенные на головной машине, доступны по протоколу NFS на всех узлах. Это означает, что каталоги /share и /home на каждом узле смонтированы из одного и того же места, и файл создаваемый на одно узле, будет сразу же виден на всех остальных. То есть, Вам не требуется в программах учитывать сетевое взаимодействие, на каждом узле все файлы видны одинаково по тем же путям, но следует позаботиться о том, чтобы одновременный доступ к одному и тому же файлу с разных узлов не привел к мешанине в данных. Также для хранения данных доступна система хранения данных на 5 Тбайт, доступ к ней открывается по просьбе пользователей – используйте её, если необходима работа с массивами данных начиная от нескольких гигабайт (пользовательские каталоги предназначены более для программ, нежели на интенсивную работу с диском). Кроме того, на каждом узле доступен локальный каталог для временных файлов (/tmp). На узлах после окончания работы программы временные файлы необходимо удалять. Итак, предположим, что Ваш пользователь на кластере только что создан, никаких данных нет, хотим создать программу с нуля и освоить редактирование файлов. test@master:~> mkdir my_progs test@master:~> cd my_progs/ test@master:~/my_progs> touch hello_args.c test@master:~/my_progs> ls -l hello* -rw-r--r-- 1 test users 0 2007-06-19 16:21 hello_args.c test@master:~/my_progs> vim hello_args.c В этом примере мы создали подкаталог my_progs, перешли в него и создали пустой файл hello_args.c и убедились, что он действительно создан. Разумеется, текстовый редактор мог бы создать новый файл и сам, автоматически, это просто иллюстрация возможности создания пустых файлов. После этого мы открываем файл в редакторе Vim. Однако, если Вы ничего не знали о редакторе Vim ранее и запускаете его впервые в жизни, Вы будете удивлены, что он работает странным образом. Например, если Вы открыли уже существовавший файл, можно заметить, что курсор передвигается, но текст не вводится, иногда могут случаться странные эффекты, и более всего непонятно, как из всего этого выйти, чтоб запортившие файл изменения – не сохранились (ибо на стандартное сочетание клавиш Ctrl-C он тоже не хочет реагировать). 15 Так происходит потому, что редактор Vim, как потомок редактора Vi, существующего нынче практически во всех системах семейства Unix, сохраняет все его основные свойства и концепции, добавляя лишь новые возможности, вроде подсветки синтаксиса, многооконности, и т.д., которые по умолчанию, впрочем, отключены – vim ведёт себя как обычный vi. А сам же редактор Vi был создан почти 30 лет назад, во времена, когда на клавиатуре было меньше клавиш, и стояла задача как можно сильнее сократить число необходимых нажатий на клавиши, чтобы работа человека шла быстрее. Поэтому в нём, в отличие от обыкновенных редакторов, в которых нажатия вводят непосредственно текст, а команды редактирования применяются нажатиями сочетаний с кнопками Alt, Shift и Ctrl, существуют два режима: командный режим и режим ввода текста. Одни и те же кнопки по-разному работают в зависимости от режима: в режиме ввода они собственно вводят текст, в командном режиме – производят с ним операции. По умолчанию после запуска редактор находится в командном режиме, в режим ввода надо специально переключиться, нажав кнопку i, ввести текст, нажать Esc, чтобы вернуться в командный режим. На первый взгляд может показаться, что наличие двух режимов лишь замедляет работу, однако это не так – после некоторого периода обучения при должной сноровке работа станет быстрее, чем в традиционных редакторах. В командном режиме есть два типа нажатий – обычные клавиши, например, нажатие w переместит курсор на одно слово вперёд, нажатие x удалит символ под курсором, и т.д., и специальная кнопка : (двоеточие), при нажатии которой курсор перемещается в строку ввода команд, и там уже набираются слова по аналогии с командной строкой (вообще, в Unix регистр имеет значение, то есть, если говорится нажать D, это не то же самое, что нажать d – то есть, надо на самом деле нажать Shift-d; в данном случае это нажатие Shift и кнопки “;”, а для нажатия % (кнопка перемещения на парную скобку) надо нажать Shift-5). Так, для сохранения файла нужно в командном режиме ввести :write, для выход :quit, причем эти команды можно сокращать до:w и :q соответственно; если у вас есть несохраненные изменения, редактор не даст это сделать, потребуется выйти без сохранения командой :q! (восклицательный знак на конце форсирует применение команды и в некоторых других случаях). Преимущества командного режима начинают проявляться тогда, когда Вы начинаете пользоваться возможностями повторения команд. Так, практически все однобуквенные команды позволяют перед ними нажать цифры, введя число повторений. Если w переместит курсор на одно слово вперёд, а j перемещает курсор на одну строку вниз, то нажатие 10w переместит курсор на 10 слов вперед, а нажатие 45j – на 45 строк вниз. Нажатие G переместит курсор в конец файла, а 237G – на 237 строку файла. Кроме того, редактор запоминает последнюю операцию командного режима, и позволяет повторить её одним нажатием . (точки) – при этом вход в режим ввода, набор и выход обратно в командный режим рассматриваются как одна операция, поэтому можно ввести слово на 16 одной строке, потом подводить курсор к нужным местам, нажимать точку, и оно будет вставляться в эти места. Чтобы освоить редактор vim, существует интерактивный курс – команда vimtutor скопирует специальную заготовку во временный файл (так что можно редактировать его прямо там, не боясь испортить), и покажет учебник на родном русском языке (если это не так, обратитесь к администратору). После освоения учебника Вы будете знать достаточно, чтобы уверенно пользоваться vim для основных задач редактирования, кроме того, в нём показано, как подключить стандартный набор настроек vim, делающих редактирование более удобным – в частности, подсветку синтаксиса. Кроме того, существует множество других команд, позволяющих сделать жизнь более удобной, вроде включения поддержки мыши, позволяющей более удобно переключаться между окнами :split, но это выходит уже за рамки базового курса. На рисунке показано, как выглядит окно с запущенным редактором vim и исходным текстом программы scalar (рассмотренной далее) при включенной подсветке синтаксиса: Рис. 4 Внешний вид кода программы в редакторе VIM. Рис. 4. Окно vim с цветовой подсветкой синтаксиса языка Си. На текущий же момент введём очень простую программу, но немного отступим от традиции “Hello, World!” – пусть программа дополнительно покажет и те аргументы командной строки, с которыми её запустили: 17 test@master:~/my_progs> cat hello_args.c #include <stdio.h> int main(int argc, char *argv[]) { int i; printf("Hello, World! My arguments are:\n"); for (i = 0; i < argc; i++) printf("argv[%d]: %s", i, argv[i]); return 0; } 18 Компиляция и запуск программ Теперь необходимо скомпилировать программу. Наиболее распространенным в наше время на Unix-системах является компилятор GNU GCC, хотя существуют и другие, например, на нашем кластере установлен также коммерческий компилятор ICC фирмы Intel, отличающийся высокой степенью оптимизации генерируемого машинного кода для процессоров этой фирмы. Компиляция одного файла очень проста: test@master:~/my_progs> gcc hello_args.c test@master:~/my_progs> ls -l -rwxr-xr-x 1 test users 9108 2007-06-19 16:44 a.out -rw-r--r-- 1 test users 190 2007-06-19 16:21 hello_args.c Заметим, что мы не указали имя выходного файла, в таких случаях по историческим причинам компилятор создает исполняемый файл с именем a.out. Также заметим, что в Unix-системах исполняемые файлы не отличаются какимлибо специфическим расширением имени файла (системе, в отличие от DOS/Windows, они безразличны, они лишь для человека), вместо этого исполнимость файла определяется его правами – если на файле установлен бит +x (executable), то ядро попытается его исполнить. В данном случае, как показывает листинг, компилятор уже автоматически установил этот бит. Кроме того, в отличие от DOS/Windows, исполняемые файлы ищутся лишь в путях, заданных переменной PATH, но не в текущем каталоге, поэтому при попытке запуска просто по имени файла мы получим сообщение ошибки от оболочки, и надо будет указать нахождение файла в текущем каталоге: test@master:~/my_progs> a.out -bash: a.out: command not found test@master:~/my_progs> ./a.out Hello, World! My arguments are: argv[0]: ./a.outtest@master:~/my_progs> ./a.out test Hello, World! My arguments are: argv[0]: ./a.outargv[1]: testtest@master:~/my_progs> В этом примере видно, что наша программа работает, но имеет очевидную логическую ошибку – печать идёт без переводов новых строк, в результате строки склеивается и вывод выглядит плохо читаемым. Откроем редактор vim и увидим, что мы забыли вставить символ перевод строки в вызов функции печати. Сделаем это. Теперь перекомпилируем программу – компилятор автоматически перезапишет выходной файл: test@master:~/my_progs> gcc hello_args.c test@master:~/my_progs> ls -l -rwxr-xr-x 1 test users 9108 2007-06-19 17:46 a.out -rw-r--r-- 1 test users 192 2007-06-19 17:45 hello_args.c -rw-r--r-- 1 test users 190 2007-06-19 16:21 hello_args.c~ Кроме того, если Вы настроили редактор vim так, как описано в предыдущем разделе, предыдущая версия файла будет сохранена как резервная 19 копия с приписанным к именем символом тильды (~) – в DOS/Windows в таком случае было бы приписано расширение .bak. Хранение предпоследних версий может быть удобно, если Вы редко возвращаетесь к программе и не помните, какой была последняя правка. В этом случае на помощь придёт утилита diff: test@master:~/my_progs> diff hello_args.c~ hello_args.c 9c9 < printf("argv[%d]: %s", i, argv[i]); --> printf("argv[%d]: %s\n", i, argv[i]); test@master:~/my_progs> diff -u hello_args.c~ hello_args.c --- hello_args.c~ 2007-06-19 16:21:47.000000000 +0700 +++ hello_args.c 2007-06-19 17:45:45.000000000 +0700 @@ -6,7 +6,7 @@ printf("Hello, World! My arguments are:\n"); for (i = 0; i < argc; i++) printf("argv[%d]: %s", i, argv[i]); printf("argv[%d]: %s\n", i, argv[i]); + return 0; } Вывод утилиты diff идет в машиночитаемом формате – возможно автоматическое применение её результатов, называемых патчами (заплатками), утилитой patch (чтобы из старого файла и присланного патча получить новую версию, как говорят, пропатчить файл). Поэтому по умолчанию вывод не очень информативен, если же указать ключ -u (unified diff), он станет более понятен человеку – удаленные строки показаны с минусом, добавленные – с плюсом, кроме того, показано некоторое количество неизмененных строк контекста. В повседневном применении, однако, имя файла a.out мало кого устроит, поэтому скомпилируем программу с указанием имени выходного файла, скопируем дополнительные файлы в каталог и посмотрим, каким именно образом оболочка раскрывает шаблоны имени файлов в аргументы запускаемой программе: test@master:~/my_progs> test@master:~/my_progs> total 244 -rwxr-xr-x 1 test users -rwxr-xr-x 1 test users -rw-r--r-- 1 test users -rw-r--r-- 1 test users -rwxr-xr-x 1 test users -rw-r--r-- 1 test users -rw-r--r-- 1 test users -rw-r--r-- 1 test users -rw------- 1 test 600 -rw------- 1 test 600 -rw------- 1 test 600 -rw------- 1 test 600 -rw------- 1 test 600 -rw------- 1 test 600 -rw------- 1 test 600 -rw------- 1 test 600 gcc -o hello_args hello_args.c ls -l 9108 9108 192 190 13753 2257 2260 5464 2808 2527 1956 2517 1710 2515 1651 13461 2007-06-19 2007-06-25 2007-06-19 2007-06-19 2007-03-20 2007-03-20 2007-03-20 2007-03-20 2007-03-20 2007-03-20 2007-03-20 2007-03-20 2007-03-20 2007-03-20 2007-03-20 2007-03-20 17:46 15:56 17:45 15:23 09:43 09:43 09:43 09:43 10:38 10:39 10:40 11:17 11:20 11:24 11:25 11:26 a.out hello_args hello_args.c hello_args.c~ scalar scalar.c scalar.c~ scalar.o scalar.out-212 scalar.out-213 scalar.out-214 scalar.out-215 scalar.out-216 scalar.out-217 scalar.out-218 scalar.out-219 20 -rw------- 1 test 600 13955 2007-03-20 11:55 scalar.out-220 -rw------- 1 test 600 146 2007-03-26 16:10 scalar.out-274 -rw------- 1 test 600 144 2007-03-26 16:12 scalar.out-276 -rw------- 1 test 600 146 2007-03-26 16:18 scalar.out-279 -rw------- 1 test 600 904 2007-03-26 16:28 scalar.out-287 -rw-r--r-- 1 test 600 441 2007-03-20 10:39 scalar.rep-212 -rw-r--r-- 1 test 600 425 2007-03-20 10:39 scalar.rep-213 -rw-r--r-- 1 test 600 388 2007-03-20 10:40 scalar.rep-214 -rw-r--r-- 1 test 600 421 2007-03-20 11:18 scalar.rep-215 -rw-r--r-- 1 test 600 457 2007-03-20 11:20 scalar.rep-216 -rw-r--r-- 1 test 600 421 2007-03-20 11:24 scalar.rep-217 -rw-r--r-- 1 test 600 448 2007-03-20 11:25 scalar.rep-218 -rw-r--r-- 1 test 600 1210 2007-03-20 11:26 scalar.rep-219 -rw-r--r-- 1 test 600 1185 2007-03-20 11:55 scalar.rep-220 -rw-r--r-- 1 test 600 270 2007-03-26 16:10 scalar.rep-274 -rw-r--r-- 1 test 600 269 2007-03-26 16:12 scalar.rep-276 -rw-r--r-- 1 test 600 270 2007-03-26 16:19 scalar.rep-279 -rw-r--r-- 1 test 600 311 2007-03-26 16:28 scalar.rep-287 test@master:~/my_progs> ./hello_args arg1 arg2 Hello, World! My arguments are: argv[0]: ./hello_args argv[1]: arg1 argv[2]: arg2 test@master:~/my_progs> ./hello_args scalar.out-21* Hello, World! My arguments are: argv[0]: ./hello_args argv[1]: scalar.out-212 argv[2]: scalar.out-213 argv[3]: scalar.out-214 argv[4]: scalar.out-215 argv[5]: scalar.out-216 argv[6]: scalar.out-217 argv[7]: scalar.out-218 argv[8]: scalar.out-219 test@master:~/my_progs> ./hello_args scalar.*-2[78]? Hello, World! My arguments are: argv[0]: ./hello_args argv[1]: scalar.out-274 argv[2]: scalar.out-276 argv[3]: scalar.out-279 argv[4]: scalar.out-287 argv[5]: scalar.rep-274 argv[6]: scalar.rep-276 argv[7]: scalar.rep-279 argv[8]: scalar.rep-287 test@master:~/my_progs> ./hello_args s1 "Строка с пробелами" arg3 one\ more arg5 Hello, World! My arguments are: argv[0]: ./hello_args argv[1]: s1 argv[2]: Строка с пробелами argv[3]: arg3 argv[4]: one more argv[5]: arg5 Здесь наглядно видно, что специальные символы в командной строке обрабатывает оболочка, и Вам, в отличие от DOS/Windows, не требуется обрабатывать их вручную, или заботиться о разделителях – а просто использовать передаваемые для обработки аргументы. 21 Программирование с использованием MPI Стандарт MPI, Message Passing Interface, описывает более 120 библиотечных функций, которые могут быть использованы в параллельных программах на кластерах. Разумеется, это очень обширная тема, и лучше всего освещена в специализированных руководствах и книгах (часть из них есть даже на нашем сайте, http://cluster.tpu.ru), как, собственно, и само параллельное программирование – это целый отдельный мир. Однако в минимальном применении можно обойтись достаточно простым набором из менее чем десятка функций. Поэтому мы ограничимся лишь очень кратким введением в эту тему, и дадим в качестве примера одну работающую программу, дающую представление о практическом использовании MPI и нескольких основных функциях, необходимых для работы. Модель использования MPI предполагает, что на кластере для решения задачи программа запускается в нескольких копиях (их число задается при запуске). Каждая имеет для простоты только один поток вычисления, и даже если другой процесс находится на том же вычислительном узле (а обычно каждый узел имеет несколько процессоров, то есть способен выполнять несколько процессов), всё равно общение с другими процессами происходит только через посылку сообщений MPI – библиотека сама выберет наиболее оптимальный способ доставки. Еще раз подчеркнем, что все копии программы (процессы), запускаемые на кластере – идентичны, это один и тот же код вашей программы, он будет одинаков везде. MPI, однако, присваивает каждому процессу номер (начиная с нуля), называемый также рангом (rank) процесса, и по этому рангу Вы можете отличить процессы внутри себя друг от друга, организовав там, где надо, соответствующие ветвления, а где не надо, оставив абсолютно одинаковый код для вычислений. Кроме того, MPI позволяет организовать деление процессов на группы, упоминаемые в функциях сокращением comm (коммуникаторы), однако в нашем примере мы обойдемся всегда существующим по умолчанию коммуникатором MPI_COMM_WORLD, включающим в себя все запущенные копии программы (на самом деле, ранг – это номер процесса для коммуникатора, если Вы будете создавать свои собственные коммуникаторы, следует иметь это ввиду). Имена всех констант и функций MPI начинаются с префикса MPI_, и для их использования следует подключить заголовочный файл mpi.h. Компиляция программ MPI требует подключения соответствующих библиотек, однако для пользователя существуют команды-обертки, скрывающие этот процесс (подробнее рассмотрено в следующем разделе). Главное, что следует помнить – все вызовы функций MPI могут происходить только между вызовами MPI_Init() и MPI_Finalize(), причем к моменту вызова последней все операции с пересылкой данных должны быть завершены. Кроме того, хотя операции пересылки сообщений и представляют 22 собой способ синхронизации между процессами, отправка данных (возврат из функции посылки) не гарантирует того, что процесс-получатель принял эти данные или хотя бы даже начал операцию приема (если это возможно по смыслу конкретной операции MPI). Всё, что имеет значение – это порядок операций посылки и приёма, но поскольку жесткий порядок не всегда удобен, MPI предоставляет вводить уникальные идентификаторы сообщений (msg tag) для отправки и приема (числа в диапазоне от 0 до 32767). Если процесс ожидает несколько сообщений на прием с разными идентификаторами, процесс может выбрать, с каким из них принять сообщение. В простых программах это часто не требуется, тогда можно использовать один идентификатор на все сообщения, и они будут приниматься просто по порядку. Отправка сообщения другому процессу: MPI_Send() int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int msgtag, MPI_Comm comm) buf – адрес начала буфера посылки сообщения (например, массива) count – число передаваемых элементов в сообщении datatype – тип передаваемых элементов массива dest – номер процесса-получателя msgtag – идентификатор сообщения comm – идентификатор группы Блокирующая посылка сообщения с идентификатором msgtag, состоящего из count элементов типа datatype, процессу с номером dest. Все элементы сообщения расположены подряд в буфере buf. Значение count может быть нулем (или единицей, если вы хотите передать одну переменную, а не массив). Тип передаваемых элементов datatype должен указываться с помощью предопределенных констант типа, например MPI_DOUBLE для типа double или MPI_INT для типа int. Разрешается передавать сообщение самому себе. Блокировка гарантирует корректность повторного использования всех параметров после возврата из подпрограммы - то есть, программа будет остановлена (заблокирована) до тех пор, пока функция не завершится. Выбор способа осуществления этой гарантии: копирование в промежуточный буфер или непосредственная передача процессу dest, остается за MPI. Следует специально отметить, что возврат из подпрограммы MPI_Send() не означает ни того, что сообщение уже передано процессу dest, ни того, что сообщение покинуло процессорный элемент, на котором выполняется процесс, выполнивший MPI_Send(). 23 Прием сообщения от другого процесса: MPI_Recv() int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int msgtag, MPI_Comm comm, MPI_Status *status) buf – адрес начала буфера, в который будет записано принятое сообщение count – максимальное число элементов в принимаемом сообщении datatype – тип элементов принимаемого сообщения source – номер процесса-отправителя msgtag – идентификатор принимаемого сообщения comm – идентификатор группы status – сюда будут записаны параметры принятого сообщения Прием сообщения с идентификатором msgtag от процесса source с блокировкой (если нет входящих сообщений, программа будет остановлена до тех пор, пока какой-либо процесс не пришлет сообщение). Число элементов в принимаемом сообщении не должно превосходить значения count. Если число принятых элементов меньше значения count, то гарантируется, что в буфере buf изменятся только элементы, соответствующие элементам принятого сообщения. Если нужно узнать точное число элементов в сообщении, то можно воспользоваться подпрограммой MPI_Probe(). Блокировка гарантирует, что после возврата из подпрограммы все элементы сообщения приняты и расположены в буфере buf – если нет входящих сообщений, программа будет остановлена до тех пор, пока не придет сообщение, удовлетворяющее заданным идентификаторам. В качестве номера процесса-отправителя можно указать предопределенную константу MPI_ANY_SOURCE – признак того, что подходит сообщение от любого процесса. В качестве идентификатора принимаемого сообщения можно указать константу MPI_ANY_TAG – признак того, что подходит сообщение с любым идентификатором. Если процесс посылает два сообщения другому процессу и оба эти сообщения соответствуют одному и тому же вызову MPI_Recv(), то первым будет принято то сообщение, которое было отправлено раньше. В параметр status будут записаны атрибуты сообщения – номера процессов отправки и получения, и код ошибки, который в нетривиальных программах стоит проверять. Первая программа MPI: скалярное произведение векторов Наша первая программа будет вычислять скалярное произведение двух длинных векторов. Как известно из математики, скалярное произведение двух векторов есть сумма произведений пар значений каждого вектора. Но что, если количество данных в векторах очень большое, например, несколько миллионов значений? Очевидно, что вычисление произведения каждой пары – не зависит от 24 других пар, и это можно делать параллельно на разных узлах, таким образом ускорив работу. Более того, каждый узел может просуммировать свою часть произведений, таким образом, для получения финального результата нужно будет просто получить сумму результатов каждого процесса, а не сумму всех тех миллионов пар, что гораздо дольше. Итак, текст программы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 test@master:~/my_progs> cat scalar.c #include "mpi.h" #include <stdio.h> #include <stdlib.h> #include <math.h> #include <signal.h> #define MYTAG 1 int myid, j; char processor_name[MPI_MAX_PROCESSOR_NAME]; double startwtime = 0.0, endwtime; int main(int argc, char *argv[]) { int total, n, numprocs, i, dest; double *a, *b, sum, result; int namelen; MPI_Status status; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &numprocs); MPI_Comm_rank(MPI_COMM_WORLD, &myid); MPI_Get_processor_name(processor_name, &namelen); if (myid == 0) { if (argc != 2) { printf("Usage: %s <length of vector>\n", argv[0]); exit(1); } total = atoi(argv[1]); } printf("Process %d of %d is on %s\n", myid, numprocs, processor_name); /*if (myid == 0)*/ startwtime = MPI_Wtime(); if (myid == 0) n = total / numprocs + 1; MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); a = malloc(n*sizeof(double)); b = malloc(n*sizeof(double)); if ((a == NULL) || (b == NULL)) { fprintf(stderr,"Error allocating vectors (not enough memory?)\n"); exit(1); } if (myid == 0) { for (dest=1; dest < numprocs; dest++) { for (i=0; i < n; i++) { a[i] = rand(); 25 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 b[i] = rand(); } MPI_Send(a, n, MPI_DOUBLE, dest, MYTAG, MPI_COMM_WORLD); MPI_Send(b, n, MPI_DOUBLE, dest, MYTAG, MPI_COMM_WORLD); } n = total - n*(numprocs-1); for (i=0; i < n; i++) { a[i] = rand(); b[i] = rand(); } } else { MPI_Recv(a, n, MPI_DOUBLE, 0, MYTAG, MPI_COMM_WORLD, &status); MPI_Recv(b, n, MPI_DOUBLE, 0, MYTAG, MPI_COMM_WORLD, &status); } printf("Process %d on node %s starting calc at %f sec\n", myid, processor_name, MPI_Wtime()-startwtime); sum = 0.0; for (i=0; i<n; i++) sum += a[i]*b[i]; printf("Process %d on node %s ending calc at %f sec\n", myid, processor_name, MPI_Wtime()-startwtime); MPI_Reduce(&sum, &result, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); if (myid == 0) { endwtime = MPI_Wtime(); printf("Answer is %.16f\n", result); printf("wall clock time = %f\n", endwtime-startwtime); fflush(stdout); } MPI_Finalize(); return 0; } Разберем подробнее, что делает эта программа. В строках 1-18 мы подключаем нужные заголовочные файлы, объявляем константу MYTAG, равную единице, объявляем переменные нужных типов. В строке 20 мы инициализируем подсистему MPI. В строках 21-23 мы получаем от подсистемы MPI параметры нашего запуска: в переменной numprocs мы получим общее количество запущенных процессов (поскольку получаем число процессов в глобальной группе MPI_COMM_WORLD, которая включает в себя все процессы); в переменной myid мы получаем ранг нашего конкретного процесса, и в переменной processor_name – имя узла (на одном узле может быть запущено несколько процессов, по числу ядер). В строках 25-31 мы проверяем ранг нашего процесса – поскольку мы выбрали, что процесс с рангом 0 будет «управляющим», этот кусок кода исполнится только на нём. Сама же эта часть получает единственный аргумент командной строки – длину векторов, печатая сообщение об ошибке, если программа запущена без аргументов. Длина вектора из строкового представления преобразуется в число и записывается в переменную total. В строках 32-33 каждый процесс напечатает на стандартный вывод сведения о себе – ранг и имя узла. В строке 37 каждый процесс (если снять 26 комментарий в строке 36, то будет лишь нулевой) запоминает текущее астрономическое время (wall clock time) от момента начала старта подсистемы MPI. Эта процедура необязательна, нам просто интересно, как долго будут считать процессы. В строке 40 мы вычисляем n, длину отрезка вектора, которую будет использовать каждый процесс, причем делаем это так, чтобы оно было немного больше нужного – поскольку остаток чуть меньшего размера мы будем считать в управляющем процессе (не простаивать же ему, но и учесть, что у него немного больше работы на управление). Далее в строке 41 мы демонстрируем использование функции MPI_Bcast() – широковещательная рассылка, все процессы получают в переменную n то значение, которое вычислил нулевой процесс (в аргументе, задающем имя отправителя, мы ставим 0). На самом деле очевидно, что в данном случае вместо строк 39-41 это число просто мог вычислить каждый процесс, формула очень проста – но целью стоит продемонстрировать как можно больше функций, каких только можно включить даже в простую программу. В реальной жизни вычисление может оказаться длительным, и его делает один процесс, пока остальные могут заняться чем-то еще, а он потом передаст им результат для следующего этапа. Далее в строках 43-49 мы выделяем у операционной системы память для двух векторов, указанного числа элементов типа double (вещественных чисел двойной точности) в каждом. Заметим, что эта память выделяется в каждом из процессов, и поскольку n было подобрано таким, чтобы быть чуть больше необходимого, процессу с номером 0 этого объема тоже хватит, нет нужды писать дополнительную обработку номера ранга. Особое внимание следует обратить на строки 36-49 – это проверка ошибки от операционной системы. Аппаратные возможности кластера велики, но не безграничны, и пользователь мог задать настолько большое значение длины вектора, что оно не влезет в память одного процесса даже с учетом того, что один процесс выделяет и обрабатывает не весь вектор, а лишь его часть. В строках 51-68 демонстрируется основное применение MPI – пересылка данных между процессами. Строки 51-64 исполняются на управляющем процессе, а строки 65-68 – на каждом из остальных. Поскольку это демонстрационная программа, данные здесь не являются реальными – в строках 52-59 мы заполняем каждый вектор случайными числами и отсылаем очередному процессу. Каждый процесс в строках 65-68 принимает свою часть вектора и сразу после этого начинает работать дальше – процессы ожидают возврата из MPI_Recv() до реального прихода сообщения. В строках же 60-64 управляющий процесс вычисляет длину остатка вектор для самого себя. В строках 73-75 происходит собственно реальная работа – каждый процесс вычисляет скалярное произведение своих частей векторов. Сразу до (70-71) и после (77-78) этого происходит печать времени начала и окончания счета, дабы можно было оценить, как долго каждый процесс ожидал завершения пересылки данных, чтобы начать считать, и когда он реально окончил счет, далее простаивая в ожидании завершения остальных процессов. 27 После вычисления каждым процессом имеется n чисел, которые для получения окончательного результата надо просто просуммировать тоже. Естественным решением будет организовать в управляющем процессе цикл с приемом и суммированием этих значений, а каждый процесс должен будет послать ему этот результат, уже знакомыми функциями MPI_Send() и MPI_Recv(). Но для ряда операций существуют более эффективные реализации, которые следует продемонстрировать. Операция Reduce – это операция получения одного значения из их набора (массива), MPI предоставляет возможность всем процессам для ряда коммутативных и ассоциативных операций сделать это более элегантно. В данном случае мы указываем предопределенную операцию MPI_SUM, для одного значение типа MPI_DOUBLE в переменной sum каждого процесса, результат будет записан в переменную result в процессе 0. Конец программы достаточно очевиден – управляющий процесс печатает ответ (сбрасывая в строке 85 возможную буферизацию печати), вместе с суммарным временем, которое было потрачено на всё, после чего в строке 88 выполняется обязательная финализация работы подсистемы MPI. 28 Запуск параллельных приложений на кластере Компиляция программ MPI После того как программа отредактирована, её необходимо скомпилировать. Для компиляции MPI-программ рекомендуется пользоваться командами mpicc/mpiCC (для программ на С и С++), и mpif77/mpif90 (для программ на Фортране 77/90). Эти команды автоматически подключают заголовочные файлы и библиотеки MPI. Для программ на языке С++ нужно использовать расширение имени файла .C или .cpp, для программ на языке Фортран 90 - .f90. Рекомендуется использовать опции компиляторов для оптимизации программ. Для компиляторов Intel и GNU gcc, установленных на нашем кластере, приемлемый уровень оптимизации дает опция -O3, для справки о других опциях оптимизации рекомендуем обращаться к руководствам по компиляторам. Если необходимо только скомпилировать один модуль, и не выполнять сборку исполняемого файла, используется опция "-с", например: test@master:~> mpiCC -c program2.C При этом будет создан объектный модуль "program2.o". Объектный модуль не является исполняемым, он используется как один из блоков при компоновке исполняемого файла. Если необходимо создать исполняемый файл, то имеет смысл воспользоваться опцией -o <имя>, чтобы задать его имя (по умолчанию его имя будет a.out). Например: test@master:~> mpif90 -O3 program.f -o program.e или, для рассмотренного в предыдущем разделе примера: test@master:~/my_progs> mpif90 -o scalar scalar.c При этом будет создан исполняемый файл program.e (или scalar для примера предыдущего раздела), который можно запускать на исполнение командой mpirun (но только на том кластере, где он был собран). Для сборки многомодульных приложений целесообразно пользоваться утилитой GNU make. Для запуска MPI-приложений используется система управления заданиями Cleo (созданная в НИВЦ МГУ), она предназначена для управления прохождением задач на многопроцессорных вычислительных установках (в том числе кластерных). Она позволяет автоматически распределять вычислительные ресурсы между задачами, управлять порядком их запуска, временем работы, получать информацию о состоянии очередей. 29 Постановка задачи в очередь Задача ставится в очередь командой mpirun, которая напоминает обычную команду запуска MPI-приложений: mpirun -np N [-q Q] [-maxtime T] [-p P] <программа с аргументами> где N - число процессоров, должно быть не более разрешенного числа процессоров для одной задачи. Q - это очередь, куда будет поставлена задача. T это максимальное время работы задачи в минутах. P - приоритет задачи в очереди. Реально задача начнет выполняться, как только она будет на верхушке очереди и будут свободны N процессоров. Система автоматически подбирает свободные узлы (процессоры) для запуска задачи. Гарантируется, что на каждом узле будет запущено не более прикладных процессов, чем реально доступно ядер процессоров (4). Если задача поставлена в очередь, система выдает подтверждение и присваивает задаче уникальный номер (ID). Например, чтобы запустить скомпилированный пример скалярного произведения двух векторов на всех 24 узлах (а на каждом два двухъядерных процессора, что позволяет запустит до 4 процессов на узел) с указанием длины вектора в почти миллиард элементов, можно так: test@master:~/my_progs> mpirun -np 96 ./scalar 999999999 Using queue main Successfully added to queue main (ID=205) После постановки задачи в очередь пользователь может отключиться от терминала, а затем в любой момент подключиться к системе и просматривать результаты прежде запущенных задач. Для запуска однопроцессных приложений используйте ключ <–as single>: test@master:~> mpirun –np 1 -as single myprog.e my_arguments Запуск однопроцессных приложений отличается тем, что в данном случае вы запускаете обычную программу Unix, без всякого параллельного программирования. По умолчанию система ожидает программу, скомпилированную с MPI, и если так запустить не-MPI приложение, запуск окончится ошибкой. В некоторых случаях, однако, необходимо запускать другие приложение: если программирование MPI для вашей цели слишком сложно, или задача очень плохо параллелится, или есть разные последовательные задачи (например, с очень разными параметрами расчета). Тогда следует создать обычную однопроцессную программу Unix, указать этот ключ, и система просто запустит этот программу вместо головного узла на случайном процессоре из свободных в текущий момент, рассматривая кластер не как связанную систему, а как набор обыкновенных компьютеров. Помните, что головной узел не предназначен для счета, на нем работают другие пользователи, не следует запускать программы прямо на нём! 30 Пояснение к системе приоритетов Задачи с большим приоритетом будут идти на счет раньше задач с меньшим приоритетом. Приоритет обычных задач по умолчанию равен 10. Если для Вас не важно, чтобы задача пошла на счет как можно быстрее и Вы считаете целесообразным уступить очередь другим пользователям, Вы можете уменьшить значение приоритета. Например: test@master:~> mpirun -np 32 -p 8 cg.A.32 Эта задача со значением приоритета 8 пойдет на счет не раньше, чем пойдут на счет все задачи с приоритетами 9 и 10. Если для Вас, наоборот, требуется посчитать задачу как можно быстрее, то Вы можете обратиться к нам с просьбой увеличить значение приоритета Вашей задачи. Самостоятельно повысить приоритет своей задачи более чем до 10 Вы не можете. Можно изменить приоритет задачи, стоящей в очереди, с помощью команды cleo-priority. Параметр -maxtime устанавливает предельное время работы задачи в минутах (по умолчанию трое суток). Просим Вас обязательно указывать этот параметр для всех задач. Это поможет нам оптимально планировать работу кластера и поможет другим пользователям ориентироваться при постановке задач в очередь. По умолчанию будет устанавливаться очень небольшое предельное время. Учтите, что при истечении предельного времени задача будет сниматься со счета (принудительно убиваться). Например, пусть на кластере свободно 8 процессоров, а в очереди уже стоит задача на 32 процессора (но все 32 процессора в ближайшие 7 часов не освободятся). Тогда если поставить в очередь короткую 4-процессорную задачу, указав максимальное время: test@master:~> mpirun -np 4 -maxtime 10 program то эта задача сразу пойдет на счет. Таким образом, вычислительные ресурсы будут распределяться более оптимально. Просим Вас разумно пользоваться возможностями системы и проявлять уважение к другим пользователям. Просмотр результатов По окончании работы задачи пользователю выдается сообщение на терминал (в дальнейшем может быть организовано также оповещение по электронной почте). Выдача программы помещается в файл в рабочей директории с именем <задача>.out-<номер>. Кроме того, создается файл отчета <задача>.rep-<номер>, где указываются следующие данные: командная строка при запуске задачи, число процессоров, код возврата, имя выходного файла, рабочая директория, астрономическое время работы программы, имена 31 узлов, на которых была запущена программа. Имена файла отчета и выходного файла можно настраивать в конфигурационном файле. Для рассматриваемого примера со скалярным произведением векторов выходной файл может выглядеть так: test@master:~/my_progs> cat scalar.out-205 Process 65 of 96 is on node-2 Process 32 of 96 is on node-18 Process 64 of 96 is on node-2 Process 35 of 96 is on node-18 Process 38 of 96 is on node-19 Process 42 of 96 is on node-1 Process 69 of 96 is on node-3 Process 51 of 96 is on node-21 Process 54 of 96 is on node-22 Process 31 of 96 is on node-17 Process 45 of 96 is on node-20 Process 49 of 96 is on node-21 Process 68 of 96 is on node-3 Process 80 of 96 is on node-6 Process 71 of 96 is on node-3 Process 36 of 96 is on node-19 Process 88 of 96 is on node-8 Process 77 of 96 is on node-5 Process 83 of 96 is on node-6 Process 50 of 96 is on node-21 Process 21 of 96 is on node-15 Process 61 of 96 is on node-24 Process 66 of 96 is on node-2 Process 93 of 96 is on node-9 Process 94 of 96 is on node-9 Process 73 of 96 is on node-4 Process 63 of 96 is on node-24 Process 52 of 96 is on node-22 Process 58 of 96 is on node-23 Process 9 of 96 is on node-12 Process 1 of 96 is on node-10 Process 8 of 96 is on node-12 Process 39 of 96 is on node-19 Process 11 of 96 is on node-12 Process 34 of 96 is on node-18 Process 3 of 96 is on node-10 Process 57 of 96 is on node-23 Process 10 of 96 is on node-12 Process 86 of 96 is on node-7 Process 13 of 96 is on node-13 Process 70 of 96 is on node-3 Process 19 of 96 is on node-14 Process 59 of 96 is on node-23 Process 26 of 96 is on node-16 Process 2 of 96 is on node-10 Process 27 of 96 is on node-16 Process 47 of 96 is on node-20 Process 89 of 96 is on node-8 Process 55 of 96 is on node-22 Process 53 of 96 is on node-22 Process 41 of 96 is on node-1 Process 29 of 96 is on node-17 Process 81 of 96 is on node-6 Process 48 of 96 is on node-21 Process 84 of 96 is on node-7 32 Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process 14 of 96 is on node-13 44 of 96 is on node-20 92 of 96 is on node-9 76 of 96 is on node-5 4 of 96 is on node-11 56 of 96 is on node-23 6 of 96 is on node-11 30 of 96 is on node-17 87 of 96 is on node-7 12 of 96 is on node-13 40 of 96 is on node-1 46 of 96 is on node-20 75 of 96 is on node-4 85 of 96 is on node-7 72 of 96 is on node-4 82 of 96 is on node-6 25 of 96 is on node-16 15 of 96 is on node-13 22 of 96 is on node-15 67 of 96 is on node-2 95 of 96 is on node-9 33 of 96 is on node-18 62 of 96 is on node-24 91 of 96 is on node-8 5 of 96 is on node-11 7 of 96 is on node-11 28 of 96 is on node-17 43 of 96 is on node-1 74 of 96 is on node-4 78 of 96 is on node-5 20 of 96 is on node-15 24 of 96 is on node-16 16 of 96 is on node-14 37 of 96 is on node-19 60 of 96 is on node-24 90 of 96 is on node-8 18 of 96 is on node-14 23 of 96 is on node-15 79 of 96 is on node-5 17 of 96 is on node-14 0 of 96 is on node-10 1 on node node-10 starting calc at 1.986553 sec 2 on node node-10 starting calc at 4.099962 sec 3 on node node-10 starting calc at 6.494678 sec 1 on node node-10 ending calc at 6.811479 sec 4 on node node-11 starting calc at 8.380775 sec 5 on node node-11 starting calc at 10.281216 sec 2 on node node-10 ending calc at 10.619027 sec 3 on node node-10 ending calc at 12.247417 sec 4 on node node-11 ending calc at 12.355679 sec 6 on node node-11 starting calc at 12.404281 sec 5 on node node-11 ending calc at 14.375157 sec 7 on node node-11 starting calc at 14.478773 sec 6 on node node-11 ending calc at 16.317051 sec 8 on node node-12 starting calc at 16.344161 sec 9 on node node-12 starting calc at 18.209255 sec 7 on node node-11 ending calc at 18.274197 sec 8 on node node-12 ending calc at 20.203265 sec 10 on node node-12 starting calc at 20.371354 sec 11 on node node-12 starting calc at 22.236358 sec 12 on node node-13 starting calc at 24.101140 sec 9 on node node-12 ending calc at 24.285586 sec 13 on node node-13 starting calc at 25.966044 sec 33 Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process 11 10 12 14 13 15 16 14 15 17 18 16 19 20 17 21 18 19 20 22 21 23 24 22 23 25 26 24 27 25 28 29 26 28 27 30 31 32 29 33 31 30 34 32 33 35 36 34 37 35 36 38 39 40 37 41 39 38 40 42 41 43 44 on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node-12 ending calc at 26.195200 sec node-12 ending calc at 26.278865 sec node-13 ending calc at 28.122228 sec node-13 starting calc at 28.131973 sec node-13 ending calc at 29.971013 sec node-13 starting calc at 30.141702 sec node-14 starting calc at 32.007457 sec node-13 ending calc at 32.055611 sec node-13 ending calc at 33.947192 sec node-14 starting calc at 34.130383 sec node-14 starting calc at 35.996762 sec node-14 ending calc at 37.986985 sec node-14 starting calc at 38.175061 sec node-15 starting calc at 40.043447 sec node-14 ending calc at 40.090141 sec node-15 starting calc at 41.911764 sec node-14 ending calc at 42.028253 sec node-14 ending calc at 43.877068 sec node-15 ending calc at 44.064366 sec node-15 starting calc at 44.074125 sec node-15 ending calc at 45.922043 sec node-15 starting calc at 46.088846 sec node-16 starting calc at 47.953646 sec node-15 ending calc at 47.981564 sec node-15 ending calc at 49.885462 sec node-16 starting calc at 50.082035 sec node-16 starting calc at 51.947098 sec node-16 ending calc at 53.823834 sec node-16 starting calc at 54.126657 sec node-16 ending calc at 55.965117 sec node-17 starting calc at 55.991784 sec node-17 starting calc at 57.857756 sec node-16 ending calc at 58.020547 sec node-17 ending calc at 59.859178 sec node-16 ending calc at 59.866633 sec node-17 starting calc at 60.022600 sec node-17 starting calc at 61.887429 sec node-18 starting calc at 63.752350 sec node-17 ending calc at 63.877885 sec node-18 starting calc at 65.617448 sec node-17 ending calc at 65.855990 sec node-17 ending calc at 65.865320 sec node-18 starting calc at 67.788874 sec node-18 ending calc at 67.810459 sec node-18 ending calc at 69.625431 sec node-18 starting calc at 69.797325 sec node-19 starting calc at 71.663182 sec node-18 ending calc at 71.708298 sec node-19 starting calc at 73.527963 sec node-18 ending calc at 73.611953 sec node-19 ending calc at 75.514085 sec node-19 starting calc at 75.686998 sec node-19 starting calc at 77.555478 sec node-1 starting calc at 79.424400 sec node-19 ending calc at 79.588560 sec node-1 starting calc at 81.293021 sec node-19 ending calc at 81.528820 sec node-19 ending calc at 81.567667 sec node-1 ending calc at 83.436041 sec node-1 starting calc at 83.453322 sec node-1 ending calc at 85.318031 sec node-1 starting calc at 85.476362 sec node-20 starting calc at 87.341581 sec 34 Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process Process 42 45 43 44 46 45 47 48 46 49 47 48 50 51 52 49 53 51 50 52 54 53 55 56 54 57 55 56 58 59 60 57 61 59 58 60 62 63 64 61 63 65 62 66 64 67 68 65 66 69 67 70 68 71 69 72 73 70 72 71 74 75 76 on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on on node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node node-1 ending calc at 87.357478 sec node-20 starting calc at 89.206999 sec node-1 ending calc at 89.275385 sec node-20 ending calc at 91.345189 sec node-20 starting calc at 91.364721 sec node-20 ending calc at 93.186706 sec node-20 starting calc at 93.360995 sec node-21 starting calc at 95.226052 sec node-20 ending calc at 95.275176 sec node-21 starting calc at 97.091059 sec node-20 ending calc at 97.192541 sec node-21 ending calc at 99.090204 sec node-21 starting calc at 99.260569 sec node-21 starting calc at 101.124842 sec node-22 starting calc at 102.989866 sec node-21 ending calc at 103.094547 sec node-22 starting calc at 104.855610 sec node-21 ending calc at 105.084581 sec node-21 ending calc at 105.116581 sec node-22 ending calc at 107.010051 sec node-22 starting calc at 107.021547 sec node-22 ending calc at 108.873117 sec node-22 starting calc at 109.039311 sec node-23 starting calc at 110.905427 sec node-22 ending calc at 110.929646 sec node-23 starting calc at 112.770830 sec node-22 ending calc at 112.870543 sec node-23 ending calc at 114.774826 sec node-23 starting calc at 114.942875 sec node-23 starting calc at 116.810026 sec node-24 starting calc at 118.678662 sec node-23 ending calc at 118.791487 sec node-24 starting calc at 120.547573 sec node-23 ending calc at 120.776525 sec node-23 ending calc at 120.796954 sec node-24 ending calc at 122.539467 sec node-24 starting calc at 122.711106 sec node-24 starting calc at 124.580326 sec node-2 starting calc at 126.447008 sec node-24 ending calc at 126.618692 sec node-24 ending calc at 128.540667 sec node-2 starting calc at 128.568682 sec node-24 ending calc at 128.609746 sec node-2 starting calc at 130.433556 sec node-2 ending calc at 132.440000 sec node-2 starting calc at 132.614312 sec node-3 starting calc at 134.478339 sec node-2 ending calc at 134.523263 sec node-2 ending calc at 136.559419 sec node-3 starting calc at 136.604531 sec node-2 ending calc at 138.420101 sec node-3 starting calc at 138.470344 sec node-3 ending calc at 140.376324 sec node-3 starting calc at 140.649617 sec node-3 ending calc at 142.497832 sec node-4 starting calc at 142.514426 sec node-4 starting calc at 144.379514 sec node-3 ending calc at 144.559493 sec node-4 ending calc at 146.372721 sec node-3 ending calc at 146.425581 sec node-4 starting calc at 146.542097 sec node-4 starting calc at 148.408210 sec node-5 starting calc at 150.273361 sec 35 Process 73 on node node-4 ending calc at 150.438133 sec Process 75 on node node-4 ending calc at 152.369008 sec Process 77 on node node-5 starting calc at 152.393202 sec Process 74 on node node-4 ending calc at 152.431586 sec Process 78 on node node-5 starting calc at 154.258755 sec Process 76 on node node-5 ending calc at 156.204467 sec Process 79 on node node-5 starting calc at 156.435276 sec Process 80 on node node-6 starting calc at 158.304309 sec Process 77 on node node-5 ending calc at 158.322973 sec Process 81 on node node-6 starting calc at 160.172196 sec Process 78 on node node-5 ending calc at 160.382663 sec Process 79 on node node-5 ending calc at 162.235015 sec Process 80 on node node-6 ending calc at 162.318579 sec Process 82 on node node-6 starting calc at 162.335486 sec Process 81 on node node-6 ending calc at 164.170514 sec Process 83 on node node-6 starting calc at 164.344626 sec Process 84 on node node-7 starting calc at 166.212309 sec Process 82 on node node-6 ending calc at 166.247443 sec Process 85 on node node-7 starting calc at 168.077163 sec Process 83 on node node-6 ending calc at 168.138188 sec Process 84 on node node-7 ending calc at 170.076853 sec Process 86 on node node-7 starting calc at 170.236582 sec Process 87 on node node-7 starting calc at 172.101324 sec Process 88 on node node-8 starting calc at 173.966105 sec Process 85 on node node-7 ending calc at 174.158338 sec Process 89 on node node-8 starting calc at 175.832028 sec Process 87 on node node-7 ending calc at 176.095715 sec Process 86 on node node-7 ending calc at 176.099371 sec Process 88 on node node-8 ending calc at 177.992950 sec Process 90 on node node-8 starting calc at 177.995608 sec Process 89 on node node-8 ending calc at 179.841781 sec Process 91 on node node-8 starting calc at 180.007976 sec Process 92 on node node-9 starting calc at 181.873386 sec Process 90 on node node-8 ending calc at 181.924975 sec Process 93 on node node-9 starting calc at 183.738248 sec Process 91 on node node-8 ending calc at 183.800574 sec Process 92 on node node-9 ending calc at 185.843274 sec Process 94 on node node-9 starting calc at 185.864344 sec Process 93 on node node-9 ending calc at 187.764613 sec Process 95 on node node-9 starting calc at 187.905170 sec Process 0 on node node-10 starting calc at 189.448858 sec Process 94 on node node-9 ending calc at 189.775795 sec Process 95 on node node-9 ending calc at 191.719839 sec Process 0 on node node-10 ending calc at 193.169813 sec Answer is 1965817060924467643529822208.0000000000000000 wall clock time = 193.170001 Видно, что на коротком промежутке времени большое влияние оказывают случайные факторы – процессы пишут сообщение о старте не по порядку. Однако далее, поскольку на пересылку данных и счет уходит более значительное время, и здесь уже явно виден порядок, в котором нулевой процесс отсылает данные. Видно также, что в большинстве случаев нулевой процесс успевает послать очередную порцию данных более чем одному процессу, прежде чем предыдущий выполнит счет, однако в данном случае вычисления крайне просты, поэтому из 193 секунд работы всех процессов более 189 до начала счета нулевым процессом ушли на генерацию и пересылку данных. В реальных задачах со сложными вычислениями время пересылки данных станет существенно меньше времени 36 пересылки данных функциями MPI, так что параллелизм оборудования станет использоваться в достаточно полной мере. Следует также иметь в виду, что реализация конкретной библиотеки MPI не обязана упорядочивать вывод от процессов – в отличие от примера выше, строки starting и ending могли бы быть выведены в совершенно произвольном порядке, порядок выполнения программы можно было бы понять только по меткам времени. Не обязана она и выводить его в файл сразу по мере печати информации процессами (например, если ваши процессы постоянно пишут что-то на экран, и вы хотите с помощью команды типа tail -f taskname.out1234 отслеживать процесс выполнения). Поэтому вывод результатов одновременно более чем одним процессом имеет ограниченное применение, в случае большого количества данных пользуйтесь отдельными файлами (возможно, в отдельном хранилище данных). Просмотр состояния очереди Посмотреть текущее состояние очереди можно командой: tasks [параметры] Сначала показываются работающие в данный момент задачи, а затем ожидающие, в т.ч. заблокированные. Команда tasks поддерживает следующие параметры: -q <очередь> – просмотр заданной очереди на текущем кластере. Если параметр не задан, в качестве имени очереди берется значение переменной среды QS_QUEUE. Обычно на нашем кластере есть только одна очередь, так что эту опцию можно не указывать. -l – просмотр расширенной информации о задачах. -o – показать только свои задачи (по умолчанию). -f – показать только задачи других пользователей. Вместе с -o будет показан полный список задач в очереди для всех пользователей. -t – включить в вывод показ текущих настроек лимитов времени. Команда tasks применяется не только для просмотра состояния, но и для других операций над очередью. Подробнее можно узнать в странице справочника man tasks. Удаление задачи Удалить свою задачу, стоящую в очереди или выполняющуюся, можно командой: tasks [-q <очередь>] -d <номер> Удалить все свои задачи можно командой: tasks [-q <очередь>] -d all В случае необходимости удаления нескольких (но не всех) задач по определенному критерию (например, только выполняющиеся, или с 37 определенным именем) у команды tasks есть возможность их задания вместо ручного перебора нужных номеров задач, за подробной информацией обратитесь к man tasks. 38