Опубликован: 19.10.2012 | Доступ: свободный | Студентов: 299 / 65 | Длительность: 05:51:00
Лекция 4:

Элементные функции. Функции прикладного программного интерфейса

Операции приведения (редукции)

Операция редукции применяется к сечению массива. Её результат – скалярное значение.

Примеры:

__sec_reduce(f, a[:])
__sec_reduce_add(a[:])

Базовые типы C поддерживаются следующими операциями редукции:

add
mul
max
max_ind
min
min_ind
all_zero
all_non_zero
any_nonzero

Есть возможность определить пользовательскую операцию приведения:

type fn(type in1, type in2);
out = __sec_reduce(fn, identity_value, in[x:y:z]);

Пример использования пользовательской операции редукции

#include <iostream>

unsigned int bitwise_and(unsigned int x, unsigned int y) {
   return (x & y);
}

int main() {
   unsigned int a[4] = {5,7,13,15};
   unsigned int b = 0;

   std::cout << "Display a:\n" << a[:] << " ";
   std::cout << std::endl << std::endl;

   b = __sec_reduce(bitwise_and, 0xffffffff, a[:]);

   std::cout << "b:\n" << b << std::endl;
   return(0);
}

Примеры применения (по предметным областям):

  • матричные операции;
  • обработка изображений;
  • вычисление средних при моделировании методом Монте- Карло.

Дополнительные примеры использования операции редукции

float sum = __sec_reduce_add(a[i:n]);


#pragma simd reduction(+:sum)
float sum=0;
for( int i=0; i<n; ++i )
    sum += a[i];

cilk::reducer_opadd<float> sum = 0;
cilk_for( int i=0; i<n; ++i )
    sum += a[i];
... = sum.get_value();

Пример использования операции редукции в Intel® Threading Building Blocks

enumerable_thread_specific<float> sum;
parallel_for( 0, n, [&]( int i ) {
    sum.local() += a[i];
});
... = sum.combine(std::plus<float>());

sum = parallel_reduce( 
    blocked_range<int>(0,n), 
    0.f,
    [&](blocked_range<int> r, float s) -> float 
    {
        for( int i=r.begin(); i!=r.end(); ++i )
            s +=  a[i];
        return s;
    },
    std::plus<float>()

Функции прикладного программного интерфейса

Функции ППИ позволяют управлять поведением программы.

Функции прикладного программного интерфейса (ППИ) используются с заголовочным файлом cilk/cilk_api.h

int __cilkrts_set_param(const char* name, const char* value);

Эта функция используется для управления некоторыми параметрами системы исполнения Cilk.

Первые 2 параметра строковые.

nworkersзначение определяет количество исполнителей. Если данная функция не используется, количество исполнителей задаётся с помощью переменной окружения CILK_NWORKERS или, по умолчанию, оно равно количеству ядер.

Данная функция действует только до первого использования cilk_spawn или cilk_for.

int __cilkrts_get_nworkers(void);

Эта функция возвращает количество потоков-исполнителей и фиксирует его так, что оно не может быть изменено вызовом функции __cilkrts_set_param.

пользуется для управления некоторыми параметрами системы исполнения Cilk.

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

int __cilkrts_get_worker_number(void);

Эта функция возвращает целое значение, показывающее исполнителя, который выполняет функцию.

int __cilkrts_get_total_workers(void);

Эта функция возвращает суммарное количество потоков-исполнителей, включая неактивные.

Несколько советов по повышению производительности

Оптимизируйте в первую очередь последовательный код.

Выбор зернистости:

  • избегайте порождения маленьких задач;
  • оптимизируйте зернистость параллельных циклов;
  • мелкозернистая декомпозиция => большие накладные расходы;
  • крупнозернистая декомпозиция => низкий параллелизм, неэффективное использование возможностей вычислительной системы.

Оптимизируйте кэш-эффективность.

Пример false-sharing:

volatile int x[32];
void f(volatile int *p)
{
for (int i = 0; i < 100000000; i++)
{
++p[0];
++p[16];
}
}
int main()
{
cilk_spawn f(&x[0]);
cilk_spawn f(&x[1]);
cilk_spawn f(&x[2]);
cilk_spawn f(&x[3]);
cilk_sync;
return 0;
}