Что значит ошибка сервиса mpi мпс

Что значит ошибка сервиса mpi мпс

Вышеупомянутый текст имеет приоритет над любым текстом по обработке ошибок внутри этого документа. Текст, в котором говорится, что ошибки будут обработаны, должен содержать информацию о том, как они могут быть обработаны.

Привязка обработчиков ошибок к коммуникатору исключительно локальная: различные процессы могут присоединить различные обработчики ошибок к тому же самому коммуникатору.

Вновь созданный коммуникатор наследует обработчик ошибок, который связан с «родительским» коммуникатором. В частности, пользователь может определить «глобальный» обработчик ошибок для всех коммуникаторов, связывая этот обработчик с коммуникатором MPI_COMM_WORLD сразу после инициализации.

В MPI доступны несколько предопределенных обработчиков ошибок :

Реализации могут обеспечивать дополнительные обработчики ошибок, программисты также могут написать свои собственные обработчики ошибок.

Совет разработчикам: Хорошие реализации MPI должны максимально ограничивать воздействие ошибки, чтобы нормальное функционирование могло продолжаться после того, как обработчик ошибок был запущен. В документации должна содержаться информация относительно возможного эффекта по каждому классу ошибок.[]

Обработчик ошибок MPI является скрытым объектом, связанным с дескриптором. Процедуры MPI обеспечивают создание новых обработчиков ошибок, связывают обработчики ошибок с коммуникаторами и проверяют, какой обработчик ошибок связан с коммуникатором.

Синтаксис функции MPI_ERRHANDLER_CREATE представлен ниже.

INfunctionустановленная пользователем процедура обработки ошибок
OUTerrhandlerMPI обработчик ошибок (дескриптор)

int MPI_Errhandler_create(MPI_Handler_function *function,
MPI_Errhandler *errhandler)

MPI_ERRHANDLER_CREATE(FUNCTION, ERRHANDLER, IERROR)
EXTERNAL FUNCTION
INTEGER ERRHANDLER, IERROR

Функция MPI_ERRHANDLER_CREATE регистрирует процедуру пользователя в качестве обработчика MPI исключений. Возвращает в errhandler дескриптор зарегистрированного обработчика исключений.

Синтаксис функции MPI_ERRHANDLER_SET представлен ниже.

int MPI_Errhandler_set(MPI_Comm comm, MPI_Errhandler errhandler)

MPI_ERRHANDLER_SET(COMM, ERRHANDLER, IERROR)
INTEGER COMM, ERRHANDLER, IERROR

Функция MPI_ERRHANDLER_SET связывает новый обработчик ошибок errorhandler с коммуникатором comm на вызывающем процессе. Заметим, что обработчик ошибок всегда связан с коммуникатором.

Синтаксис функции MPI_ERRHANDLER_GET представлен ниже.

INcommкоммуникатор, из которого получен обработчик ошибок (дескриптор)
OUTerrhandlerMPI обработчик ошибок, связанный с коммуникатором (дескриптор)

int MPI_Errhandler_get(MPI_Comm comm, MPI_Errhandler *errhandler)

MPI_ERRHANDLER_GET(COMM, ERRHANDLER, IERROR)
INTEGER COMM, ERRHANDLER, IERROR

Функция MPI_ERRHANDLER_GET возвращает в errhandler дескриптор обработчика ошибок, связанного с коммуникатором comm. Пример: библиотечная функция может записать на входной точке текущий обработчик ошибок для коммуникатора, затем установить собственный частный обработчик ошибок для этого коммуникатора и восстановить перед выходом предыдущий обработчик ошибок.

Синтаксис функции MPI_ERRHANDLER_FREE представлен ниже.

INOUTerrhandlerMPI обработчик ошибок (дескриптор)

int MPI_Errhandler_free(MPI_Errhandler *errhandler)

MPI_ERRHANDLER_FREE(ERRHANDLER, IERROR)
INTEGER ERRHANDLER, IERROR

Синтаксис функции MPI_ERROR_STRING представлен ниже.

MPI_ERROR_STRING(errorcode, string, resultlen)

INerrorcodeккод ошибки, возвращаемый процедурой MPI
OUTstringтекст, соответствующий errorcode
OUTresultlenдлина (в печатных знаках) результата, возвращаемого в string

int MPI_Error_string(int errorcode, char *string, int *resultlen)

