IBM®
Перейти к тексту
    в России и странах СНГ [изменить]    Условия использования
 
 
   
    Главная страница    Продукты    Услуги и решения    Поддержка и загрузка    Мой профиль    
Перейти к тексту

developerWorks Россия  >  Open source | Linux  >

Инфраструктура библиотек ускоренных вычислений для Cell Broadband Engine. Руководство программиста и справочник по функциям: Часть 5. Приложения

Software Development Kit for Multicore Acceleration (Инструментарий разработчика для ускоренных многоядерных вычислений)

developerWorks
На предыдущую страницуСтраница 2 из 11 На предыдущую страницу

Опции документа

Обсудить


Выскажите мнение об этом учебном пособии

Помогите нам улучшить содержание


Приложение B. Примеры

В данном разделе описываются следующие примеры:

  • «Сложение матриц — пример распределения данных на основном процессоре»
  • «Сложение матриц — пример распределения данных на акселераторе», стр. 126
  • «Пример табличного преобразования», стр. 126
  • «Пример поиска минимума и максимума», стр. 128
  • «Скалярное произведение векторов», стр. 130
  • «Пример использования совмещенного буфера ввода-вывода», стр. 133
  • «Пример использования зависимости заданий», стр. 135

Базовые примеры

В данном разделе описаны следующие базовые примеры:

  • «Сложение матриц — пример распределения данных на основном процессоре». Данный пример включает исходный текст.
  • «Сложение матриц — пример распределения данных на акселераторе», стр. 126

Сложение матриц — пример распределения данных на основном процессоре

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

A[m,n] + B[m,n] = C[m,n],

где m и n — размерности матриц. Этот простой пример демонстрирует, как:

  • запустить среду исполнения ALF;
  • работать с дескриптором задания;
  • запустить задание на акселераторах;
  • создать рабочий блок и добавить его в очередь задания;
  • корректно завершить работу со средой исполнения ALF.

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

В данном примере программа, выполняемая на основном процессоре:

  • инициализирует среду исполнения ALF;
  • создает дескриптор задания;
  • создает задание на основе этого дескриптора;
  • создает рабочие блоки вместе с соответствующими списками пересылки данных. Данные блоки инициируют вызов вычислительного ядра на акселераторе;
  • ожидает завершения работы вычислительного ядра и завершает счет.

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

Ниже приведена скалярная программа для сложения двух матриц на машине с универсальным процессором.

float mat_a[NUM_ROW][NUM_COL]; float mat_b[NUM_ROW][NUM_COL]; 
			float mat_c[NUM_ROW][NUM_COL]; int main(void)
{
int i,j;
for (i=0; i<NUM_ROW; i++)
for (j=0; j<NUM_COL; j++)
mat_c[i][j] = mat_a[i][j] + mat_b[i][j];
return 0;
}

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

  • Инициализация
  • Подготовка задания
  • Подготовка рабочих блоков
  • Ожидание завершения работы и выход


В начало


Исходный текст

В данном листинге приведены лишь отдельные фрагменты кода, соответствующие названным этапам. Полный листинг можно найти в каталоге примеров программирования для ALF по адресу:

matrix_add/STEP1a_partition_scheme_A/common/host_partition



В начало


Инициализация

Следующий фрагмент программы выполняет инициализацию среды исполнения ALF и выделение ресурсов акселератора для конкретной реализации ALF.

alf_handle_t alf_handle;
unsigned int nodes;
/* инициализация среды исполнения ALF*/
alf_init(&config_parms, &alf_handle;);
/* получение числа сопроцессоров-акселераторов, доступных Opteron */
rc = alf_query_system_info(alf_handle, ALF_QUERY_NUM_ACCEL, ALF_ACCEL_TYPE_SPE, &nodes;);
/* установка общего количества экземпляров для акселераторов
			(в данном случае сопроцессоров), */
/* доступных среде исполнения ALF в процессе ее работы */
rc = alf_num_instances_set (alf_handle, nodes);



