NVIDIA Thrust教程

news2024/11/15 21:45:25

NVIDIA Thrust教程

Thrust 的 API 参考指南,CUDA C++ 模板库。
在这里插入图片描述

1.简介

Thrust 是基于标准模板库 (STL) 的 CUDA 的 C++ 模板库。 Thrust 允许您通过与 CUDA C 完全互操作的高级接口,以最少的编程工作实现高性能并行应用程序。

Thrust 提供了丰富的数据并行原语集合,例如扫描、排序和归约,它们可以组合在一起,以简洁、可读的源代码实现复杂的算法。 通过用这些高级抽象描述您的计算,您可以让 Thrust 自由地自动选择最有效的实现。 因此,Thrust 可用于 CUDA 应用程序的快速原型设计(其中程序员的生产力最为重要),也可用于生产(其中稳健性和绝对性能至关重要)。

本文档描述了如何使用 Thrust 开发 CUDA 应用程序。 本教程旨在易于访问,即使您的 C++ 或 CUDA 经验有限。

1.1. 安装和版本控制

安装 CUDA 工具包会将 Thrust 头文件复制到系统的标准 CUDA 包含目录。 由于 Thrust 是头文件的模板库,因此无需进一步安装即可开始使用 Thrust。

此外,新版本的 Thrust 继续通过 GitHub Thrust 项目页面在线提供。

2.向量

Thrust 提供了两个 vector 容器,host_vectordevice_vector。 顾名思义,host_vector 存储在主机内存中,而 device_vector 存储在 GPU 设备内存中。 Thrust 的向量容器就像 C++ STL 中的 std::vector。 与 std::vector 一样,host_vectordevice_vector 是可以动态调整大小的通用容器(能够存储任何数据类型)。 以下源代码说明了 Thrust 的矢量容器的使用。

#include <thrust/host_vector.h>
#include <thrust/device_vector.h>

#include <iostream>

int main(void)
{
    // H has storage for 4 integers
    thrust::host_vector<int> H(4);

    // initialize individual elements
    H[0] = 14;
    H[1] = 20;
    H[2] = 38;
    H[3] = 46;

    // H.size() returns the size of vector H
    std::cout << "H has size " << H.size() << std::endl;

    // print contents of H
    for(int i = 0; i < H.size(); i++)
        std::cout << "H[" << i << "] = " << H[i] << std::endl;

    // resize H
    H.resize(2);

    std::cout << "H now has size " << H.size() << std::endl;

    // Copy host_vector H to device_vector D
    thrust::device_vector<int> D = H;

    // elements of D can be modified
    D[0] = 99;
    D[1] = 88;

    // print contents of D
    for(int i = 0; i < D.size(); i++)
        std::cout << "D[" << i << "] = " << D[i] << std::endl;

    // H and D are automatically deleted when the function returns
    return 0;
}

如本示例所示,= 运算符可用于将 host_vector 复制到 device_vector(反之亦然)。 = 运算符也可用于将 host_vector 复制到 host_vector 或将 device_vector 复制到 device_vector。 另请注意,可以使用标准括号表示法访问 device_vector 的各个元素。 但是,因为这些访问中的每一个都需要调用 cudaMemcpy,所以应该谨慎使用它们。 稍后我们将介绍一些更有效的技术。

将向量的所有元素初始化为特定值,或仅将一组特定值从一个向量复制到另一个向量通常很有用。 Thrust 提供了几种方法来执行这些类型的操作。

#include <thrust/host_vector.h>
#include <thrust/device_vector.h>

#include <thrust/copy.h>
#include <thrust/fill.h>
#include <thrust/sequence.h>

#include <iostream>

int main(void)
{
    // initialize all ten integers of a device_vector to 1
    thrust::device_vector<int> D(10, 1);

    // set the first seven elements of a vector to 9
    thrust::fill(D.begin(), D.begin() + 7, 9);

    // initialize a host_vector with the first five elements of D
    thrust::host_vector<int> H(D.begin(), D.begin() + 5);

    // set the elements of H to 0, 1, 2, 3, ...
    thrust::sequence(H.begin(), H.end());

    // copy all of H back to the beginning of D
    thrust::copy(H.begin(), H.end(), D.begin());

    // print D
    for(int i = 0; i < D.size(); i++)
        std::cout << "D[" << i << "] = " << D[i] << std::endl;

    return 0;
}