MPI_ERROR_STRING(ERRORCODE, STRING, RESULTLEN, IERROR)
INTEGER ERRORCODE, RESULTLEN, IERROR
CHARACTER*(*) STRING

void MPI::Get_error_string(int errorcode, char* name, int& resulten)

Источник

Часть 2. MPI — Учимся следить за процессами

В этом цикле статей речь идет о параллельном программировании с использованием MPI.

В предыдущей статье мы обсудили как запускать программу, что такое MPI и зачем нужно это параллельное программирование, если можно писать и без него. В этой статье, предпологаем, что читатель ознакомился с материалом, изложенным в предыдущей и приступаем к следующему шагу изучения технологии MPI, а именно управлению процессами. Дабы избежать негодования опытных программистов далее я буду иметь ввиду под «потоками», «процессами» и т.п. часть вычислительной системы на которой запущен конкретный экземпляр программы (Этой частью может быть как конкретный поток, так и любой вычислительный узел системы).

Номера процессов и общее число процессов

Чтобы выполнять полезные действия при построении параллельной программы необходимо распределять роли между вычислительными узлами, потоками. Для этого нам просто жизненно необходимо знать какой поток обрабатывает конкретный экземпляр запущенной на нем программы, но для начала неплохо было бы узнать сколько их запущено вообще.

Для того чтобы узнать на каком потоке запущена программа существует процедур MPI_Comm_size. Она принимает на вход коммуникатор(о нем пойдет речь далее), и адрес памяти куда будет записано целое число, то есть количество потоков обрабатывающих программу.

Так что такое коммуникатор и зачем он собственно нужен? Коммуникатор это такой объект, который хранит в себе информацию о запущенных потоках, доступ к которым ему предоставлен. Роль коммуникатора в программе очень важна, так как большая часть работы с процессами связана именно через него, на то он и называется коммуникатором. В MPI существует глобальный коммуникатор который имеет доступ ко всем запущенным потокам, его название MPI_COMM_WORLD. Также мы можем создавать свои, локальные коммуникаторы для выполнения определенных задач на конкретных потоках, и это довольно мило.

Что такое коммуникатор разобрались, теперь было бы неплохо узнать на каком из процессов работает конкретный экземпляр программы. Для этого существует процедура MPI_Comm_rank. Она принимает на вход аналогичные параметры, только вместо сохранения количества процессов она сохраняет по адресу номер конкретного процесса. Определена она вот так:

То есть мы передаем ей коммуникатор в котором надо узнать номер процесса и собственно адрес куда его нужно записать.

Теперь предлагаю соединить эти 2 важные процедуры и посмотреть на их работу на практике, пока еще конечно на бесполезной программе, но понять как работают эти процедуры она позволит вполне.

Выход для 5 потоков будет следующим:

Как видим каждый поток напечатал свой номер процесса и общее число запущенных процессов.

Как это можно использовать? Я думаю вы уже догадались, что уже имея только эту информацию можно использовать возможности параллельного выполнения программы. Допустим у нас есть задача где довольно много однотипных независимых вычислений, будь то сложение, вычитание матриц, векторов, возведение в степень большого числа чисел и т.п. То есть те задачи, где вычисления никак не зависят друг от друга.

Работа Comm_size, Comm_rank на примере

Более полезным примером такого распараллеливания будет поиск квадратов чисел в заданном диапазоне. Далее идет небольшой пример программы которая это и делает.

Выход для 5 потоков:

Дабы не вставлять огромные участки с кодом я взял число MAX=20. Как видим зная номер процесса на котором исполняется конкретный экземпляр программы уже дает ощутимые возможности, но это далеко не все, ведь сложные задачи далеко не всегда так легко делятся на независимые участки с вычислениями.

Важное замечание, которое вы, возможно, заметите: процессы совсем не обязательно будут выводить в консоль в правильном порядке свои результаты. MPI гарантирует лишь корректность вывода в плане целостности данных, то есть дается гарантия что строка будет выведена целиком, но порядок не гарантируется.

