Программирование DSP VisualDSP: квалификаторы банка Mon, October 21 2019  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

VisualDSP: квалификаторы банка Печать
Добавил(а) microsin   

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

Квалификатор банка может bank быть подсоединен к декларациям данных, чтобы показать, что данные находятся в определенном банке памяти, например:

int bank("blue")  *ptr1;
int bank("green") *ptr2;

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

Для любой функции 3 банка определяются автоматически. Это следующие банки:

• Банк по умолчанию для глобальных данных. Подразумевается, что данные "static" или "extern", которые не помещены явно в другой банк, находятся в этом банке. Обычно этот банк называется "__data", хотя может быть специально выбрано другое имя директивой #pragma data_bank(имябанка).
• Банк по умолчанию для локальных данных. Подразумевается, что локальные переменные класса хранилища "auto", которые не помещены явно в другой банк, находятся в этом банке. Обычно этот банк называется "__stack", хотя может быть специально выбрано другое имя директивой #pragma stack_bank(имябанка).
• Банк по умолчанию для инструкций функции. В этот банк помещается сама функция. Обычно этот банк называется "__code", хотя может быть выбран другой банк директивой #pragma code_bank(bankname).

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

[Директивы банка памяти]

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

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

Обратите внимание, что банки памяти отличаются от секций памяти: 

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

#pragma code_bank(bankname)

Прагма code_bank информирует компилятор, что инструкции функции, которая идет сразу за этой прагмой, будут помещены в банк с именем bankname. Без этой прагмы компилятор подразумевает, что инструкции этой функции помещаются в банк с именем "__code". При оптимизации функции компилятор знает об атрибутах банка памяти bankname и определяет, сколько времени будет занимать выборка каждой инструкции из банка памяти.

В следующем примере функция add_slowly() размещается в банк "slowmem", который может иметь характеристики производительности, отличающиеся от банка "__code", в который помещается функция add_quickly().

#pragma code_bank(slowmem)
int add_slowly (int x, int y) { return x + y; }
int add_quickly(int a, int b) { return a + b; }

#pragma data_bank(bankname)

Прагма data_bank информирует компилятор, что функция, которая сразу идет за ней, использует банк памяти bankname в качестве модели для доступа к не локальным данным, которые иначе не определяют банк памяти. Без этой прагмы компилятор подразумевает, что не локальные данные должны использовать банк "__data" для характеристик поведения. Прагма data_bank действует только на одну функцию, которая идет сразу за ней, но не на следующие функции. Т. е. для каждой функции, если необходимо указать для неё специальный банк памяти данных по умолчанию, должна предшествовать прагма data_bank.

В следующем примере определены две функции green_func() и blue_func(), в обоих используется внешняя переменная i, связанная с банком памяти "blue". Извлечение и обновление переменной i оптимизируется на использование характеристик производительности, связанных с банком памяти "blue".

// У функции green_func по умолчанию используется
// банк памяти данных "green":
#pragma data_bank(green)
int green_func(void)
{
   extern int arr1[32];
   extern int bank("blue") i;
   i &= 31;
   return arr1[i++];
}
 
// У функции blue_func по умолчанию используется
// банк памяти данных "__data", потому что перед
// blue_func нет прагмы data_bank:
int blue_func(void)
{
   extern int arr2[32];
   extern int bank("blue") i;
   i &= 31;
   return arr2[i++];
}

Массив arr1 в своей декларации не имеет явного указания на банк памяти. Таким образом, он связан с банком памяти "green", потому что в функции green_func() есть указание на определенный банк данных по умолчанию. Массив arr2, в отличие от arr1, связан с банком памяти "__data", потому что перед функцией blue_func() нет #pragma data_bank.

#pragma stack_bank(bankname)

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

В следующем примере функция dotprod() помещает значения переменных sum и i в банк памяти "mystack", в то время как функция fib() помещает переменные r, a и b в банк памяти "__stack", потому что перед функцией fib() нет прагмы stack_bank. Функция count_ticks() не декларирует никакие локальные данные, однако любые генерируемые компилятором локальные переменные будут использовать характеристики производительности банка памяти "sysstack".