这里我们说明了填充、复制和序列功能的使用。 copy 函数可用于将一系列主机或设备元素复制到另一个主机或设备向量。 与相应的 STL 函数一样,thrust::fill 只是将一系列元素设置为特定值。 Thrust 的序列函数可用于创建等距值序列。

2.1. Thrust命名空间

您会注意到,我们在示例中使用了诸如 Thrust::Host_VectorThrust::Copy 之类的东西。 Thrust:: 部分告诉 C++ 编译器我们想要在 Thrust 命名空间中查找特定的函数或类。 命名空间是避免名称冲突的好方法。 例如,thrust::copy 与 STL 中提供的 std::copy 不同。 C++ 命名空间允许我们区分这两个复制函数。

2.2. 迭代器和静态调度

在本节中,我们使用了 H.begin()H.end() 等表达式或 D.begin() + 7 等偏移量。begin()end() 的结果在 C++ 中称为迭代器。 对于向量容器(实际上只是数组)来说,迭代器可以被认为是指向数组元素的指针。 因此,H.begin() 是一个迭代器,它指向存储在 H 向量内的数组的第一个元素。 类似地,H.end() 指向 H 向量的最后一个元素后面的元素。

尽管向量迭代器与指针类似,但它们携带更多信息。 请注意,我们不必告诉 Thrust::fill 它正在 device_vector 迭代器上运行。 此信息在 D.begin() 返回的迭代器类型中捕获,该类型与 H.begin() 返回的类型不同。 当调用 Thrust 函数时,它会检查迭代器的类型以确定是使用主机还是设备实现。 此过程称为静态调度,因为主机/设备调度是在编译时解析的。 请注意,这意味着调度过程没有运行时开销。

您可能想知道当“原始”指针用作 Thrust 函数的参数时会发生什么。 与 STL 一样,Thrust 允许这种用法,并且它将调度算法的主机路径。 如果所讨论的指针实际上是指向设备内存的指针,那么您需要在调用该函数之前用Thrust::device_ptr将其包装起来。 例如:

size_t N = 10;

// raw pointer to device memory
int * raw_ptr;
cudaMalloc((void **) &raw_ptr, N * sizeof(int));

// wrap raw pointer with a device_ptr
thrust::device_ptr<int> dev_ptr(raw_ptr);

// use device_ptr in thrust algorithms
thrust::fill(dev_ptr, dev_ptr + N, (int) 0);

要从 device_ptr 中提取原始指针,应按如下方式应用 raw_pointer_cast

size_t N = 10;

// create a device_ptr
thrust::device_ptr<int> dev_ptr = thrust::device_malloc<int>(N);

// extract raw pointer from device_ptr
int * raw_ptr = thrust::raw_pointer_cast(dev_ptr);

区分迭代器和指针的另一个原因是迭代器可用于遍历多种数据结构。 例如,STL 提供了一个链表容器 (std::list),它提供双向(但不是随机访问)迭代器。 虽然 Thrust 不提供此类容器的设备实现,但它与它们兼容。

#include <thrust/device_vector.h>
#include <thrust/copy.h>
#include <list>
#include <vector>

int main(void)
{
    // create an STL list with 4 values
    std::list<int> stl_list;

    stl_list.push_back(10);
    stl_list.push_back(20);
    stl_list.push_back(30);
    stl_list.push_back(40);

    // initialize a device_vector with the list
    thrust::device_vector<int> D(stl_list.begin(), stl_list.end());

    // copy a device_vector into an STL vector
    std::vector<int> stl_vector(D.size());
    thrust::copy(D.begin(), D.end(), stl_vector.begin());

    return 0;
}

供将来参考:我们目前介绍的迭代器很有用,但相当基础。 除了这些普通的迭代器之外,Thrust 还提供了一组奇特的迭代器,其名称如 counting_iterator 和 zip_iterator。 虽然它们看起来和感觉起来都像普通的迭代器,但奇特的迭代器能够做更令人兴奋的事情。 我们将在本教程的后面部分重新讨论这个主题。