Пологаю, что все кто попробуют запустить такую программу заметят, что выполнение начинается не сразу, а иногда даже из-за этого программа на одном потоке работает горазда быстрее чем параллельная. С виду кажется не совсем логичным, если не знать о том как работает параллельное выполнение, однако на все есть свои веские причины. Когда срабатывает процедура MPI_Init, довольно большое количество ресурсов тратится на то чтобы собственно породить эти параллельные потоки и создать среду для их корректного выполнения и именно поэтому маленькие программы и мелкие, краткосрочные вычисления, мало того что не требуют подобной оптимизации, так еще и выполняются медленнее из-за того что время порождения потоков занимает существенную часть из времени всех вычислений, а иногда даже и больше чем сами вычисления. Именно поэтому стоит несколько раз подумать нужно ли распараллеливать данный участок вообще.

Работа со временем

Первая процедура возвращает на вызвавшем ее процессе астрономическое время в секундах, прошедшее с некоторого момента в прошлом. Какой это конкретно момент не совсем имеет значения, но гарантируется, эта точка отсчета не изменится в течение всего времени выполнения программы. Зная эту процедуру можно довольно легко определить время выполнения конкретного участка кода, ведь разность между Wtime в конце и начале программы как раз его и определяет, все стандартно и вполне знакомо.

Таймеры разных процессов не всегда могут быть синхронизированы, для того чтобы узнать так это или нет можно вызвать глобальную переменную MPI_WTIME_IS_GLOBAL, она вернет 0 или 1, то есть не синхронизированы или синхронизированы.

Вторая процедура как раз возвращает разрешение таймера конкретного процесса в секундах.

И на последок покажу как узнать имя физического процессора на котором выполняется программа. Для этого есть процедура MPI_Get_processor_name. Синтаксис и параметры вот такие:

На вход она принимает соответственно ссылку на область памяти куда нужно записать имя физического процессора и ссылку куда будет записана длина этой строки.

Резюмируем

В данной статье я наглядно показал как можно работать с процессами в MPI, зачем нам узнавать на каком процессе запущена программа и как работать со временем. Имея в багаже эти знания уже можно вполне успешно создавать простые параллельные программы и управлять ими.

Для закрепления полученных знаний предлагаю написать вам простую программу, которая узнает является ли число простым для чисел в заданном диапазоне от 1 до N. Это наглядно покажет вам как можно легко и просто распараллелить вычисления с помощью данной технологии и позволит отложить все полученные навыки в голове.

Всем приятного времени суток, хабравчане и те кто набрел на эту статью извне.

Источник

Основы технологии MPI на примерах

Параллельное программирование — очень актуальное направление, т.к. большинство современных вычислительных устройств (включая телефоны) являются многоядерными или многопроцессорными. В предыдущей записи я публиковал учебник по OpenMP, однако OpenMP позволяет программировать только системы с общей памятью — в большей части, многоядерные компьютеры. Основной проблемой таких систем является плохая масштабируемость — не так легко и дешево увеличить число ядер в компьютере.

Другой класс параллельных ЭВМ — системы с распределенной памятью, к которым, в частности, относятся кластеры. Они представляют собой набор соединенных сетью компьютеров. Такая система гораздо лучше мастабируется — не составит особого труда купить и подключить в сеть дополнительную сотню компьютеров. Число вычислительных узлов в кластерах измеряется тысячами.

Что значит ошибка сервиса mpi мпс. Смотреть фото Что значит ошибка сервиса mpi мпс. Смотреть картинку Что значит ошибка сервиса mpi мпс. Картинка про Что значит ошибка сервиса mpi мпс. Фото Что значит ошибка сервиса mpi мпсКластерная архитектура

Взаимодействуют узлы кластера с помощью передачи сообщений по сети, поэтому стандартом программирования таких систем является MPI (Message Passing Interface). Библиотека MPI предоставляет программисту огромный выбор способов передать сообщение между узлами, в этой статье я постараюсь описать основные способы на простых примерах.

Содержание:

Ключевые особенности MPI

Допустим, есть у нас кластер. Чтобы программа начала на нем выполняться, ее необходимо скопировать на каждый узел, запустить и установить связь между процессами. Эту работу берет на себя утилита mpirun (под Linux) или mpiexec (под Windows), так например, чтобы запустить 5 процессов достаточно написать:

Однако программа должна быть написана определенным образом. Вообще, технология MPI позволяет как использовать модель SPMD (Single Process, Multiple Data), так и MPMD [1], в этой статье я рассматривают только первый вариант. Далее по тексту узел и процесс будут означать одно и тоже, хотя на одном узле может быть создано несколько процессов (именно так я делаю при запуске примеров статьи, т.к. отлаживаю их на персональном компьютере). Это вводная статья, поэтому тут не пойдет речь о коммуникаторах, в которые могут группироваться процессы.

