Распределенные вычисления: Часть 6. Приложения BOINC

Узнайте как управлять проектом BOINC удаленно через специальный интерфейс администрирования

Создайте первое собственное приложение, использующее всю мощь распределенных вычислений на базе платформы BOINC!

Евгений Ивашко, Сотрудник Института РАН, Институт прикладных математических исследований Карельского научного центра РАН.

Сотрудник Института прикладных математических исследований Карельского научного центра РАН. Имеет степень магистра математики.



24.11.2011

1. Введение

API взаимодействия

Для платформы BOINC разработчики предоставляют документированные интерфейсы API, предназначенные для взаимодействия с приложениями и web-сайтами. Возможности, предоставляемые этими интерфейсами, включают в себя:

  • локальный и удаленный контроль графического интерфейса клиента BOINC;
  • предоставление информации о набранных кредитах (в целом по проекту, для отдельного пользователя, команды или страны);
  • управление учетными записями нескольких проектов через единый web-интерфейс;
  • суммарная статистика по кредитам для хостов, команд и пользователей, участвующих в нескольких проектах;
  • механизмы, позволяющие локальным программам редактировать настройки клиента BOINC.

Подробную информацию по каждому типу API можно найти по ссылкам на странице http://boinc.berkeley.edu/trac/wiki/SoftwareAddon

Перед вами новая — шестая — статья серии, посвященной изучению платформы для организации распределенных вычислений BOINC. Сегодня мы начинаем новую тему — создание приложений, способных использовать всю мощь volunteer computing. Если вы не знаете что такое BOINC, тогда прочтите первую статью серии (п. 1 раздела Ссылки). Разрабатывая приложения, необходимо ясно представлять себе архитектуру платформы BOINC, основные компоненты серверной части, последовательность обработки задания службами и многое другое. Основные сведения по этому вопросу вы найдете во второй статье (п. 2 раздела Ссылки). В других статьях серии рассказывалось о том, как установить, настроить и сопровождать свой собственный сервер распределенных вычислений на базе платформы BOINC (пп. 3-5 раздела ссылки).

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

2. Простейший пример распределенного приложения

Первый пример, который мы будем рассматривать можно найти среди исходных кодов Boinc в каталоге samples/example_app. Этот пример представляет собой простейшее однопоточное приложение BOINC. Программа выполняет простую работу (например, проверку), которая может быть завершена либо успешно, либо неудачно. Пример можно использовать как основу для собственных приложений — нужно лишь заменить реализацией собственного алгоритма ту часть, которая относится к вычислениям.

Исходный код примера разбит на заголовочный файл, два файла с кодом и Makefile для сборки проекта (не считаем файлы, содержащие суффикс «mac»):

ls ~/boinc/samples/example_app
Makefile 
uc2.cpp  
uc2_graphics.cpp  
uc2.h

Далее мы подробнее изучим исходный текст программы-примера. Однако лучше иметь перед глазами оригинальный исходный файл — отдельные фрагменты и незначимые для целей изучения BOINC блоки кода мы будем пропускать или рассматривать в другом порядке. Среди всего прочего будут пропущены те части, которые связаны с взаимодействием процессов в рамках одного компьютера и весь вывод графики. Ни в том, ни в другом нет особенностей, связанных с BOINC. В разделе 2.3 дана таблица функций API BOINC, используемых в программе. К этой таблице можно обращаться по мере появления в исходном коде соответствующих функций (в тексте статьи они будут выделены курсивом).

2.1 Программа-пример UpperCase

Программы на языках, отличных от C/C++

Существуют более-менее простые способы использования других языков программирования, помимо базовых для BOINC-приложений C и C++. Разработчиками описаны «трюки» для следующих языков:

  • FORTRAN — можно выбрать между использованием конвертером в язык C или тернистым путем включения вызовов исходных API BOINC в разрабатываемое приложение (см. http://boinc.berkeley.edu/trac/wiki/FortranApps);
  • Java — если простота разработки приложения компенсирует необходимость «тащить» с программой на каждый клиентский компьютер виртуальную машину Java, то можно воспользоваться инструкциями с этой страницы: http://boinc.berkeley.edu/trac/wiki/JavaApps. Больше всего внимания уделяется тому, как запустить jar-файл;
  • Python — рекомендуется воспользоваться компилятором py2exe, переводящим программу с языка Python в исполняемый Windows-код. Естественно, запустить такую программу смогут лишь BOINC-клиенты, работающие под ОС семейства Windows.

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

Основной код приложения содержится в файле uc2.cpp, программа переводит текст входного файла в верхний регистр. Для демонстрации возможностей BOINC, разработчики программы реализовали обработку следующих параметров командной строки:

  • run_slow: приложение «засыпает» на 1 секунду после каждого обработанного символа;
  • cpu_time N: по завершении обработки программа дополнительно использует N секунд времени процессора;
  • early_exit: принудительное завершение программы после обработки 30 символов;
  • early_crash: аварийное завершение программы после обработки 30 символов;
  • early_sleep: программа «засыпает» после обработки 30 символов.

Естественно, исходный код программы начинается с подключения заголовочных файлов:

#ifdef _WIN32
#include "boinc_win.h" // этот заголовочный файл содержит определения констант 
                       // для компиляции под ОС семейства Windows
#else

#include "config.h"    // здесь содержатся различные настройки 
                       // для платформы GNU/Linux… <пропустим подключение стандартных библиотек С++>
#endif

Затем подключаются различные заголовочные файлы платформы, содержащие определения функций API BOINC.

#include "str_util.h"
#include "util.h"
#include "filesys.h"
#include "boinc_api.h"
#include "mfile.h"
#include "graphics2.h"

Все указанные заголовочные файлы (за исключением стандартных для C++) в каталоге исходных кодов BOINC находятся в подкаталогах lib и api. Там же можно найти множество определений (и реализаций) других функций API BOINC.

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

#define CHECKPOINT_FILE "upper_case_state"
#define INPUT_FILENAME "in"
#define OUTPUT_FILENAME "out"

Теперь перейдем к основной функции приложения.

int main(int argc, char **argv) {
    int i;
    int c, nchars = 0, retval, n;
    double fsize, fd;
    char input_path[512], output_path[512], chkpt_path[512];
    MFILE out;
    FILE* state, *infile;

	  <пропустим разбор параметров командной строки>

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

	retval = boinc_init();
    if (retval) {
        fprintf(stderr, "%s boinc_init returned %d\n",
            boinc_msg_prefix(), retval);
        exit(retval);
    }

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

Для чтения открывается входной файл. Функция boinc_resolve_filename используется, чтобы определить физический путь к файлу, заданному логическим именем INPUT_FILENAME:

boinc_resolve_filename(INPUT_FILENAME, input_path, sizeof(input_path));

Затем открывается для чтения файл, располагающийся по выясненному раннее физическому пути:

    infile = boinc_fopen(input_path, "r");
    if (!infile) {
        fprintf(stderr,
            "%s Couldn't find input file, resolved name %s.\n",
            boinc_msg_prefix(), input_path
        );
        exit(-1);
    }

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

boinc_resolve_filename(OUTPUT_FILENAME, output_path, sizeof(output_path));
    boinc_resolve_filename(CHECKPOINT_FILE, chkpt_path, sizeof(chkpt_path));
    state = boinc_fopen(chkpt_path, "r");

Необходимость задействования вспомогательного файла определяется самой сутью «volunteer computing». Программа (клиент BOINC) могла быть завершена до окончания расчета задания. Однако было бы неразумным в этом случае начинать всю работу заново (особенно, если для завершения требуется несколько часов непрерывных вычислений). Для восстановления текущего состояния используется вспомогательный файл:

    if (state) {

Читаем из вспомогательного файла число уже обработанных символов:

        n = fscanf(state, "%d", &nchars);
        fclose(state);
    }
    if (state && n==1) {

Пропускаем во входном файле все обработанные символы:

        fseek(infile, nchars, SEEK_SET);

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

boinc_truncate(output_path, nchars);

Для работы с выходным файлом разработчики посчитали более удобным использовать класс MFILE (которому принадлежит переменная out) — он определен в файле lib/mfile.h.

        retval = out.open(output_path, "ab");
    } else {
        retval = out.open(output_path, "wb");
    }
    if (retval) {
        fprintf(stderr, "%s APP: upper_case output open failed:\n",
            boinc_msg_prefix()
        );
        fprintf(stderr, "%s resolved name %s, retval %d\n",
            boinc_msg_prefix(), output_path, retval
        );
        perror("open");
        exit(1);
    }

Наконец, когда выполнена вся подготовительная работа, настало время основного цикла программы:

      for (int i=0; ; i++) {
        c = fgetc(infile);

        if (c == EOF) break;
        c = toupper(c);
        out._putchar(c);
        nchars++;

Если в командной строке был указан какой-либо из параметров (см. выше), то программа модифицирует свое поведение:

        if (run_slow) {
            boinc_sleep(1.);
        }

        if (early_exit && i>30) {
            exit(-10);
        }

        if (early_crash && i>30) {
            boinc_crash();
        }
        if (early_sleep && i>30) {
            g_sleep = true;
            while (1) boinc_sleep(1);
        }

Сохранение текущего состояния выполняется с помощью периодического вызова функции API BOINC boinc_time_to_checkpoint(). Эта функция, сверяясь с пользовательскими настройками клиента BOINC, определяет настало ли время для фиксации промежуточных результатов работы (тогда в нашей программе вызывается функция do_checkpoint).

        if (boinc_time_to_checkpoint()) {

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

            retval = do_checkpoint(out, nchars);
            if (retval) {
                fprintf(stderr, "%s APP: upper_case checkpoint failed %d\n",
                    boinc_msg_prefix(), retval
                );
                exit(retval);
            }

Клиент информируется о завершении сохранения состояния (промежуточных результатов) с помощью вызова функции boinc_checkpoint_completed():

boinc_checkpoint_completed();
        }

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

        fd = nchars/fsize;
        if (cpu_time) fd /= 2;
        boinc_fraction_done(fd);
    }

Согласно настройкам (см. выше, где написано о параметрах командной строки), потратим немного машинного времени «впустую». Заметьте, что при этом мы не забываем обновлять информацию о проценте выполнения работы!

    if (cpu_time) {
        double start = dtime();
        for (int i=0; ; i++) {
            double e = dtime()-start;
            if (e > cpu_time) break;
            fd = .5 + .5*(e/cpu_time);
            boinc_fraction_done(fd);

            if (boinc_time_to_checkpoint()) {
                retval = do_checkpoint(out, nchars);
                if (retval) {
                    fprintf(stderr, "%s APP: upper_case checkpoint failed %d\n",
                        boinc_msg_prefix(), retval
                    );
                    exit(1);
                }
                boinc_checkpoint_completed();
            }

Функция do_a_giga_flop выполняет триллион операций с плавающей запятой (Gflop).

            comp_result = do_a_giga_flop(i);
        }
    }

Задание выполнено на 100%

boinc_fraction_done(1);

Завершаем работу приложения BOINC

boinc_finish(0);
}

По окончании работы клиент BOINC самостоятельно свяжется с сервером, передаст ему результаты работы и запросит новое задание.

2.2 Структура программы BOINC

Теперь вернемся к структуре стандартного приложения. Первая функция API BOINC, которая может быть вызвана в программе, - это функция инициализации boinc_init(). Соответственно, завершиться приложение должно функцией boinc_finish(). В процессе работы необходимо обновлять информацию о степени выполнения задания (функция boinc_fraction_done()) и сохранять результаты промежуточных вычислений (связка boinc_time_to_checkpoint() и boinc_checkpoint_completed()).

boinc_init()
<основной цикл вычислений>
   ...
   boinc_fraction_done(x)
   if boinc_time_to_checkpoint()
      write checkpoint file
      boinc_checkpoint_completed()
boinc_finish(0)

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

2.3 Функции API BOINC

В этом разделе представлен список функций API BOINC, встречавшихся в программе-примере.

Таблица 1. Список используемых функций API BOINC
Определение функцииНазначение
1int boinc_init()Инициализация однопоточной программы; первая функция boinc, которая может быть вызвана.
2boinc_msg_prefix()Функция возвращает дату и идентификатор процесса. Этот префикс должен сопровождать все сообщения, записываемые приложением на стандартный вывод.
3int boinc_resolve_filename(char *logical_name, char *physical_name, int len)

или

int boinc_resolve_filename_s(char *logical_name, std::string& physical_name);
Назначение функции — преобразование логического имени файла в физическое. Например, для открытия файла my_file на чтение вместо
f = fopen("my_file", "r");

приложение должно иметь примерно такой код:

string resolved_name;
retval = boinc_resolve_filename_s("my_file", resolved_name);
if (retval) fail("can't resolve filename");
f = fopen(resolved_name.c_str(), "r");

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

4boinc_fopen(char* path, char* mode);Вместо fopen() рекомендуется все-таки использовать функцию boinc_fopen(char* path, char* mode);

Это связано со специфическими проблемами платформ: например, в Windows программы безопасности или индексации могут кратковременно заблокировать файл. boinc_fopen() выполняет несколько попыток открытия файла с односекундными интервалами; в Unix-системах сигнал может получить ошибку EINTR при использовании fopen()— boinc_fopen проверяет на наличие такой ошибки и повторяет попытку. Также boinc_fopen устанавливает флаг 'close-on-exec'.

5int boinc_truncate (const char * path, double size) «Обрезает» файл, делая его размер равным size. Отличие от обычной функции truncate заключается лишь в отдельной обработке при работе в ОС Windows.
6boinc_graphics_make_shmem()Функция, которая вызывается из основного приложения, создает сегмент разделяемой памяти указанного размера. Если сегмент не может быть выделен (например, из-за нехватки памяти), то приложение завершится ошибкой.
7typedef void (*FUNC_PTR)(); void boinc_register_timer_callback(FUNC_PTR); Регистрируется функция, которая вызывается каждую секунду по сигналу таймера.
8void boinc_sleep(double time)Бездействие программы в течение time секунд
9void boinc_crash ( ) Функция вызывает аварийный останов программы; интересен также метод, которым это делается (см. файл lib/util.cpp):
void boinc_crash() {
#ifdef _WIN32
	DebugBreak();
#else
	*(int*)0 = 0;
#endif
}
10boinc_fraction_done(double fraction_done);Клиент BOINC отображает процент выполнения задания. Эта информация передается клиенту приложением с помощью функции boinc_fraction_done(double fraction_done), где fraction_done — это число от 0 до 1. Функция выполняется быстро и может вызываться часто.
11int boinc_time_to_checkpoint();При проведении больших вычислений приложение BOINC может периодически сохранять текущее состояние выполнения работы. При этом используемый файл состояния должен содержать всю информацию, необходимую для продолжения выполнения задания.

Удобная пользователю частота сохранения состояния указывается в настройках клиента BOINC. Функция boinc_time_to_checkpoint() вызывается для того, чтобы определить подошло ли время сохранения состояния. В паре с этой функцией должна быть вызвана функция boinc_checkpoint_completed() - она сообщает клиенту о завершении сохранения состояния.

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

12void boinc_checkpoint_completed()boinc_checkpoint_completed() - это парная функция к boinc_time_to_checkpoint(), ее назначение — уведомление клиента BOINC о завершении сохранения состояния программы.
13int boinc_finish(int status)Функция должна быть вызвана по завершении программы. Ненулевой статус говорит об ошибке.

3. Заключение

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

Как видите, нет ничего сложного в создании приложений, способных использовать распределенную структуру вычислителей. Всю основную часть работы берет на себя платформа BOINC: на серверной части она распределяет задания между клиентами, а клиент запускает задания на выполнение и отчитывается перед сервером (а попутно еще и отображает пользователю ход выполнения работы). Однако до сих пор наш клиент BOINC выполнял одно-единственное задание. Как же сделать большое количество заданий? В следующей статье будет рассказано о том, как автоматизировать процесс генерации рабочих заданий для приложения.

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=776816
ArticleTitle=Распределенные вычисления: Часть 6. Приложения BOINC
publish-date=11242011