3. 算法

Thrust 提供了大量常用的并行算法。 其中许多算法在 STL 中都有直接的类似物,当存在等效的 STL 函数时,我们选择名称(例如,thrust::sortstd::sort)。

Thrust 中的所有算法都有针对主机和设备的实现。 具体来说,当使用主机迭代器调用 Thrust 算法时,将调度主机路径。 类似地,当使用设备迭代器定义范围时,将调用设备实现。

除了可以在主机和设备之间复制数据的 Thrust::copy 之外,Thrust 算法的所有迭代器参数都应该位于同一位置:要么全部位于主机上,要么全部位于设备上。 当违反此要求时,编译器将产生一条错误消息。

3.1. 变换

转换是对一组(零个或多个)输入范围中的每个元素应用操作,然后将结果存储在目标范围中的算法。 我们已经看到的一个例子是 thrust::fill,它将范围内的所有元素设置为指定值。 其他转换包括 thrust::sequencethrust::replace,当然还有 thrust::transform。 有关完整列表,请参阅文档。

以下源代码演示了几种转换算法。 请注意,thrust::negatethrust::modulus 在 C++ 术语中称为函子。 Thrust 在文件 thrust/functional.h 中提供了这些和其他常见的仿函数,例如加号和乘法。

#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)
{
    // allocate three device_vectors with 10 elements
    thrust::device_vector<int> X(10);
    thrust::device_vector<int> Y(10);
    thrust::device_vector<int> Z(10);

    // initialize X to 0,1,2,3, ....
    thrust::sequence(X.begin(), X.end());

    // compute Y = -X
    thrust::transform(X.begin(), X.end(), Y.begin(), thrust::negate<int>());

    // fill Z with twos
    thrust::fill(Z.begin(), Z.end(), 2);

    // compute Y = X mod 2
    thrust::transform(X.begin(), X.end(), Z.begin(), Y.begin(), thrust::modulus<int>());

    // replace all the ones in Y with tens
    thrust::replace(Y.begin(), Y.end(), 1, 10);

    // print Y
    thrust::copy(Y.begin(), Y.end(), std::ostream_iterator<int>(std::cout, "\n"));

    return 0;
}

虽然 Throws/Functional.h 中的函子涵盖了大部分内置算术和比较运算,但我们经常想做一些不同的事情。 例如,考虑向量运算 y <- a * x + y,其中 x 和 y 是向量,a 是标量常数。 这是任何 BLAS 库提供的众所周知的 SAXPY 操作。

如果我们想用 Thrust 实现 SAXPY,我们有几个选择。 第一种是使用两个转换(一个加法和一个乘法)和一个用值 a 填充的临时向量。 更好的选择是将单个转换与用户定义的函子一起使用,这正是我们想要的。 我们在下面的源代码中说明了这两种方法。

struct saxpy_functor
{
    const float a;

    saxpy_functor(float _a) : a(_a) {}

    __host__ __device__
        float operator()(const float& x, const float& y) const {
            return a * x + y;
        }
};

void saxpy_fast(float A, thrust::device_vector<float>& X, thrust::device_vector<float>& Y)
{
    // Y <- A * X + Y
    thrust::transform(X.begin(), X.end(), Y.begin(), Y.begin(), saxpy_functor(A));
}

void saxpy_slow(float A, thrust::device_vector<float>& X, thrust::device_vector<float>& Y)
{
    thrust::device_vector<float> temp(X.size());

    // temp <- A
    thrust::fill(temp.begin(), temp.end(), A);

    // temp <- A * X
    thrust::transform(X.begin(), X.end(), temp.begin(), temp.begin(), thrust::multiplies<float>());

    // Y <- A * X + Y
    thrust::transform(temp.begin(), temp.end(), Y.begin(), Y.begin(), thrust::plus<float>());
}

saxpy_fastsaxpy_slow 都是有效的 SAXPY 实现,但是 saxpy_fast 将明显快于 saxpy_slow。 忽略分配临时向量和算术运算的成本,我们有以下成本:

fast_saxpy:执行 2N 次读取和 N 次写入

Slow_saxpy:执行 4N 次读取和 3N 次写入

由于 SAXPY 受内存限制(其性能受限于内存带宽,而不是浮点性能),较大的读写次数使得 saxpy_slow 的成本更高。 相反,在优化的 BLAS 实现中,saxpy_fast 的执行速度与 SAXPY 一样快。 在像 SAXPY 这样的内存绑定算法中,通常值得应用内核融合(将多个操作组合到一个内核中)以最小化内存事务的数量。

Thrust::transform 仅支持具有一个或两个输入参数的转换(例如 f ( x ) → y f(x)\rightarrow y f(x)y f ( x , x ) → y f(x,x)\rightarrow y f(x,x)y). 当转换使用两个以上的输入参数时,有必要使用不同的方法。 random_transformation 示例演示了使用Thrust::zip_iteratorThrust::for_each 的解决方案。

3.2. Reductions

缩减算法使用二元运算将输入序列缩减为单个值。 例如,数字数组的和是通过用加运算减少数组来获得的。 类似地,数组的最大值是通过使用接受两个输入并返回最大值的运算符进行归约来获得的。 数组的求和是用thrust::reduce实现的,如下所示:

int sum = thrust::reduce(D.begin(), D.end(), (int) 0, thrust::plus<int>());

reduce 的前两个参数定义值的范围,而第三个和第四个参数分别提供初始值和归约运算符。 实际上,这种归约非常常见,以至于在没有提供初始值或运算符时它是默认选择。 因此,以下三行是等效的:

int sum = thrust::reduce(D.begin(), D.end(), (int) 0, thrust::plus<int>());
int sum = thrust::reduce(D.begin(), D.end(), (int) 0);
int sum = thrust::reduce(D.begin(), D.end())

尽管 Thrust::reduce 足以实现各种缩减,但为了方便起见,Thrust 还提供了一些附加函数(如 STL)。 例如,thrust::count 返回给定序列中特定值的实例数:

#include <thrust/count.h>
#include <thrust/device_vector.h>
...
// put three 1s in a device_vector
thrust::device_vector<int> vec(5,0);
vec[1] = 1;
vec[3] = 1;
vec[4] = 1;

// count the 1s
int result = thrust::count(vec.begin(), vec.end(), 1);
// result is three

其他归约操作包括 Thrust::count_ifthrust::min_elementthrust::max_elementthrust::is_sortedthrust::inner_product 等。 有关完整列表,请参阅文档。

转换部分中的 SAXPY 示例展示了如何使用内核融合来减少转换内核使用的内存传输数量。 使用thrust::transform_reduce,我们还可以将内核融合应用于缩减内核。 考虑以下计算向量范数的示例。

#include <thrust/transform_reduce.h>
#include <thrust/functional.h>
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <cmath>

// square<T> computes the square of a number f(x) -> x*x
template <typename T>
struct square
{
    __host__ __device__
        T operator()(const T& x) const {
            return x * x;
        }
};

int main(void)
{
    // initialize host array
    float x[4] = {1.0, 2.0, 3.0, 4.0};

    // transfer to device
    thrust::device_vector<float> d_x(x, x + 4);

    // setup arguments
    square<float>        unary_op;
    thrust::plus<float> binary_op;
    float init = 0;

    // compute norm
    float norm = std::sqrt( thrust::transform_reduce(d_x.begin(), d_x.end(), unary_op, init, binary_op) );

    std::cout << norm << std::endl;

    return 0;
}

这里我们有一个称为 square 的一元运算符,它对输入序列的每个元素进行平方。 然后使用标准加约化来计算平方和。 与 SAXPY 变换的较慢版本一样,我们可以通过多次传递来实现范数:首先使用平方或可能只是乘法进行变换,然后对临时数组进行加减法。 然而,这会造成不必要的浪费并且速度会相当慢。 通过将平方运算与缩减内核融合,我们再次获得了高度优化的实现,它提供了与手写内核相同的性能。

3.3. 前缀和

并行前缀和或扫描操作是许多并行算法(例如流压缩和基数排序)中的重要构建块。 考虑以下源代码,它说明了使用默认加号运算符的包含扫描操作:

#include <thrust/scan.h>

int data[6] = {1, 0, 2, 2, 1, 3};

thrust::inclusive_scan(data, data + 6, data); // in-place scan

// data is now {1, 1, 3, 5, 6, 9}

在包含扫描中,输出的每个元素都是输入范围的相应部分和。 例如,data[2] = data[0] + data[1] + data[2]。 独占扫描类似,但向右移动一位:

#include <thrust/scan.h>

int data[6] = {1, 0, 2, 2, 1, 3};

thrust::exclusive_scan(data, data + 6, data); // in-place scan

// data is now {0, 1, 1, 3, 5, 6}

所以现在data[2] = data[0] + data[1]。 正如这些示例所示,inclusion_scanExclusive_scan 允许就地执行。 Thrust还提供了函数transform_inclusive_scantransform_exclusive_scan,它们在执行扫描之前将一元函数应用于输入序列。 请参阅文档以获取扫描变体的完整列表。

3.4. 重新排序

Thrust 通过以下算法提供对分区和流压缩的支持:

  • copy_if :复制通过谓词测试的元素

  • partition :根据谓词对元素重新排序(真值先于假值)

  • removeremove_if :删除谓词测试失败的元素

  • unique:删除序列中连续的重复项

有关重新排序函数及其用法示例的完整列表,请参阅文档。

3.5. 排序

Thrust 提供了多种函数来根据给定标准对数据进行排序或重新排列数据。 Thrust::sortThrust::stable_sort 函数与 STL 中的 sortstable_sort 直接类似。

#include <thrust/sort.h>

...
const int N = 6;
int A[N] = {1, 4, 2, 8, 5, 7};

thrust::sort(A, A + N);

// A is now {1, 2, 4, 5, 7, 8}

此外,Thrust 提供了 thrust::sort_by_keythrust::stable_sort_by_key,它们可以对存储在不同位置的键值对进行排序。

#include <thrust/sort.h>

...
const int N = 6;
int    keys[N] = {  1,   4,   2,   8,   5,   7};
char values[N] = {'a', 'b', 'c', 'd', 'e', 'f'};

thrust::sort_by_key(keys, keys + N, values);

// keys is now   {  1,   2,   4,   5,   7,   8}
// values is now {'a', 'c', 'b', 'e', 'f', 'd'}

与它们的 STL 一样,排序函数也接受用户定义的比较运算符:

#include <thrust/sort.h>
#include <thrust/functional.h>

...
const int N = 6;
int A[N] = {1, 4, 2, 8, 5, 7};

thrust::stable_sort(A, A + N, thrust::greater<int>());

// A is now {8, 7, 5, 4, 2, 1}

4. 迭代器

精美的迭代器具有多种有价值的用途。 在本节中,我们将展示奇妙的迭代器如何让我们能够使用标准 Thrust 算法来解决更广泛的问题。 对于那些熟悉 Boost C++ 库的人,请注意我们的奇特迭代器是受 Boost 迭代器库中的启发(并且通常派生自)的。

4.1. constant_iterator

可以说是最简单的,constant_iterator 只是一个迭代器,每当我们访问它时,它都会返回相同的值。 在下面的示例中,我们用值 10 初始化一个常量迭代器。

#include <thrust/iterator/constant_iterator.h>
...
// create iterators
thrust::constant_iterator<int> first(10);
thrust::constant_iterator<int> last = first + 3;

first[0]   // returns 10
first[1]   // returns 10
first[100] // returns 10

// sum of [first, last)
thrust::reduce(first, last);   // returns 30 (i.e. 3 * 10)

每当需要输入常量值序列时,constant_iterator 都是一种方便且高效的解决方案。

4.2. 计数迭代器

如果需要一系列递增的值,那么counting_iterator是合适的选择。 这里我们用值 10 初始化一个counting_iterator,并像访问数组一样访问它。

#include <thrust/iterator/counting_iterator.h>
...
// create iterators
thrust::counting_iterator<int> first(10);
thrust::counting_iterator<int> last = first + 3;

first[0]   // returns 10
first[1]   // returns 11
first[100] // returns 110

// sum of [first, last)
thrust::reduce(first, last);   // returns 33 (i.e. 10 + 11 + 12)

虽然constant_iteratorcounting_iterator充当数组,但它们实际上不需要任何内存存储。 每当我们取消引用这些迭代器之一时,它都会即时生成适当的值并将其返回给调用函数。

4.3. 变换迭代器

在算法部分,我们谈到了内核融合,即将单独的算法(如变换和归约)组合成一个单一的变换归约操作。 即使我们没有特殊的 transform_xxx 版本的算法,transform_iterator 也允许我们应用相同的技术。 此示例展示了将转换与归约融合的另一种方法,这次仅将简单的归约应用于transform_iterator

#include <thrust/iterator/transform_iterator.h>
// initialize vector
thrust::device_vector<int> vec(3);
vec[0] = 10; vec[1] = 20; vec[2] = 30;

// create iterator (type omitted)
...
first = thrust::make_transform_iterator(vec.begin(), negate<int>());
...
last  = thrust::make_transform_iterator(vec.end(),   negate<int>());

first[0]   // returns -10
first[1]   // returns -20
first[2]   // returns -30

// sum of [first, last)
thrust::reduce(first, last);   // returns -60 (i.e. -10 + -20 + -30)

请注意,为了简单起见,我们省略了第一个和最后一个迭代器的类型。 Transform_iterator 的一个缺点是指定迭代器的完整类型可能很麻烦,而且可能会相当冗长。 因此,通常的做法是简单地将对 make_transform_iterator 的调用放在所调用算法的参数中。 例如,

// sum of [first, last)
thrust::reduce(thrust::make_transform_iterator(vec.begin(), negate<int>()),
               thrust::make_transform_iterator(vec.end(),   negate<int>()));

允许我们避免创建一个变量来存储第一个和最后一个。

4.4. 排列迭代器

在上一节中,我们展示了如何使用transform_iterator将转换与另一种算法融合,以避免不必要的内存操作。 permutation_iterator 是类似的:它允许我们将聚集和分散操作与 Thrust 算法甚至其他奇特的迭代器融合。 以下示例演示如何将收集操作与归约融合。

#include <thrust/iterator/permutation_iterator.h>

...

// gather locations
thrust::device_vector<int> map(4);
map[0] = 3;
map[1] = 1;
map[2] = 0;
map[3] = 5;

// array to gather from
thrust::device_vector<int> source(6);
source[0] = 10;
source[1] = 20;
source[2] = 30;
source[3] = 40;
source[4] = 50;
source[5] = 60;

// fuse gather with reduction:
//   sum = source[map[0]] + source[map[1]] + ...
int sum = thrust::reduce(thrust::make_permutation_iterator(source.begin(), map.begin()),
                         thrust::make_permutation_iterator(source.begin(), map.end()));

这里我们使用 make_permutation_iterator 函数来简化 permutation_iterator 的构造。 make_permutation_iterator 的第一个参数是收集操作的源数组,第二个参数是映射索引列表。 请注意,在这两种情况下,我们都为第一个参数传递 source.begin() ,但改变第二个参数来定义序列的开始和结束。

permutation_iterator 用作函数的输出序列时,它相当于将分散操作融合到算法中。 一般来说,permutation_iterator 允许您对序列中的一组特定值进行操作,而不是对整个序列进行操作。

4.5. zip_iterator

继续阅读,我们把最好的迭代器留到最后! zip_iterator 是一个非常有用的小工具:它接受多个输入序列并生成一个元组序列。 在这个例子中,我们将一个 int 序列和一个 char 序列“压缩”到一个 tuple<int,char> 序列中,并计算具有最大值的元组。

#include <thrust/iterator/zip_iterator.h>
...
// initialize vectors
thrust::device_vector<int>  A(3);
thrust::device_vector<char> B(3);
A[0] = 10;  A[1] = 20;  A[2] = 30;
B[0] = 'x'; B[1] = 'y'; B[2] = 'z';

// create iterator (type omitted)
first = thrust::make_zip_iterator(thrust::make_tuple(A.begin(), B.begin()));
last  = thrust::make_zip_iterator(thrust::make_tuple(A.end(),   B.end()));

first[0]   // returns tuple(10, 'x')
first[1]   // returns tuple(20, 'y')
first[2]   // returns tuple(30, 'z')

// maximum of [first, last)
thrust::maximum< tuple<int,char> > binary_op;
thrust::tuple<int,char> init = first[0];
thrust::reduce(first, last, init, binary_op); // returns tuple(30, 'z')

zip_iterator 如此有用的原因是大多数算法接受一个或偶尔两个输入序列。 zip_iterator 允许我们将许多独立的序列组合成单个元组序列,该序列可以由多种算法处理。

请参阅 random_transformation 示例,了解如何使用 zip_iteratorfor_each 实现三元转换。 此示例的简单扩展将允许您计算具有多个输出序列的转换。

除了方便之外,zip_iterator 还可以让我们更高效地实现程序。 例如,在 CUDA 中将 3d 点存储为 float3 数组通常是一个坏主意,因为数组访问未正确合并。 使用 zip_iterator,我们可以将三个坐标存储在三个单独的数组中,这确实允许合并内存访问。 在这种情况下,我们使用 zip_iterator 创建一个虚拟的 3d 向量数组,我们可以将其输入 Thrust 算法。 有关更多详细信息,请参阅 dot_products_with_zip 示例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/673223.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows自带的linux系统,从C盘迁移到D盘

1. 查看当前wsl版本和 运行状态 wsl -l -v wsl --list, -l 用于列出分发 本人电脑装的是Ubuntu-18.04&#xff0c;正在运行&#xff0c;版本1 2. 在D盘建linux目录&#xff0c;打包Ubuntu-18.04&#xff0c;导入到D盘的linux目录 wsl --export <DistributionName> &l…

9个最实用的PS插件盘点!

因为个人原因&#xff0c;对PS的插件用了不下 100 款&#xff0c;其中有好有坏&#xff0c;有优有劣&#xff0c;大浪淘沙&#xff0c;优胜劣汰&#xff0c;现在整理了自己觉得不错的 PS 插件。 1、Alien Skin Blow Up 3 for mac Blow Up 3 mac 版是 Macos 上一款 PS 图像无损放…

Apache Zeppelin系列教程第十篇——SQL Debug In Zeppelin

SQL Debug介绍 首先介绍下什么是SQL Debug&#xff1f; 但是经常有这样一个需求&#xff0c;一大段sql 跑出来之后&#xff0c;发现不是自己想要的结果&#xff1f;比如&#xff1a; demo 1: select id,name from ( select id,name from table1 union all select id,name fr…

web漏洞之文件上传漏洞

文章目录 一、漏洞原因二、漏洞危害三、漏洞利用1.三个条件2.利用方式3.绕过方式a.绕过JS验证① BP绕过② F12绕过③ 菜刀上传实操 b.绕过MIME-Type验证c.绕过黑名单验证① 直接修改后缀名绕过② htaccess绕过(有拦截)③ 大小写绕过(有拦截)④ 空格绕过⑤ .号绕过⑥ 特…

技术改变生活,开发者必须掌握这些技能

技术改变生活&#xff0c;开发者必须掌握这些技能 一、前言二、背景三、开发者必须掌握这些技能1. 语言与编程2. 数据结构与算法3. 开发框架与工具4. 应用开发与测试5. 团队协作与沟通 一、前言 随着科技的不断进步和发展&#xff0c;我们的生活方式也在不断地变化。互联网、智…

Session覆盖测试-业务安全测试实操(19)

弱Token设计缺陷测试,Session覆盖测试 Session覆盖测试 测试原理和方法 找回密码逻辑漏洞测试中也会遇到参数不可控的情况,比如要修改的用户名或者绑定的手机号无法在提交参数时修改,服务端通过读取当前session会话来判断要修改密码的账号,这种情况下能否对Session中的内容做…

【架构】洋葱架构

文章目录 前言一、为什么要用洋葱架构&#xff1f;二、原则2.1、依赖性2.2、数据封装2.3、关注点的分离2.4、耦合性 三、洋葱架构层四、领域模型/实体五、领域服务六、应用服务七、基础设施服务八、可观察性服务九、测试策略十、微服务十一、模块化与打包十二、框架、客户端和驱…

基于Java个人博客网站设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

基于Java游戏攻略网站设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

【Unityc#专题篇】之c#进阶篇

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

【C#进阶】C# 索引器

序号系列文章13【C#进阶】C# 特性14【C#进阶】C# 反射15【C#进阶】C# 属性 文章目录 前言1、索引器的概念2、索引器的定义3、索引器的基本使用4、索引器的重载5、接口中的索引器6、属性和索引器之间的比较7、索引器的适用场景结语 前言 &#x1f342; Hello大家好啊&#xff0c…

基于Java会员管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

从零开始 Spring Boot 46:@Lookup

从零开始 Spring Boot 46&#xff1a;Lookup 图源&#xff1a;简书 (jianshu.com) 在前文中&#xff0c;我介绍了 Spring Bean 的作用域&#xff08;Scope&#xff09;&#xff0c;且讨论了将一个短生命周期的 bean &#xff08;比如request作用域的 bean&#xff09;注入到长…

事务小总结

事务定义 是一个数据库操作序列&#xff0c;这些操作要么全部执行,要么全部不执行&#xff0c;是一个不可分割的工作&#xff08;程序执行&#xff09;单元。事务由事务开始与事务结束之间执行的全部数据库操作组成。 事务特性 原子性(Atomicity)一致性(Consistency)隔离性(…

Linux下 文件删除但是空间未被释放 或者 磁盘已满但找不到对应的大文件 的解决方案

Linux下文件删除但是空间未被释放的解决方案 前言1. 查看当前磁盘占用情况2. 模拟进程占用3. 执行rm -rf 命令删除文件4. 查看被删除但是未释放空间的文件5. 执行清空文件操作 前言 linux磁盘空间已满&#xff0c;手动rm -rf 删除了大文件之后&#xff0c;df -h 查看一下发现空…

操作系统第四章练习题

第一部分 教材习题&#xff08;P152&#xff09; 1、为什么要配置层次式存储器&#xff1f; 设置多个存储器能够使存储器两头的硬件能并行工作;采用多级存储系统,专门是Cache 技术,是减轻存储器带宽对系统性能影响的最佳结构方案;在微处置机内部设置各类缓冲存储器,减轻对存储…

【Android -- 面试】Android 面试题集锦(Java 基础)

Java 基础 1、Java 的类加载过程 jvm 将 .class 类文件信息加载到内存并解析成对应的 class 对象的过程&#xff0c; 注意&#xff1a;jvm 并不是一开始就把所有的类加载进内存中&#xff0c;只是在第一次遇到某个需要运行的类才会加载&#xff0c;并且只加载一次 主要分为三…

【ARMv8 SIMD和浮点指令编程】NEON 比较指令——比较的方方面面

比较指令是常见的一类指令,NEON 中当然也排除,下面涉及比较和测试位两类指令。 1 CMEQ (register) 按位比较相等(向量)。该指令将第一个源 SIMD&FP 寄存器中的每个向量元素与第二个源 SIMD&FP 寄存器中的相应向量元素进行比较,如果比较相等,则将目标 SIMD&…

基于Python+Django+mysql+html通讯录管理系统

基于PythonDjangomysqlhtml通讯录管理系统 一、系统介绍二、功能展示1.用户登陆2.用户注册3.密码修改4.查询5.添加6.修改7.删除 三、其它系统四、获取源码 一、系统介绍 该系统实现了 用户登陆、用户注册、密码修改、查询信息、添加信息&#xff0c;修改信息、删除信息 运行环…

vue2脚手架可视化小项目

vue2脚手架小项目 这个项目又数据可视化可以通过点击按钮修改数据&#xff0c;同时图形的内容也会随着变化这里面也又保存功能&#xff0c;搜索功能&#xff0c;添加功能用到的框架有vue2脚手架&#xff0c;语言有bootstrap&#xff0c;css&#xff0c;也用了echarts 下面会先…