Суть SPMD заключается в том, что для решения задачи запускается множество одинаковых процессов. На приведенном выше рисунке видно, что пользователь (который вводит данные и хочет получить результат) взаимодействует только с одним узлом кластера. Такой узел называется root и логика его работы должна отличаться, ведь он должен не только взаимодействовать с пользователем, но и, получив исходные данные, выполнить их рассылку остальным процессам. Каждый процесс имеет свой номер (в MPI принят термин «ранг«) в рамках каждого коммуникатора, при этом у root ранг обычно равен нулю.

Процессы обмениваются сообщениями, каждое из которых помимо номеров процесса отправителя и получателя имеет тег, а также тип и количество передаваемых элементов. Тег представляет собой целое число, с помощью которого программа может отличать одни сообщения от других. В силу того, что сегодня мы рассмотрим очень простые примеры, тег нам не особо пригодится (можно было бы везде использовать, скажем, «ноль» вместо тега). Все поступающие процессу сообщения помещаются в очередь, из которой могут быть извлечены в соответствии с запрашиваемыми параметрами (тегом, отправителем и т.п.).

В связи с тем, что MPI-программа обладает множеством особенностей, компилироваться она должна специальным компилятором. Под Linux для этого используется mpic++, а под Windows можно применять расширение для Microsoft Visual Studio. Для сборки примеров статьи под Linux я использовал примерно следующую команду:

Разобравшись с особенностями технологии, перейдем к примерам. В этой статье мы будем пытаться посчитать сумму элементов массива — несмотря на простоту, эта задача позволяет продемонстрировать самые различные способы передачи данных между узлами.

Операции точка-точка MPI

Блокирующие операции — MPI_Send, MPI_Recv, MPI_Probe

Операции точка-точка позволяют передавать данные между парой узлов. Самые простые функции этого вида — MPI_Send и MPI_Recv, выполняющие передачу и прием сообщения, соответственно:

Функция MPI_Send выполняет передачу count элементов типа datatype, начиная с адреса определенного buf, процессу с номером dest в коммуникаторе comm. При этом сообщению присваивается некоторый тег.

Функция MPI_Recv выполняет прием, при этом она ожидает появления во входном буфере сообщения с заданными параметрами (выполнение программы не продолжится до того, как такое сообщение поступит). Функция также возвращает статус, который может содержать код ошибки.

Рассмотрим первый пример:

Все обращения к функциям MPI должны размещаться между MPI_Init и MPI_Finalize. В качестве коммуникатора в этом примере используется MPI_COMM_WORLD, который включает в себя все процессы, запущенные для текущей задачи с помощью mpirun. Каждый процесс получает свой ранг с помощью вызова MPI_Comm_rank, количество процессов в коммуникаторе возвращает функция MPI_Comm_size.

В теле программы явно выделяется два блока кода — один выполняется главным процессом, а другой — остальными. Главный процесс занимается вводом/выводом данных, и их распределением между остальными узлами. Все остальные процессы ожидают от главного часть массива, вычисляют сумму элементов и передают результат назад. Чтобы принять массив, дочерние процессы должны сначала выделить память, но для этого необходимо знать сколько именно элементов им будет передано — сделать это можно с помощью функции MPI_Probe, которая работает также как MPI_Send (блокирует процесс до поступления в очередь сообщения с заданными параметрами), но не удаляет сообщение из очереди, а лишь возвращает его статус. Структура типа MPI_Status содержит тип элементов и их количество.

Операции MPI_Send, MPI_Probe и MPI_Recv являются синхронными (блокирующими), т.к. останавливают выполнение основного потока процесса до тех пор, пока не выполнится какое-либо действие (данные не будут записаны в сокет или не поступит сообщение с требуемыми параметрами).

Асинхронные операции — MPI_Isend

Приведенный выше пример работает прекрасно, но бутылочным горлышком является нулевой процесс, т.к. перед тем как приступить к обработке своей части массива root должен передать данные всем остальным процессами. Немного улучшить картину можно с помощью неблокирующих (асинхронных) операций — одной из них является MPI_Isend.

