На этом шаге мы рассмотрим алгоритмы.
В библиотеке Thrust реализованы следующие алгоритмы:
Приведем примеры, демонстрирующие возможности алгоритмов библиотеки Thrust.
#include <thrust/device_vector.h> #include <thrust/transform.h> #include <thrust/sequence.h> #include <thrust/copy.h> #include <thrust/fill.h> #include <thrust/replace.h> #include <thrust/functional.h> #include <iostream> int main(void){ //Создаем 3 вектора в памяти видеокарты thrust::device_vector<int> X(10); thrust::device_vector<int> Y(10); thrust::device_vector<int> Z(10); //Заполняем вектор числами 0, 1, 2, и т.д. thrust::sequence(X.begin(), X.end()); //Помещаем в вектор Y элементы вектора X, умноженные на -1 thrust::transform(X.begin(), X.end(), Y.begin(), thrust::negate<int>()); //Заполняем вектор Z значением равным 2 thrust::fill(Z.begin(), Z.end(), 2); //Умножаем каждый элемент вектора X на соответсвующий ему элемент вектора Z //и результат помещаем в вектор Y thrust::transform(X.begin(),X.end(),Z.begin(),Y.begin(),thrust::modulus<int>()); //Изменяем все элементы вектора Y, равные 1, на значение 10 thrust::replace(Y.begin(), Y.end(), 1, 10); //Выводим содержимое вектора Y на экран, //используя функцию копирования и потоковый итератор thrust::copy(Y.begin(),Y.end(),std::ostream_iterator<int>( std::cout,"\n")); return 0; } /* Результат выполнения программы: 0 10 0 10 0 10 0 10 0 10 */
Полный текст приложения можно взять здесь.
Опишем каждую из функций.
#include <thrust/device_vector.h> #include <thrust/reduce.h> #include <thrust/functional.h> #include <cstdio> using namespace std; const int N = 512 * 512 * 512; int main(){ //Создаем вектор из N элементов и инициализируем его единицами thrust::device_vector<float> X(N, 1); //Посчитаем паралельно сумму всех элементов, используя функцию reduce float result = thrust::reduce(X.begin(), X.end()); //Выведем на экран результат printf("%.10f", result); return 0; } /* Результат выполнения программы: 134217728.0000000000 */
Полный текст приложения можно взять здесь.
Параллельная редукция является одной из часто встречающихся операций над массивами. В общем случае данная операция формулируется следующим образом:
Пусть заданы массив a0, a1, a2, ... , an-1 и некоторая бинарная ассоциативная операция. В качестве такой операции мы рассматрели операцию сложения, однако данная операция может быть умножением, минимумом или максимум из двух чисел и т.п.
Тогда редукцией массива a0, a1, a2, ... , an-1 относительно операции сложения будет следующая величина (фактически это просто сумма всех элементов массива): A = ((...(a0 + a1) + ... + an-2) + an-1.
Данная фунция reduce принимает 2 параметра (на самом деле она может иметь больше параметров, смотри документацию):
По умолчанию используется операция суммирования, но можно в качестве операции (3-й параметр) указать, например, операцию взятия максимума.
#include <thrust/scan.h> #include <cstdio> int main(){ //Создадим два массива int data1[6] = {1, 0, 2, 2, 1, 3}; int data2[6] = {1, 0, 2, 2, 1, 3}; //Посчитаем префикс - сумму данных массивов thrust::inclusive_scan(data1, data1 + 6, data1); thrust::exclusive_scan(data2, data2 + 6, data2); //Выведем на экран содержимое первого массива for (int i = 0; i < 6; ++i){ printf("%d ", data1[i]); } printf("\n"); //Выведем на экран содержимое второго массива for (int i = 0; i < 6; ++i){ printf("%d ", data2[i]); } printf("\n"); return 0; } /* Результат выполнения программы: 1 1 3 5 6 9 0 1 1 3 5 6 */
Полный текст приложения можно взять здесь.
Различие между функциями inclusive_scan и exclusive_scan заключается только в том, что в первом случае результат будет размещаться с 0-го элемента, а во втором случае - с 1-го, при этом в 0-й элемент будет записано число 0.
#include <thrust/sort.h> #include <cstdio> const int N = 6; //Создадим массив из N элементов int a[N] = {1, 4, 2, 8, 5, 7}; //Функция вывода массива на экран void PRINT( ){ for(int i = 0; i < N; ++i){ printf("%d ", a[i]); } printf("\n"); } int main(){ //Отсортируем массив и выведем его содержимое на экран thrust::sort(a, a + N); printf("sort a: "); PRINT(); //Отсортируем элементы массива в обратном порядке и выведем его содержимое на экран thrust::stable_sort(a, a + N, thrust::greater<int>()); printf("\nstable_sort a: "); PRINT(); int keys[N] = {1, 4, 2, 8, 5, 7}; char values[N] = {'a', 'b', 'c', 'd', 'e', 'f'}; //Отсортируем массив values, используя ключи - элементы массива keys //т.е. на первом месте в массиве values будет стоять элемент с наименьшим ключем, //на последнем месте - с наибольшим thrust::sort_by_key(keys, keys + N, values); //Выведем содержимое массива values на экран printf("\nsort_by_key values: "); for(int i = 0; i < N; ++i){ printf("%c ", values[i]); } printf("\n"); return 0; } /* Результат выполнения программы: sort a: 1 2 4 5 7 8 stable_sort a: 8 7 5 4 2 1 sort_by_key values: a c b e f d */
Полный текст приложения можно взять здесь.
На следующем шаге мы рассмотрим итераторы.