Инженерная методика адаптации приложения к гибридному кластеру с ускорителями на ПЛИС

Часть 1. Запись работы с многомерными массивами.

С. С. Андреев, С. А. Дбар, А. О. Лацис, Е. А. Плоткина

Нам предстоит писать программу, в которой интенсивно используются многомерные массивы вещественных чисел. В языке C, в отличие, например, от Фортрана, не предусмотрено стандартного, простого и общепринятого способа записи для многих часто используемых действий с многомерными массивами. Например, не вполне очевидно, как можно передать в функцию в качестве аргумента многомерный массив вместе с его размерами, не известными во время компиляции. Разные программисты используют, при работе с многомерными массивами неизвестного заранее размера в программах на C, те или иные персональные ухищрения, которые совсем не делают их программы простыми и понятными. Чтобы не усложнять самим себе жизнь утомительными второстепенными деталями, условимся раз и навсегда о том, как именно мы будем далее записывать работу с многомерными массивами. Предлагаемый способ основан на возможностях языка C99, которые поддерживаются всеми известными авторам компиляторами C.

Как известно, многомерный массив в C – это одномерный массив массивов на единицу меньшей размерности. Например, двумерный массив, объявленный как double da[100][30] — это, в действительности, одномерный массив длиной 100, элементами которого являются строки - одномерные массивы вещественных чисел длиной 30.

Допустим, мы не знаем в момент написания программы, какой именно длины массив строк нам потребуется. Тогда нам придется объявить указатель на тип данных, элемент которых — это строка, то есть массив вещественных чисел длиной 30. Это делается не совсем очевидным способом. Запись:

double (*da)[30]
объявляет da как указатель на строки длиной по 30 вещественных чисел.

Теперь мы можем выделить память обращением к malloc(), присвоить адрес выделенной памяти указателю da, а затем индексировать его как двумерный массив обычным порядком:

    da = (typeof(da))malloc( m*sizeof(*da));
    ..............
    if ( (i < m) && (j < 30) ) f = da[i][j];
    ..............

Чтобы не путаться впредь со скобками и звездочками в объявлении указателей вроде da, напишем впрок макросы объявления указателей на массивы для наиболее употребительных размерностей, поместим их в файле dimension.h, и будем использовать при необходимости:

#define DIM2( basetype, name, w1 ) basetype (*name)[w1]
#define DIM3( basetype, name, w1, w2 ) basetype (*name)[w1][w2]
#define DIM4( basetype, name, w1, w2, w3 ) basetype (*name)[w1][w2][w3]
#define DIM5( basetype, name, w1, w2, w3, w4 ) basetype (*name)[w1][w2][w3][w4]
.........
и так далее до DIM7

Таким образом, макрос DIM2 служит для объявления указателей на строки двумерного массива, как мы продемонстрировали только что, макрос DIM3 – для объявления указателей на слои трехмерного массива, и т. д. Вместо приведенной выше записи

    double (*da)[30]
мы теперь можем написать, не рискуя по ошибке поместить звездочку вне скобок:
    DIM2( double, da, 30 )

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

int main( int argc, char *argv[] )
{
    .........
    m = 10000;
    n = 5000;
    {
// Мы открыли новый блок, чтобы в пределах блока объявления 
// располагались, как положено, до исполняемых операторов:
       DIM2( double, da, n );
       da = (typeof(da))malloc( m*sizeof(*da) );
       ..........
       if ( (i < m) && (j < n) ) f = da[i][j];
       ..........

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

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

void myfunc( void *pda, int m, int n )
{
    DIM2( double, da, n ) = (typeof(da))pda;
    ..........
    if ( (i < m) && (j<n) ) f = da[i][j];
    ..........

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

Более подробный разбор предложенных здесь правил обращения с многомерными массивами можно найти в «Десяти простых шагах», в Шаге 2.

◄ Введение Часть 2 ►
 
 
 
 
 
 
 
 
  Тел. +7(499)220-79-72; E-mail: inform@kiam.ru