Элементные функции. Функции прикладного программного интерфейса
Операции приведения (редукции)
Операция редукции применяется к сечению массива. Её результат – скалярное значение.
Примеры:
__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;
}