В начало


Подготовка задания

Следующий раздел программы для основного процессора содержит информацию, описывающую задание и необходимую для подготовки среды его исполнения. Функция alf_task_desc_create создает дескриптор задания. Этот дескриптор можно использовать неоднократно для создания различных исполняемых заданий. Функция alf_task_create создает задание, чтобы запустить программу для сопроцессора под именем spe_add_program.

/* объявления переменных */ 
			alf_task_desc_handle_t task_desc_handle; 
			alf_task_handle_t task_handle;
const char* spe_image_name; const char* library_path_name; const char* comp_kernel_name;

/* описание задания — исполняемого файла для сопроцессора */ 
			alf_task_desc_create(alf_handle, 
			ALF_ACCEL_TYPE_SPE, &task_desc_handle;); 
			alf_task_desc_set_int32(task_desc_handle, 
			ALF_TASK_DESC_TSK_CTX_SIZE, 0);
alf_task_desc_set_int32(task_desc_handle, 
			ALF_TASK_DESC_WB_PARM_CTX_BUF_SIZE, sizeof(add_parms_t)); 
			alf_task_desc_set_int32(task_desc_handle, 
			ALF_TASK_DESC_WB_IN_BUF_SIZE, H * V * 2 sizeof(float)); 
			alf_task_desc_set_int32(task_desc_handle, 
			ALF_TASK_DESC_WB_OUT_BUF_SIZE, H * V * sizeof(float)); 
			alf_task_desc_set_int32(task_desc_handle, 
			ALF_TASK_DESC_NUM_DTL_ENTRIES, 8); 
			alf_task_desc_set_int32(task_desc_handle, 
			ALF_TASK_DESC_MAX_STACK_SIZE, 4096);
/* имя исполняемого файла для сопроцессора */
alf_task_desc_set_int64(task_desc_handle, 
			ALF_TASK_DESC_ACCEL_IMAGE_REF_L,
			(unsigned long long) spe_image_name);
alf_task_desc_set_int64(task_desc_handle, 
			ALF_TASK_DESC_ACCEL_LIBRARY_REF_L,
			(unsigned long) library_path_name);
alf_task_desc_set_int64(task_desc_handle, 
			ALF_TASK_DESC_ACCEL_KERNEL_REF_L,
			(unsigned long) comp_kernel_name);



В начало


Подготовка рабочих блоков

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

Каждый рабочий блок содержит описание ввода для участков входных матриц размером H * V, начиная с позиции matrix[row][0], где H и V представляют горизонтальное и вертикальное измерения участка.

В данном примере предполагается, что память акселератора может содержать два входных буфера размером H * V элементов и выходной буфер также размером H * V. Программа вызывает функцию alf_wb_enqueue(), чтобы поставить рабочий блок в очередь на обработку. Среда исполнения ALF действует в режиме немедленного исполнения. Как только в очередь помещен первый рабочий блок, начинается его обработка. Функция alf_task_finalize закрывает очередь рабочих блоков.

alf_wb_handle_t wb_handle;
add_parms_t parm attribute ((aligned(128)));
parm.h = H; /* горизонтальный размер участка */
parm.v = V; /* вертикальный размер участка */
/* создание рабочих блоков, добавление буферов параметров и ввода вывода */
for (i = 0; i < NUM_ROW; i += H) {
alf_wb_create(task_handle, ALF_WB_SINGLE, 0,&wb_handle);
/* создание нового списка пересылки данных для ВВОДА */
alf_wb_dtl_set_begin(wb_handle, ALF_BUF_IN, 0);
/* добавление элемента H*V матрицы mat_a в качестве входных данных */
alf_wb_dtl_set_entry_add(wb_handle, &matrix_a[i][0], H * V, ALF_DATA_FLOAT);
/* добавление элемента H*V матрицы mat_b в качестве входных данных */
alf_wb_dtl_set_entry_add(wb_handle, &matrix_b[i][0], H * V, ALF_DATA_FLOAT);
alf_wb_dtl_set_end(wb_handle);
/* создание нового списка пересылки данных для ВЫВОДА */
alf_wb_dtl_set_begin(wb_handle, ALF_BUF_OUT, 0);
/* добавление элемента H*V матрицы mat_c в качестве выходных данных */
alf_wb_dtl_set_entry_add(wb_handle, &matrix_c[i][0], H * V, ALF_DATA_FLOAT);
alf_wb_dtl_set_end(wb_handle);
/* передача параметров H и V сопроцессору */
alf_wb_parm_add(wb_handle, (void *) (&parm), sizeof(parm), ALF_DATA_BYTE, 0);
/* помещение рабочего блока в очередь */
alf_wb_enqueue(wb_handle);
}
alf_task_finalize(task_handle);