Функции неблокирующего обмена имеют такие же аргументы как и их блокирующие аналоги, но в качестве дополнительного параметра принимают параметр типа MPI_Request. Чтобы нулевой процесс в приведенном выше примере работал асинхронно достаточно изменить лишь этот фрагмент кода:

Теперь функция передачи возвращает управление немедленно, сама же передача происходит параллельно с выполнением других команд процесса. Узнать о ходе выполнения асинхронной операции мы сможем с помощью специальных функций:

Функция MPI_Wait является блокирующей — она останавливает выполнение процесса до тех пор, пока передача, связанная с request не будет завершена. Функция MPI_Test является неблокирующей (не дожидается окончания выполнения операции) — о том была завершена операция или нет, она сигнализирует с помощью флага (true — завершена).

В нашем примере в использовании функций MPI_Wait и MPI_Test нет необходимости, т.к. синхронно выполняется сбор результатов вычислений процессов. Главный процесс инициирует передачу данных и приступает к обработке своей части массива, после этого синхронно ожидает поступления данных от каждого по очереди процесса. Кстати, асинхронной может быть не только передача, но и прием данных — функция MPI_Irecv.

Чтобы избежать ошибок, необходимо представлять как именно может быть реализована работа неблокирующих функций. Например, MPI_Isend инициирует передачу данных, которая выполняется в отдельном потоке параллельно. По окончанию передачи этот поток должен изменить переменную request. Это значит, что такой код вполне может привести к ошибкам:

Тут на каждой итерации цикла создается экземпляр переменной типа MPI_Request, инициируется передача и объект разрушается (память освобождается). Это значит, что поток, выполняющий передачу обратится к памяти, которая будет освобождена (и возможно уже повторно распределена для других целей), а значит — программа будет вести себя непредсказуемо. Вывод — объект request должен существовать до завершения асинхронной передачи.

Вызов функции MPI_Request_free необходим в случаях если не требуется ждать окончания асинхронной передачи и при этом хочется использовать один экземпляр MPI_Request — в нашем случае один и тот же объект используется для передачи данных всем дочерним процессам. За более детальной информацией о работе асинхронных операций MPI предлагаю обратиться к стандарту (раздел 3.7 Nonblocking Communication) [3].

Буферизованная передача — MPI_Bsend

В стандарте MPI сказано, что функция MPI_Send вернет управление после того, как сообщение будет записано в выходной буфер, я же выше писал что это сокет (пусть это не совсем точно) — стандарт в этом месте дает некоторую свободу, возможны различные реализации. Однако, «выходной буфер» в стандарте — это однозначно не область в оперативной памяти, т.к. для работы с буфером в ОЗУ предусмотрена другая функция — MPI_Bsend.

Функция MPI_Send записывает данные в сокет и только после этого возвращает управление. Функция MPI_Isend вернет управление сразу, но нам все равно нельзя будет изменять передаваемые данные, пока они не будут записаны в сокет. При этом, работа с сокетом выполняется медленнее, чем с оперативной памятью — поэтому ускорить работу программы в ряде случаев можно путем копирования данных в некоторый буфер в оперативной памяти и передачей данных из этого буфера. Изменять отправляемые данные мы сможем сразу после того, как они будут скопированы. Примерно так и работает функция MPI_Bsend. Сложность реализации такой операции вручную заключается также в том, что после отправки данных память из под буфера нужно освободить.

Чтобы выделенная область памяти использовалась в качестве буфера при передачи в буферизованном режиме — необходимо использовать функцию MPI_Buffer_attach. Буфер может состоять из нескольких «присоединенных» областей. В разделе 3.6.1 стандарта [3] говорится о том, что буфер может быть устроен как циклическая очередь сообщений, отправленные сообщения из буфера удаляются, позже на их место могут быть записаны другие сообщения.

При использовании буферизованного режима исходный код будет не сильно отличаться от приведенного выше, ниже приведен только фрагмент, которого коснулись изменения:

Перед использованием функции выделяется память под буфер, т.к. в этой памяти размещается очередь — следовательно есть некоторые издержки (размер которых зависит от конкретной реализации стандарта), поэтому необходимо выделять чуть больше памяти (MPI_BSEND_OVERHEAD). Выделенная область прикрепляется к буферу, затем используется MPI_Bsend (аргументы функции полностью совпадают с MPI_Send, буфер в качестве аргумента не передается, т.к. он является глобальным для процесса). После того, как все сообщения будут переданы — буфер можно отсоединить, а память освободить.

