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

1. Введение

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

В этой статье мы рассмотрим тестовый пример и обсудим базовую структуру приложения 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

Основной код приложения содержится в файле 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 самостоятельно свяжется с сервером, передаст ему результаты работы и запросит новое задание.

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

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

FIXME(вставить рисунок в виде таблицы)

*API взаимодействия - для платформы BOINC разработчики предоставляют документированные интерфейсы API, предназначенные для взаимодействия с приложениями и web-сайтами. Возможности, предоставляемые этими интерфейсами, включают в себя: * локальный и удаленный контроль графического интерфейса клиента BOINC; * предоставление информации о набранных кредитах (в целом по проекту, для отдельного пользователя, команды или страны); * управление учетными записями нескольких проектов через единый web-интерфейс; * суммарная статистика по кредитам для хостов, команд и пользователей, участвующих в нескольких проектах; * механизмы, позволяющие локальным программам редактировать настройки клиента BOINC. Подробную информацию по каждому типу API можно найти по ссылкам на странице http://boinc.berkeley.edu/trac/wiki/SoftwareAddon.

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

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

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


Статья взята с http://www.ibm.com

 
ru/chast_6.txt · Последние изменения: 2011/12/11 13:37 (внешнее изменение)
 
За исключением случаев, когда указано иное, содержимое этой вики предоставляется на условиях следующей лицензии: CC Attribution-Noncommercial-Share Alike 4.0 International
Recent changes RSS feed Driven by DokuWiki