В начало


Ожидание завершения задания и выход

После того как все рабочие блоки помещены в очередь, программа ожидает, пока акселераторы завершат обработку этих блоков. Затем вызывается функция alf_exit(), обеспечивающая корректное завершение работы среды исполнения ALF.

/* ожидание, пока все рабочие блоки будут обработаны */
alf_task_wait(task_handle, -1);
/* завершение работы среды исполнения ALF */
alf_exit(alf_handle, ALF_EXIT_WAIT, -1);



В начало


Программа для акселератора

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

int alf_accel_comp_kernel(void *p_task_context,
void *p_parm_context,
void *p_input_buffer,
void *p_output_buffer,
void *p_inout_buffer,
unsigned int current_count,
unsigned int total_count)
{
unsigned int i, cnt;
vector float *sa, *sb, *sc;
add_parms_t *p_parm = (add_parms_t *)p_parm_context;
cnt = p_parm->h * p_parm->v / 4;
sa = (vector float *) p_input_buffer;
sb = sa + cnt;
sc = (vector float *) p_output_buffer;
for (i = 0; i < cnt; i += 4) {
sc[i] = spu_add(sa[i], sb[i]);
sc[i + 1] = spu_add(sa[i + 1], sb[i + 1]);
sc[i + 2] = spu_add(sa[i + 2], sb[i + 2]);
sc[i + 3] = spu_add(sa[i + 3], sb[i + 3]);
}
return 0;
}



В начало


Сложение матриц — пример распределения данных на акселераторе

В данном примере решается задача, аналогичная задаче «Сложение матриц — пример распределения данных на основном процессоре», стр. 123: сложение двух больших матриц с использованием ALF.

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

Требуется также описание функций alf_accel_input_dtl_prepare и alf_accel_output_dtl_prepare.

Полный текст программы для данного примера приведен в каталоге примеров программирования ALF по адресу:

matrix_add/common/accel_partitioning

В начало


Примеры работы с контекстом задания

В данном разделе описаны следующие примеры работы с контекстом задания:

  • «Пример табличного преобразования»
  • «Пример поиска минимума и максимума», стр. 128
  • «Скалярное произведение векторов», стр. 130
  • «Пример использования совмещенного буфера ввода-вывода», стр. 133


В начало


Пример табличного преобразования

В данном примере иллюстрируется использование буфера контекста задания в качестве большой таблицы преобразования для конвертации 16-разрядных входных значений в 8-разрядные.

Определяется следующая таблица преобразования, содержащая 65536 записей

For all –32768 <= in <32768
	/ 0,	in < –4096
Table(n)=	| in/32	–4096 <=in<4096
	\ 255,	in >= 4096

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



В начало


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

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

The my_wb_parms_t data structure represents the parameter and context data for each work block.

/* ---------------------------------------------- */
/* структуры данных, используемые совместно основным процессором и акселератором */
/* ---------------------------------------------- */
typedef struct _my_task_context_t
{
alf_data_byte_t table[65536];
} my_task_context_t;
typedef struct _my_wb_parms_t
{
alf_data_uint32_t num_data; /* количество элементов данных в данном РБ */
} my_wb_parms_t;