Коллективные операции. Пример использования MPI_Reduce

Коллективные операции выполняются всеми процессами указанного коммуникатора. Ниже приведена картинка из стандарта [3], на которой показана суть некоторых операций:

Что значит ошибка сервиса mpi мпс. Смотреть фото Что значит ошибка сервиса mpi мпс. Смотреть картинку Что значит ошибка сервиса mpi мпс. Картинка про Что значит ошибка сервиса mpi мпс. Фото Что значит ошибка сервиса mpi мпсКоллективные операции MPI

Верхняя часть схемы иллюстрирует операцию MPI_Bcast, которая позволяет передать некоторые данные с одного узла кластера на все остальные. Нижняя — соответствует операциям MPI_Scatter и MPI_Gather. Если у нас на узле U есть массив из N элементов и его части необходимо передать на P узлов кластера — можно использовать функцию MPI_Scatter. Проблем не возникнет если N делится нацело на P, т.к. при выполнении MPI_Scatter все узлы получат одинаковое количество элементов. Обратную операцию выполняет MPI_Gather, т.е. собирает данные со всех P узлов на узел U.

Эти операции являются синхронными и используют MPI_Send (это закреплено стандартом), однако существуют асинхронные аналоги — MPI_Ibcast, MPI_Igather и MPI_Iscatter.

Операция MPI_Bcast теоретически (зависит от реализации библиотеки) может работать более эффективно и выполняться за \(O(log(n))\) операций вместо \(O(n)\).

Что значит ошибка сервиса mpi мпс. Смотреть фото Что значит ошибка сервиса mpi мпс. Смотреть картинку Что значит ошибка сервиса mpi мпс. Картинка про Что значит ошибка сервиса mpi мпс. Фото Что значит ошибка сервиса mpi мпсЭффективная реализация MPI_Reduce и MPI_Bcast

На приведенной схеме цветом выделен узел, на котором находятся передаваемые данные. В начале работы такой узел один. После первой передачи данные есть уже на двух узлах, оба они могут участвовать в передачи. При реализации такой схемы для передачи данных на 1000 узлов будет достаточно 10 операций. Таким же образом может работать операция MPI_Reduce:

A more efficient implementation is achieved by taking advantage of associativity and using a logarithmic tree reduction. [3]

Операция MPI_Reduce не просто передает данные, но и выполняет над ними заданную операцию. В нашем примере применить ее можно вместо сбора результатов вычисления сумм:

Операция MPI_Reduce может выполняться не только над числами, но и над массивами (при этом будет применена к каждому его элементу отдельно).

Заключение

Целью статьи было продемонстрировать ряд функций библиотеки MPI и показать, что реализовать их вручную не так легко — этим привлечь внимание к библиотеке.

Пример с функцией MPI_Isend наглядно демонстрирует насколько сложно реализовать аналогичное поведение вручную. Дело не только в том, что передача выполняется в отдельном потоке. Сложно придумать механизм лучший, чем работа с MPI_Request, однако к объекту request может параллельно обращаться несколько потоков, поэтому все эти операции защищаются семафорами.

Еще более наглядным является пример с MPI_Bsend, т.к. вручную реализовать циклический буфер сообщений (который, кстати, тоже защищается семафорами) — не простая задача. Однако, в библиотеке MPI гораздо больше функций типа точка-точка. Различие между ними заключается в преставлении о «завершенной передаче», т.е. моменте когда блокирующая операция вернет управление или для асинхронной операции завершится MPI_Wait.

Примеры статьи являются полностью искусственными — именно по этой причине я не стал приводить таблицу с временем выполнения программы для различных вариантов реализации. Дело в том, что кластерная архитектура (а следовательно и MPI) подходит не всех типов задач — необходимо учитывать затраты на передачу данных. Так, для вычисления на кластере суммы элементов массива необходимо выполнить, порядка \(O(n)\) операций передачи, но \(O(\frac

)\) операций сложения может быть выполнено параллельно. Трудоемкость вычислений будет оцениваться \(O(n)\) при любом количестве узлов кластера. Подробнее об оценке трудоемкости параллельных алгоритмов советую прочитать в книге Миллера [4].

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *