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