В начало


Настройка дескриптора задания

Следующий фрагмент кода иллюстрирует процесс настройки дескриптора задания для данного приложения. Информация в тексте программы, касающаяся контекста задания, выделена полужирным шрифтом.

alf_task_desc_create(alf_handle, 0, &task_desc_handle;);
/* настройка дескриптора задания	.	. */
/* имя вычислительного ядра */
alf_task_desc_set_int64(task_desc_handle,
ALF_TASK_DESC_ACCEL_KERNEL_REF_L, "comp_kernel");

/* размер буфера контекста задания */ 
			alf_task_desc_set_int32(task_desc_handle, 
			ALF_TASK_DESC_TSK_CTX_SIZE, sizeof(my_task_context_t));
/* размер буфера параметров рабочего блока */
alf_task_desc_set_int32(task_desc_handle,
ALF_TASK_DESC_WB_PARM_CTX_BUF_SIZE, sizeof(my_wb_parms_t));

/* размер входного буфера */ 
			alf_task_desc_set_int32(task_desc_handle, 
			ALF_TASK_DESC_WB_IN_BUF_SIZE, PART_SIZE*sizeof(alf_data_int16_t));

/* размер выходного буфера */ 
			alf_task_desc_set_int32(task_desc_handle, 
			ALF_TASK_DESC_WB_OUT_BUF_SIZE, PART_SIZE*sizeof(alf_data_byte_t));

/* элемент контекста задания */ 
			alf_task_desc_ctx_entry_add(task_desc_handle, 
			ALF_DATA_BYTE, sizeof(my_task_context_t)/sizeof(alf_data_byte_t));



В начало


Настройка рабочего блока

Следующий фрагмент кода иллюстрирует создание рабочего блока.

/* создание РБ и добавление буфера параметров и ввода-вывода */
for (i = 0; i < NUM_DATA; i += PART_SIZE)
{
alf_wb_create(task_handle, ALF_WB_SINGLE, 0, &wb_handle);
alf_wb_dtl_begin(wb_handle, ALF_BUF_IN, 0); /* ввод */
alf_wb_dtl_entry_add(wb_handle, pcm16_in+i, PART_SIZE, ALF_DATA_INT16);
alf_wb_dtl_end(wb_handle);
alf_wb_dtl_begin(wb_handle, ALF_BUF_OUT, 0); /* вывод */
alf_wb_dtl_entry_add(wb_handle, pcm8_out+i,PART_SIZE, ALF_DATA_BYTE);
alf_wb_dtl_end(wb_handle);
wb_parm.num_data = PART_SIZE;
alf_wb_parm_add(wb_handle, (void *)&wb_parm, /* параметры РБ */
sizeof(wb_parm)/sizeof(unsigned int), ALF_DATA_INT32, 0);
alf_wb_enqueue(wb_handle);
}



В начало


Код для акселератора

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

/* ---------------------------------------------- */
/* код для акселератора */
/* ---------------------------------------------- */
/* функция вычислительного ядра */
int comp_kernel(void *p_task_context, void *p_parm_ctx_buffer,
void *p_input_buffer, void *p_output_buffer,
void *p_inout_buffer, unsigned int current_count,
unsigned int total_count)
{
my_task_context_t *p_ctx = (my_task_context_t *) p_task_context;
my_wb_parms_t *p_parm = (my_wb_parms_t *) p_parm_ctx_buffer;
alf_data_int16_t *in = (alf_data_int16_t *)p_input_buffer;
alf_data_byte_t *out = (alf_data_byte_t *)p_output_buffer;
unsigned int size = p_parm->num_data;
unsigned int i;
// простая табличная подстановка
for(i=0;i<size;i++)
{
out[i] = p_ctx->table[(unsigned short)in[i]];
}
return 0;
}



В начало


Пример поиска минимума и максимума

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

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

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

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


Рис. 12. Пример поиска минимума и максимума
Рис. 12. Пример поиска минимума и максимума


В начало


Исходный текст

Исходный текст программы находится в каталоге примеров по адресу task_context/min_max.



В начало


Вычислительное ядро

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

/* ---------------------------------------------- */
/* код акселератора */
/* ---------------------------------------------- */
/* функция вычислительного ядра */
int comp_kernel(void *p_task_context, void *p_parm_ctx_buffer,
void *p_input_buffer, void *p_output_buffer,
void *p_inout_buffer, unsigned int current_count,
unsigned int total_count)
{
my_task_context_t *p_ctx = (my_task_context_t *) p_task_context;
my_wb_parms_t *p_parm = (my_wb_parms_t *) p_parm_ctx_buffer;
alf_data_int32_t *a = (alf_data_int32_t *)p_input_buffer;
unsigned int size = p_parm->num_data;
unsigned int i;
/* помещение подходящих значений в буфер контекста */
for(i=0;i<size;i++) {
if(a[i]>p_ctx->max)
p_ctx->max = a[i];
else if(a[i]<p_ctx->min)
p_ctx->min = a[i];
} return 0;
}



В начало


Объединение контекстов задания

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

/* Функция объединения контектсов */
int ctx_merge(void* p_task_context_to_be_merged,
void* p_task_context)
{
my_task_context_t *p_ctx = (my_task_context_t *) p_task_context;
my_task_context_t *p_mgr_ctx = (my_task_context_t *)
p_task_context_to_be_merged;
if(p_mgr_ctx->max > p_ctx->max)
p_ctx->max = p_mgr_ctx->max;
if(p_mgr_ctx->min < p_ctx->min)
p_ctx->min = p_mgr_ctx->min;
return 0;
}



В начало


Скалярное произведение нескольких векторов

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

Даны два списка векторов A = {A1, A2, A3, ..., Am} и B = [B1, B2, B3, ..., Bm}, где Ai и Bi являются векторами размерностью N;

Вычислить C = (c1 c2, c3, ..., cm}, где ci = Ai•Bi.

Операция скалярного умножения, обозначаемая знаком "•", по отношению к двум N-мерным векторам A и B определяется как A•B = где ai и bi являются элементами векторов A и B соответственно.

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

Если же вектор слишком велик, чтобы уместиться в один рабочий блок, метод «лобового» решения не подходит. Например, сопроцессоры системы Cell BE оснащены всего 256 Кбайт локальной памяти, которая не позволяет хранить пару векторов из чисел двойной точности, если их размерность превышает 16384. Если же часть памяти будет занята для нужд двойной буферизации, хранения кода и т. д., место останется только векторов с 7500 элементами двойной точности (7500*8[размер числа двойной точности]*2[два вектора] * 2[двойная буферизация] ≈ 240 Кбайт локальной памяти). В данном случае большие векторы следует разделить на ряд рабочих блоков, и каждый блок вернет лишь промежуточный результат скалярного умножения.

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

ALF позволяет решить эту задачу двумя способами:

  • «Способ 1: Использование контекста задания и распределения пакетированных рабочих блоков»
  • «Способ 2: Применение рабочих блоков многократного использования в сочетании с контекстом задания или буферами параметров/контекста рабочих блоков», стр. 132 (в данном случае вступает в силу требование распределения данных на акселераторах)


В начало


Исходный текст

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

  • task_context/dot_prod directory: Способ 1. Контекст задания и распределение пакетированных рабочих блоков
  • task_context/dot_prod_multi directory: Способ 2. Рабочие блоки многократного использования в сочетании с контекстом задания или буферами параметров/контекста рабочих блоков


В начало


Способ 1: Использование контекста задания и распределения пакетированных рабочих блоков

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

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

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


Рис. 13. Использование контекста задания и распределения пакетированных рабочих блоков
Рис. 13. Использование контекста задания и распределения пакетированных рабочих блоков


В начало


Способ 2: Применение рабочих блоков многократного использования в сочетании с контекстом задания или буферами параметров/контекста рабочих блоков

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

Оба способа, как с применением контекста задания, так и рабочих блоков многократного использования, равно применимы.


Рис. 14. Применение рабочих блоков многократного использования в сочетании с контекстом задания или буферами параметров/контекста рабочих блоков
Рис. 14. Применение рабочих блоков многократного использования в сочетании с контекстом задания или буферами параметров/контекста рабочих блоков


В начало


Пример использования совмещенного буфера ввода-вывода

Следующие два примера иллюстрируют использование совмещенных буферов ввода-вывода. В обоих примерах выполняется сложение матриц.

  • В первом примере вычисляется сумма C=A+B, где A, B и C — различные матрицы. В основной памяти матрицам соответствуют три разных массива a, b и c.
  • Во втором примере выполняется сложение A=A+B, то есть конечный результат записывается в матрицу A. Данные хранятся в основной памяти в массивах a и b. Сумма a+b записывается в массив b.

Рис. 15. Пример использования двух совмещенных буферов ввода-вывода
Рис. 15. Пример использования двух совмещенных буферов ввода-вывода


В начало


Создание массивов

Примечание: программа аналогична приведенной в примере matrix_add (см. «Сложение матриц — пример распределения данных на основном процессоре» на стр. 123. Здесь приведены непосредственно относящиеся к данному примеру фрагменты кода.

/* ---------------------------------------------- */
/* объявление матриц для обоих случаев */
/* ---------------------------------------------- */
#ifdef C_A_B // C = A + B
alf_data_int32_t mat_a[ROW_SIZE][COL_SIZE]; // матрица a
alf_data_int32_t mat_b[ROW_SIZE][COL_SIZE]; // матрица b
alf_data_int32_t mat_c[ROW_SIZE][COL_SIZE]; // матрица c
#else // A = A + B
alf_data_int32_t mat_a[ROW_SIZE][COL_SIZE]; // матрица a
alf_data_int32_t mat_b[ROW_SIZE][COL_SIZE]; // матрица b
#endif



В начало


Настройка рабочего блока

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

for (i = 0; i < ROW_SIZE; i+=PART_SIZE){
if(i+PART_SIZE <= ROW_SIZE)
wb_parm.num_data = PART_SIZE;
else
wb_parm.num_data = ROW_SIZE - i;
alf_wb_create(task_handle, ALF_WB_SINGLE, 0, &wb_handle);
#ifdef C_A_B // C = A + B
// входные данные A и B
alf_wb_dtl_begin(wb_handle, ALF_BUF_OVL_IN, 0); // смещение 0
alf_wb_dtl_entry_add(wb_handle, &mat_a[i][0], 
			wb_parm.num_data*COL_SIZE, ALF_DATA_INT32); // A
alf_wb_dtl_entry_add(wb_handle, &mat_b[i][0], 
			wb_parm.num_data*COL_SIZE, ALF_DATA_INT32); // B
alf_wb_dtl_end(wb_handle);
// выходные данные C переписываются входными данными A
// смещение 0, переписывается A
alf_wb_dtl_begin(wb_handle, ALF_BUF_OVL_OUT, 0);
alf_wb_dtl_entry_add(wb_handle, &mat_c[i][0], 
			wb_parm.num_data*COL_SIZE, ALF_DATA_INT32); // C
alf_wb_dtl_end(wb_handle);
#else // A = A + B
// входные и выходные данные A
alf_wb_dtl_begin(wb_handle, ALF_BUF_OVL_INOUT, 0); // смещение 0
alf_wb_dtl_entry_add(wb_handle, &mat_a[i][0], 
			wb_parm.num_data*COL_SIZE, ALF_DATA_INT32); // A
alf_wb_dtl_end(wb_handle);
// входные данные B помещаются после A
// помещается после A
alf_wb_dtl_begin(wb_handle, ALF_BUF_OVL_IN, 
			wb_parm.num_data*COL_SIZE*sizeof(alf_data_int32_t));
alf_wb_dtl_entry_add(wb_handle, &mat_b[i][0], 
			wb_parm.num_data*COL_SIZE, ALF_DATA_INT32); // B
alf_wb_dtl_end(wb_handle);
#endif
alf_wb_parm_add(wb_handle, (void *)&wb_parm, 
			sizeof(wb_parm)/sizeof(unsigned int), ALF_DATA_INT32, 0);
alf_wb_enqueue(wb_handle);
}



В начало


Код для акселератора

Код для акселератора приведен ниже. В обоих случаях выходная переменная sc может быть помещена в тех же участках памяти акселератора, что и sa и sb.

/* ---------------------------------------------- */
/* Код для акселератора	*/
/* ---------------------------------------------- */
/* функция вычислительного ядра */
int comp_kernel(void *p_task_context, void *p_parm_ctx_buffer,
void *p_input_buffer, void *p_output_buffer,
void *p_inout_buffer, unsigned int current_count,
unsigned int total_count)
{
unsigned int i, cnt;
int *sa, *sb, *sc;
my_wb_parms_t *p_parm = (my_wb_parms_t *) p_parm_context;
cnt = p_parm->num_data * COL_SIZE;
sa = (int *) p_inout_buffer;
sb = sa + cnt;
sc = sa;
for (i = 0; i < cnt; i ++)
sc[i] = sa[i] + sb[i];
return 0;
}



В начало


Пример зависимости заданий

В данном примере проиллюстрировано использование зависимости заданий в приложении, использующем двухступенчатый конвейер. Задача — простое моделирование.

Объект P помещается на середину плоской поверхности, ограниченной прямоугольной коробкой. На каждом этапе моделирования объект перемещается на случайное расстояние в случайно выбранном направлении. При ударе о стенку коробки объект возвращается в исходную позицию. Задача заключается в том, чтобы вычислить число ударов о все четыре стенки в заданный период времени.


Рис. 16. Объект P случайно ударяется о стенку коробки
Рис. 16. Объект P случайно ударяется о стенку коробки

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

  • На первой ступени с помощью генератора псевдослучайных чисел генерируются случайные числа
  • На второй ступени моделируются перемещения объекта

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

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

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

Задание для ступени 1. В задании первой ступени для простоты используется генератор псевдослучайных чисел на основе метода Фибоначчи с запаздыванием. В данном примере применяется следующий алгоритм:

Sn=(Sn-j^Sn-k)%232
где k > n > 0 и k = 71, j = 65

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

Задание для ступени 2: В задании второй ступени контекст задания используется для сохранения текущего состояния модели, включая положение объекта и число ударов о стенки. Рабочий блок в данном случае имеет лишь входные данные, которые являются случайными числами, полученными на ступени 1.

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

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

  • Задание ступени 1 генерирует случайные числа и записывает результаты во временный буфер
  • Задание ступени 2 считывает случайные числа из временного буфера, чтобы выполнить моделирование

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

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

На рис. 17, a изображена зависимость заданий, описанная выше. Чтобы свести к минимуму использование временных промежуточных буферов, можно использовать технику двойной или многократной буферизации. Граф зависимости заданий для двойной буферизации промежуточных буферов показан на рис. 17, б, где новая зависимость добавляется между (n–2)-м заданием ступени 2 и n-м заданием ступени 1, чтобы задание первой ступени не переписывало данных, которые еще могут использоваться предыдущим заданием второй ступени. Данный алгоритм реализован в примере программы.


Рис. 17. Пример использования зависимости заданий
Рис. 17. Пример использования зависимости заданий


В начало


Исходный текст

Полный текст программы помещен в каталоге с примером pipe_line.



В начало



На предыдущую страницуСтраница 2 из 11 На предыдущую страницу
    IBM в России Конфиденциальность Контакты