#pragma stack_bank(mystack)
short dotprod(int n, const short *x, const short *y)
{
   int sum = 0;
   int i = 0;
   for (i = 0; i < n; i++)
      sum += *x++ * *y++;
   return sum;
}
 
int fib(int n)
{
   int r;
   if (n < 2)
   {
      r = 1;
   }
   else
   {
      int a = fib(n-1);
      int b = fib(n-2);
      r = a + b;
   }
   return r;
}
 
#include < sys/exception.h>
#pragma stack_bank(sysstack)
EX_INTERRUPT_HANDLER(count_ticks)
{
   extern int ticks;
   ticks++;
}

#pragma bank_memory_kind(bankname, kind)

Прагма bank_memory_kind информирует компилятор, к какому виду памяти относится банк памяти bankname. Компилятор позволяет использовать следующие виды памяти, соответствующие разным классам производительности (скорости доступа):

• Internal - банк памяти с самым высоким быстродействием (память L1, доступная для ядра без тактов задержки).
• L2 - банк памяти, который находится на кристалле процессора, однако эта память не относится непосредственно к вычислительному ядру (т. е. доступ к ней более дорогой, чем к памяти L1).
• External - банк памяти, которая является внешней по отношению к процессору (обычно это SDRAM L3).

Эта прагма должна появляться в глобальной области кода, вне определений какой-либо функции, однако она не должна непосредственно предшествовать определению функции.

В следующем примере компилятор знает о том, что все доступы к массиву data[] осуществляются к банку памяти "blue", который относится к внутренней памяти процессора (самая быстрая память).

#pragma bank_memory_kind(blue, internal)
int sum_list(const int bank("blue") *data, int n)
{
   int sum = 0;
   while (n--)
      sum += data[n];
   return sum;
}

#pragma bank_read_cycles(bankname, cycles)

Прагма bank_read_cycles говорит компилятору, что каждая операция чтения банка памяти bankname требует cycles тактов для того, чтобы данные стали доступны. Это позволяет компилятору планировать достаточный код между инициацией чтения и использованием его результатов, чтобы предотвратить ненужные простои ядра.

В следующем примере компилятор подразумевает, что чтение из *x займет один такт ядра, поскольку это время чтения по умолчанию, однако чтение из *y займет двадцать тактов, поскольку так определено в прагме bank_read_cycles.

#pragma bank_read_cycles(slowmem, 20)
int dotprod(int n, const int *x, bank("slowmem") const int *y)
{
   int i, sum;
   for (i=sum=0; i < n; i++)
      sum += *x++ * *y++;
   return sum;
}

Прагма bank_read_cycles должна появляться в глобальной области действия, вне каких-либо определений функций, однако она не должна непосредственно предшествовать определению функции.

#pragma bank_write_cycles(bankname, cycles)

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

В следующем примере компилятор знает, что каждая запись по указателю ptr осуществляется в банк памяти "output", который для завершения записи требует 6 тактов ядра процессора.

void write_buf(int n, const char *buf)
{
   volatile bank("output") char *ptr = REG_ADDR;
   while (n--)
      *ptr = *buf++;
}
 
#pragma bank_write_cycles(output, 6)

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

#pragma bank_optimal_width(bankname, width)

Прагма bank_optimal_width информирует компилятор, что width это оптимальное количество бит для передачи данных в память и из памяти банка bankname за 1 цикл. Это может использоваться, чтобы показать компилятору, что некоторые области памяти могут иметь извлечь выгоду из векторизации подобных стратегий разрядности, по сравнению с другими. Параметр width должен быть 8, 16, 24 или 32.

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

void memcpy_simple(char *dst, const char *src, size_t n)
{
   while (n--)
      *dst++ = *src++;
}
 
#pragma bank_optimal_width(__code, 16)

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

[Ссылки]

1. VisualDSP: полезные директивы #pragma.
2. VisualDSP: управление оптимизацией кода.
3. VisualDSP: использование секций памяти.

 

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


Защитный код
Обновить

Top of Page