CUDA内核的Thrust操作进一步优化的机会

huangapple go评论111阅读模式
英文:

Further chance of optimization of Thrust operation of CUDA kernel

问题

以下是您提供的CUDA内核代码的中文翻译:

__global__ void myOpKernel(double *vals, double *data, int *nums, double *crit, int N, int K) {
  int index = blockIdx.x * blockDim.x + threadIdx.x;

  if (index >= N) return;

  double _crit = crit[index];
  for (int i = 0; i < nums[index]; i++) {
    double _res = vals[index * K + i];
    if (data[index * K + i] >= _crit) { _res = 0.0; }

    vals[index * K + i] = _res;
  }
}

此内核根据data[N*K]crit[N]的比较来评估vals[N*K],比较是在vals段的前nums[N]个元素(宽度为K)上进行的。如果data小于crit,则不改变vals

以下是您提供的基于Thrust的代码的中文翻译:

struct myOp : public thrust::unary_function<thrust::tuple<double, double, int, int, int, double>, double> {
  // vals   data   1/K 1%K nums crit
  __host__ __device__
  double operator()(const thrust::tuple<double, double, int, int, int, double> &t) const {
    double res;

    if (thrust::get<2>(t) >= thrust::get<4>(t)) {
      res = thrust::get<0>(t);  // 什么都不做
    } else {
      if (thrust::get<3>(t) >= thrust::get<4>(t)) {
        res = thrust::get<0>(t); // 什么都不做
      } else {
        double tmp = thrust::get<0>(t);
        if (thrust::get<1>(t) >= thrust::get<5>(t)) { tmp = 0.0; }
        res = tmp;
      }
    }

    return res;
  }
};

int main() {

  using namespace thrust::placeholders;

  thrust::device_vector<double> vals(N * K);
  thrust::device_vector<double> data(N * K);
  thrust::device_vector<double> crit(N);
  thrust::device_vector<int> nums(N);
  thrust::device_vector<double> res(N * K);

  // ... 填充值 ...

  thrust::device_vector<int> nums_expand(N * K);
  thrust::device_vector<double> crit_expand(N * K);

  // 'expand()' 做的类似于 [1,2,3] -> [1,1,1,2,2,2,3,3,3]
  expand(thrust::constant_iterator<int>(K),
         thrust::constant_iterator<int>(K) + N,
         nums.begin(),
         nums_expand.begin());

  expand(thrust::constant_iterator<int>(K),
         thrust::constant_iterator<int>(K) + N,
         crit.begin(),
         crit_expand.begin());

  thrust::transform(thrust::make_zip_iterator(vals.begin(),
                                              data.begin(),
                                              thrust::make_transform_iterator(thrust::counting_iterator<int>(0), _1 / K), // 与N相关的索引
                                              thrust::make_transform_iterator(thrust::counting_iterator<int>(0), _1 % K), // 与K相关的索引
                                              nums_expand.begin(),
                                              crit_expand.begin()),
                    thrust::make_zip_iterator(vals.end(),
                                              data.end(),
                                              thrust::make_transform_iterator(thrust::counting_iterator<int>(0), _1 / K) + N * K,
                                              thrust::make_transform_iterator(thrust::counting_iterator<int>(0), _1 % K) + N * K,
                                              nums_expand.end(),
                                              crit_expand.end()),
                    res.begin(),
                    myOp());

  // ...
}

请注意,这个翻译仅包括代码的部分,不包括问题或其他内容。如果需要任何进一步的信息或翻译,请随时提出。

英文:

I have a CUDA kernel which essentially looks like the following.

__global__ void myOpKernel(double *vals, double *data, int *nums, double *crit, int N, int K) {
int index = blockIdx.x*blockDim.x + threadIdx.x;
if (index &gt;= N) return;
double _crit = crit[index];
for (int i=0; i&lt;nums[index]; i++) {
double _res = vals[index*K + i];
if (data[index*K + i] &gt;= _crit) { _res = 0.0; }
vals[index*K + i] = _res;
}
}

This kernel evaluates vals[N*K] based on its data[N*K] compared to crit[N], and the comparison is conducted on the first nums[N] elements of the vals's segment (width K). If the data is smaller than crit, it leaves vals unchanged.

For example, data under consideration will look like the following

  int N = 3;
int K = 5;
vals[ 0] = 1.0; data[ 0] = 5.1; crit[0] = 5.0; nums[0] = 3;
vals[ 1] = 1.0; data[ 1] = 4.9;
vals[ 2] = 1.0; data[ 2] = 3.0;
vals[ 3] = 0.0; data[ 3] = 0.0;
vals[ 4] = 0.0; data[ 4] = 0.0;
//-----------------------
vals[ 5] = 1.0; data[ 5] = 2.9; crit[1] = 3.0; nums[1] = 2;
vals[ 6] = 1.0; data[ 6] = 3.1;
vals[ 7] = 0.0; data[ 7] = 0.0;
vals[ 8] = 0.0; data[ 8] = 0.0;
vals[ 9] = 0.0; data[ 9] = 0.0;
//-----------------------
vals[10] = 1.0; data[10] = 8.1; crit[2] = 9.0; nums[2] = 5;
vals[11] = 1.0; data[11] = 7.8;
vals[12] = 1.0; data[12] = 9.1;
vals[13] = 1.0; data[13] = 200.;
vals[14] = 1.0; data[14] = -1.0;

I noticed that this kind of operation is one of top 3 time-consuming kernels, and am considering Thrust-based acceleration.

What I came up with so far looks like the following. It uses expand provided on Thrust samples (https://github.com/NVIDIA/thrust/blob/master/examples/expand.cu).

struct myOp : public thrust::unary_function&lt;thrust::tuple&lt;double,double,int,int,int,double&gt;, double&gt; {
// vals   data   1/K 1%K nums crit
__host__ __device__                   // 0      1      2   3   4    5
double operator() (const thrust::tuple&lt;double,double,int,int,int, double&gt; &amp;t) const {
double res;
if (thrust::get&lt;2&gt;(t) &gt;= thrust::get&lt;4&gt;(t)) {
res = thrust::get&lt;0&gt;(t);  // do nothing
}else {
if (thrust::get&lt;3&gt;(t) &gt;= thrust::get&lt;4&gt;(t)) {
res = thrust::get&lt;0&gt;(t); // do nothing
}else {
double tmp = thrust::get&lt;0&gt;(t);
if (thrust::get&lt;1&gt;(t) &gt;= thrust::get&lt;5&gt;(t)) { tmp = 0.0; }
res = tmp;
}
}
return res;
}
};
int main() {
using namespace thrust::placeholders;
thrust::device_vector&lt;double&gt; vals(N*K);
thrust::device_vector&lt;double&gt; data(N*K);
thrust::device_vector&lt;double&gt; crit(N);
thrust::device_vector&lt;int&gt;    nums(N);
thrust::device_vector&lt;double&gt; res(N*K);
// ... fill values ...
thrust::device_vector&lt;int&gt;    nums_expand(N*K);
thrust::device_vector&lt;double&gt; crit_expand(N*K);
// &#39;expand()&#39; does something like [1,2,3] -&gt; [1,1,1,2,2,2,3,3,3]
expand(thrust::constant_iterator&lt;int&gt;(K),
thrust::constant_iterator&lt;int&gt;(K)+N,
nums.begin(),
nums_expand.begin());
expand(thrust::constant_iterator&lt;int&gt;(K),
thrust::constant_iterator&lt;int&gt;(K)+N,
crit.begin(),
crit_expand.begin());
thrust::transform(thrust::make_zip_iterator(vals.begin(),
data.begin(),
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1/K), // index related to N
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1%K), // index related to K
nums_expand.begin(),
crit_expand.begin()),
thrust::make_zip_iterator(vals.end(),
data.end(),
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1/K) + N*K,
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1%K) + N*K,
nums_expand.end(),
crit_expand.end()),
res.begin(),
myOp());
...
}

When I tried this with arbitrary values in the arrays with sets of [N,K] = [1000,256], [10000,256], [50000,256], [100000,256], already the performance is satisfactory.

CUDA内核的Thrust操作进一步优化的机会

But I wonder if there is any further chance of speed-up with my Thrust operations. I am expanding some values to take them into if statements, but maybe this can be avoided by permutation_iterator and so on, but I cannot come up with how. Also, I am doing _1/K, _1%K stuff to get the global and local index of the elements, which could be somehow avoided with more clever mind.

At least, for the cosmetics point of view, I would love to insert expand(...) into thrust::transform(...) directly without having to define another vector such as nums_expand.

Any suggestions for any chance of improvements are welcome.

Full code used for the comparison

//https://stackoverflow.com/questions/31955505/can-thrust-transform-reduce-work-with-2-arrays%5B/url%5D
#include &lt;thrust/device_vector.h&gt;
#include &lt;thrust/reduce.h&gt;
#include &lt;thrust/gather.h&gt;
#include &lt;thrust/copy.h&gt;
#include &lt;thrust/iterator/transform_iterator.h&gt;
#include &lt;thrust/iterator/counting_iterator.h&gt;
#include &lt;thrust/iterator/discard_iterator.h&gt;
//#include &lt;thrust/execution_policy.h&gt;
#include &lt;iostream&gt;
#include &lt;iomanip&gt;
#include &lt;thrust/transform.h&gt;
#include &lt;thrust/functional.h&gt;
#include &lt;helper_timer.h&gt;
/////////  https://github.com/NVIDIA/thrust/blob/master/examples/expand.cu //////////
template &lt;typename InputIterator1,
typename InputIterator2,
typename OutputIterator&gt;
OutputIterator expand(InputIterator1 first1,
InputIterator1 last1,
InputIterator2 first2,
OutputIterator output)
{
typedef typename thrust::iterator_difference&lt;InputIterator1&gt;::type difference_type;
difference_type input_size  = thrust::distance(first1, last1);
difference_type output_size = thrust::reduce(first1, last1);
// scan the counts to obtain output offsets for each input element
thrust::device_vector&lt;difference_type&gt; output_offsets(input_size, 0);
thrust::exclusive_scan(first1, last1, output_offsets.begin()); 
// scatter the nonzero counts into their corresponding output positions
thrust::device_vector&lt;difference_type&gt; output_indices(output_size, 0);
thrust::scatter_if
(thrust::counting_iterator&lt;difference_type&gt;(0),
thrust::counting_iterator&lt;difference_type&gt;(input_size),
output_offsets.begin(),
first1,
output_indices.begin());
// compute max-scan over the output indices, filling in the holes
thrust::inclusive_scan
(output_indices.begin(),
output_indices.end(),
output_indices.begin(),
thrust::maximum&lt;difference_type&gt;());
// gather input values according to index array (output = first2[output_indices])
thrust::gather(output_indices.begin(),
output_indices.end(),
first2,
output);
// return output + output_size
thrust::advance(output, output_size);
return output;
}
/////////////////////////////////////////////////////////////////////////////////////
template&lt;typename T&gt;
void print_vector(T&amp; vec) {
for (const auto&amp; elem : vec) {
std::cout &lt;&lt; std::setw(5) &lt;&lt; elem; 
}
std::cout &lt;&lt; std::endl;
}
void printSdkTimer(StopWatchInterface **timer, int average) {
float fAvgSeconds =
((float)1.0e-3 * (float)sdkGetTimerValue(timer) / (float)average);
printf(&quot; - Elapsed time: %.5f sec \n&quot;, fAvgSeconds);
}
struct myOp : public thrust::unary_function&lt;thrust::tuple&lt;double,double,int,int,int,double&gt;, double&gt; {
// vals   data   1/K 1%K nums crit
__host__ __device__             // 0      1      2   3   4    5
double operator() (const thrust::tuple&lt;double,double,int,int,int, double&gt; &amp;t) const {
double res;
if (thrust::get&lt;2&gt;(t) &gt;= thrust::get&lt;4&gt;(t)) {
res = thrust::get&lt;0&gt;(t);  // do nothing
}else {
if (thrust::get&lt;3&gt;(t) &gt;= thrust::get&lt;4&gt;(t)) {
res = thrust::get&lt;0&gt;(t); // do nothing
}else {
double tmp = thrust::get&lt;0&gt;(t);
if (thrust::get&lt;1&gt;(t) &gt;= thrust::get&lt;5&gt;(t)) { tmp = 0.0; }
res = tmp;
}
}
return res;
}
};
__global__ void myOpKernel(double *vals, double *data, int *nums, double *crit, int N, int K) {
int index = blockIdx.x*blockDim.x + threadIdx.x;
if (index &gt;= N) return;
double _crit = crit[index];
for (int i=0; i&lt;nums[index]; i++) {
double _res = vals[index*K + i];
if (data[index*K + i] &gt;= _crit) { _res = 0.0; }  
vals[index*K + i] = _res;
}
}
int main(int argc, char **argv) {
using namespace thrust::placeholders;
int N = atoi(argv[1]); 
int K = atoi(argv[2]); 
std::cout &lt;&lt; &quot;N &quot; &lt;&lt; N &lt;&lt; &quot; K &quot; &lt;&lt; K &lt;&lt; std::endl;
thrust::device_vector&lt;double&gt; vals(N*K);
thrust::device_vector&lt;double&gt; data(N*K);
thrust::device_vector&lt;double&gt; crit(N);
thrust::device_vector&lt;int&gt;    nums(N);
thrust::device_vector&lt;double&gt; res(N*K);
for (int i=0; i&lt;N; i++) {
crit[i] = 101.0; // arbitrary
nums[i] = 200;   // arbitrary number less than 256
for (int j=0; j&lt;K; j++) {
vals[i*K + j] = (double)(i*K + j); // arbitrary
data[i*K + j] = (double)(i*K + j); // arbitrary
}
}
// to be used for kernel
thrust::device_vector&lt;double&gt; vals2 = vals;
thrust::device_vector&lt;double&gt; data2 = data;
thrust::device_vector&lt;double&gt; crit2 = crit;
thrust::device_vector&lt;int&gt;    nums2 = nums;
StopWatchInterface *timer=NULL;
//--- 1) thrust
thrust::device_vector&lt;int&gt;    nums_expand(N*K);
thrust::device_vector&lt;double&gt; crit_expand(N*K);
expand(thrust::constant_iterator&lt;int&gt;(K),
thrust::constant_iterator&lt;int&gt;(K)+N,
nums.begin(),
nums_expand.begin());
expand(thrust::constant_iterator&lt;int&gt;(K),
thrust::constant_iterator&lt;int&gt;(K)+N,
crit.begin(),
crit_expand.begin());
sdkCreateTimer(&amp;timer);
sdkStartTimer(&amp;timer);
thrust::transform(thrust::make_zip_iterator(vals.begin(), 
data.begin(),
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1/K), // for N
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1%K), // for K
nums_expand.begin(),
crit_expand.begin()),
thrust::make_zip_iterator(vals.end(), 
data.end(),
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1/K) + N*K, 
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1%K) + N*K, 
nums_expand.end(),
crit_expand.end()),
res.begin(),
myOp());
sdkStopTimer(&amp;timer);
printSdkTimer(&amp;timer,1);
cudaDeviceSynchronize();
sdkResetTimer(&amp;timer);
sdkStartTimer(&amp;timer);
//--- 2) kernel
double *raw_vals2 = thrust::raw_pointer_cast(vals2.data());
double *raw_data2 = thrust::raw_pointer_cast(data2.data());
double *raw_crit2 = thrust::raw_pointer_cast(crit2.data());
int    *raw_nums2 = thrust::raw_pointer_cast(nums2.data());
int Nthreads = 256;
int Nblocks = (N*K - 1) / Nthreads + 1;
myOpKernel&lt;&lt;&lt;Nblocks,Nthreads&gt;&gt;&gt;(raw_vals2, raw_data2, raw_nums2, raw_crit2, N, K);
cudaDeviceSynchronize();
sdkStopTimer(&amp;timer);
printSdkTimer(&amp;timer,1);
sdkDeleteTimer(&amp;timer);
return 0;
}

答案1

得分: 2

以下是修改后的基准代码,包含改进的内核。使用以下命令编译:nvcc --extended-lambda -arch=sm_89 -O3 main.cu -o main

由于您提供的代码中包含许多代码和注释,我将只提供已翻译的部分,以免混淆。以下是翻译后的代码部分:

// 编译命令
// Since the timer is not included in your code, I use cudaEvents instead.
// Data buffers are initialized in host vectors to avoid millions of memcopies.
// I also noticed that the thrust approach does not produce identical results to your kernel for large N.

// 我添加了两个内核:myOpKernel3 和 myOpKernel4,它们执行不同的计算任务。
// 对于 nums[i] = 256、nums[i] = 128 和 nums[i] = 4,分别提供了计算时间。

// 注意:我没有测试非均匀分段大小。
// 临时内存分配和 thrust 调用的性能开销可以通过与 thrust 的 `thrust::cuda::par_nosync` 执行策略结合使用自定义分配器来减少。

// 以下是包含的头文件和命名空间声明...
// 在这之后是一些函数和内核的定义...

int main(int argc, char **argv) {
  int N = atoi(argv[1]);
  int K = atoi(argv[2]);
  // 剩余的主函数部分...
}

由于代码太长,无法一次性提供完整的翻译,但我已经提供了主要部分的翻译。如果您需要更多特定部分的翻译或有其他问题,请随时提问。

英文:

Below is some modified benchmark code with improved kernels. Compiled with nvcc --extended-lambda -arch=sm_89 -O3 main.cu -o main

Since the timer is not included in your code, I use cudaEvents instead.
Data buffers are initialized in host vectors to avoid millions of memcopies.
I also noticed that the thrust approach does not produce identical results to your kernel for large N.

I added two kernels. myOpKernel3 simply uses 1 threadblock per index to access the num[index] values.

myOpKernel4 uses 1 thread per output element. this requires a prefix sum of nums, and the computation of index per thread. I chose to precompute the indices. An alternative approach would be to perform a binary search on the prefix sum within the kernel.

For full segments, i.e. nums[i] = 256, the output is

N 100000 K 256
expandtime 6.28806 ms
thrusttransformtime 1.05165 ms
myOpKerneltime 2.04301 ms
results from thrust and myOpKernel do not match
myOpKerneltime3 0.662016 ms
myOpKernel4_setuptime 0.723872 ms
myOpKerneltime4 0.785408 ms

For nums[i] = 128

N 100000 K 256
expandtime 6.2976 ms
thrusttransformtime 1.05472 ms
myOpKerneltime 1.04054 ms
results from thrust and myOpKernel do not match
myOpKerneltime3 0.337952 ms
myOpKernel4_setuptime 0.369152 ms
myOpKerneltime4 0.386048 ms

nums[i] = 4

N 100000 K 256
expandtime 6.2936 ms
thrusttransformtime 1.05165 ms
myOpKerneltime 0.273536 ms
results from thrust and myOpKernel do not match
myOpKerneltime3 0.119104 ms
myOpKernel4_setuptime 0.293824 ms
myOpKerneltime4 0.066848 ms

I did not test non-uniform segment sizes.
Note that performance costs of temporary memory allocations and thrust calls can be reduced by using custom allocators in conjunction with thrust's thrust::cuda::par_nosync execution policy.

//https://stackoverflow.com/questions/31955505/can-thrust-transform-reduce-work-with-2-arrays%5B/url%5D
#include &lt;thrust/device_vector.h&gt;
#include &lt;thrust/reduce.h&gt;
#include &lt;thrust/gather.h&gt;
#include &lt;thrust/copy.h&gt;
#include &lt;thrust/iterator/transform_iterator.h&gt;
#include &lt;thrust/iterator/counting_iterator.h&gt;
#include &lt;thrust/iterator/discard_iterator.h&gt;
#include &lt;thrust/execution_policy.h&gt;
#include &lt;thrust/host_vector.h&gt;
#include &lt;iostream&gt;
#include &lt;iomanip&gt;
#include &lt;thrust/transform.h&gt;
#include &lt;thrust/functional.h&gt;
/////////  https://github.com/NVIDIA/thrust/blob/master/examples/expand.cu //////////
template &lt;typename InputIterator1,
typename InputIterator2,
typename OutputIterator&gt;
OutputIterator expand(InputIterator1 first1,
InputIterator1 last1,
InputIterator2 first2,
OutputIterator output)
{
typedef typename thrust::iterator_difference&lt;InputIterator1&gt;::type difference_type;
difference_type input_size  = thrust::distance(first1, last1);
difference_type output_size = thrust::reduce(first1, last1);
// scan the counts to obtain output offsets for each input element
thrust::device_vector&lt;difference_type&gt; output_offsets(input_size, 0);
thrust::exclusive_scan(first1, last1, output_offsets.begin()); 
// scatter the nonzero counts into their corresponding output positions
thrust::device_vector&lt;difference_type&gt; output_indices(output_size, 0);
thrust::scatter_if
(thrust::counting_iterator&lt;difference_type&gt;(0),
thrust::counting_iterator&lt;difference_type&gt;(input_size),
output_offsets.begin(),
first1,
output_indices.begin());
// compute max-scan over the output indices, filling in the holes
thrust::inclusive_scan
(output_indices.begin(),
output_indices.end(),
output_indices.begin(),
thrust::maximum&lt;difference_type&gt;());
// gather input values according to index array (output = first2[output_indices])
thrust::gather(output_indices.begin(),
output_indices.end(),
first2,
output);
// return output + output_size
thrust::advance(output, output_size);
return output;
}
/////////////////////////////////////////////////////////////////////////////////////
template&lt;typename T&gt;
void print_vector(T&amp; vec) {
for (const auto&amp; elem : vec) {
std::cout &lt;&lt; std::setw(5) &lt;&lt; elem; 
}
std::cout &lt;&lt; std::endl;
}
struct myOp : public thrust::unary_function&lt;thrust::tuple&lt;double,double,int,int,int,double&gt;, double&gt; {
// vals   data   1/K 1%K nums crit
__host__ __device__             // 0      1      2   3   4    5
double operator() (const thrust::tuple&lt;double,double,int,int,int, double&gt; &amp;t) const {
double res;
if (thrust::get&lt;2&gt;(t) &gt;= thrust::get&lt;4&gt;(t)) {
res = thrust::get&lt;0&gt;(t);  // do nothing
}else {
if (thrust::get&lt;3&gt;(t) &gt;= thrust::get&lt;4&gt;(t)) {
res = thrust::get&lt;0&gt;(t); // do nothing
}else {
double tmp = thrust::get&lt;0&gt;(t);
if (thrust::get&lt;1&gt;(t) &gt;= thrust::get&lt;5&gt;(t)) { tmp = 0.0; }
res = tmp;
}
}
return res;
}
};
__global__ void myOpKernel(double *vals, double *data, int *nums, double *crit, int N, int K) {
int index = blockIdx.x*blockDim.x + threadIdx.x;
if (index &gt;= N) return;
double _crit = crit[index];
for (int i=0; i&lt;nums[index]; i++) {
double _res = vals[index*K + i];
if (data[index*K + i] &gt;= _crit) { _res = 0.0; }  
vals[index*K + i] = _res;
}
}
__global__ void myOpKernel3(
double * __restrict__ vals, 
const double * __restrict__ data, 
const int * __restrict__ nums, 
const double * __restrict__ crit, 
int N, 
int K
){
for(int index = blockIdx.x; index &lt; N; index += gridDim.x){   
const double _crit = crit[index];
const int num = nums[index];
for(int i = threadIdx.x; i &lt; num; i += blockDim.x){
double _res = vals[index*K + i];
if (data[index*K + i] &gt;= _crit) { _res = 0.0; }          
vals[index*K + i] = _res;
}
}
}
__global__ 
void myOpKernel4(
double * __restrict__ vals, 
const double * __restrict__ data, 
const int * __restrict__ nums, 
const double * __restrict__ crit, 
const int* __restrict__ numsPrefixSum,
const int* __restrict__ indexForThread,
int totalnums,
int N, 
int K
){
const int tid = threadIdx.x + blockIdx.x * blockDim.x;
const int numValid = totalnums;
if(tid &lt; numValid){
const int index = indexForThread[tid];
const int i = tid - numsPrefixSum[index];
const double _crit = crit[index];
double _res = vals[index*K + i];
if (data[index*K + i] &gt;= _crit) { _res = 0.0; }          
vals[index*K + i] = _res;
}
}
int main(int argc, char **argv) {
using namespace thrust::placeholders;
int N = atoi(argv[1]); 
int K = atoi(argv[2]); 
std::cout &lt;&lt; &quot;N &quot; &lt;&lt; N &lt;&lt; &quot; K &quot; &lt;&lt; K &lt;&lt; std::endl;
thrust::host_vector&lt;double&gt; h_vals(N*K);
thrust::host_vector&lt;double&gt; h_data(N*K);
thrust::host_vector&lt;double&gt; h_crit(N);
thrust::host_vector&lt;int&gt;    h_nums(N);
for (int i=0; i&lt;N; i++) {
h_crit[i] = 101.0; // arbitrary
h_nums[i] = 4;   // arbitrary number less than 256
for (int j=0; j&lt;K; j++) {
h_vals[i*K + j] = (double)(i*K + j); // arbitrary
h_data[i*K + j] = (double)(i*K + j); // arbitrary
}
}
thrust::device_vector&lt;double&gt; vals = h_vals;
thrust::device_vector&lt;double&gt; data = h_data;
thrust::device_vector&lt;double&gt; crit = h_crit;
thrust::device_vector&lt;int&gt;    nums = h_nums;
thrust::device_vector&lt;double&gt; res(vals.size());
cudaEvent_t eventA; cudaEventCreate(&amp;eventA);
cudaEvent_t eventB; cudaEventCreate(&amp;eventB);
//--- 1) thrust
cudaEventRecord(eventA);
thrust::device_vector&lt;int&gt;    nums_expand(N*K);
thrust::device_vector&lt;double&gt; crit_expand(N*K);
expand(thrust::constant_iterator&lt;int&gt;(K),
thrust::constant_iterator&lt;int&gt;(K)+N,
nums.begin(),
nums_expand.begin());
expand(thrust::constant_iterator&lt;int&gt;(K),
thrust::constant_iterator&lt;int&gt;(K)+N,
crit.begin(),
crit_expand.begin());
cudaEventRecord(eventB);
cudaEventSynchronize(eventB);
float expandtime; cudaEventElapsedTime(&amp;expandtime, eventA, eventB);
std::cout &lt;&lt; &quot;expandtime &quot; &lt;&lt; expandtime &lt;&lt; &quot; ms\n&quot;;
cudaEventRecord(eventA);
thrust::transform(thrust::make_zip_iterator(vals.begin(), 
data.begin(),
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1/K), // for N
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1%K), // for K
nums_expand.begin(),
crit_expand.begin()),
thrust::make_zip_iterator(vals.end(), 
data.end(),
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1/K) + N*K, 
thrust::make_transform_iterator(thrust::counting_iterator&lt;int&gt;(0), _1%K) + N*K, 
nums_expand.end(),
crit_expand.end()),
res.begin(),
myOp());
cudaEventRecord(eventB);
cudaEventSynchronize(eventB);
float thrusttransformtime; cudaEventElapsedTime(&amp;thrusttransformtime, eventA, eventB);
std::cout &lt;&lt; &quot;thrusttransformtime &quot; &lt;&lt; thrusttransformtime &lt;&lt; &quot; ms\n&quot;;
cudaDeviceSynchronize();
//   std::cout &lt;&lt; &quot;vals after thrust\n&quot;;
//   for(int i = 0; i &lt; res.size(); i++){
//     std::cout &lt;&lt; res[i] &lt;&lt; &quot; &quot;;
//   }
//   std::cout &lt;&lt; &quot;\n&quot;;
//--- 2) kernel
thrust::device_vector&lt;double&gt; vals2 = h_vals;
thrust::device_vector&lt;double&gt; data2 = h_data;
thrust::device_vector&lt;double&gt; crit2 = h_crit;
thrust::device_vector&lt;int&gt;    nums2 = h_nums;
cudaEventRecord(eventA);
int Nthreads = 256;
int Nblocks = (N*K - 1) / Nthreads + 1;
myOpKernel&lt;&lt;&lt;Nblocks,Nthreads&gt;&gt;&gt;(vals2.data().get(), data2.data().get(), nums2.data().get(), crit2.data().get(), N, K);
cudaEventRecord(eventB);
cudaEventSynchronize(eventB);
float myOpKerneltime; cudaEventElapsedTime(&amp;myOpKerneltime, eventA, eventB);
std::cout &lt;&lt; &quot;myOpKerneltime &quot; &lt;&lt; myOpKerneltime &lt;&lt; &quot; ms\n&quot;;
cudaDeviceSynchronize();
if(res == vals2){
std::cout &lt;&lt; &quot;results from thrust and myOpKernel match\n&quot;;
}else{
std::cout &lt;&lt; &quot;results from thrust and myOpKernel do not match\n&quot;;
}
{
//1 block per index
thrust::device_vector&lt;double&gt; vals_new = h_vals;
thrust::device_vector&lt;double&gt; data_new = h_data;
thrust::device_vector&lt;double&gt; crit_new = h_crit;
thrust::device_vector&lt;int&gt;    nums_new = h_nums;
cudaEventRecord(eventA);
int Nthreads = 256;
int Nblocks = N;
myOpKernel3&lt;&lt;&lt;Nblocks,Nthreads&gt;&gt;&gt;(vals_new.data().get(), data_new.data().get(), nums_new.data().get(), crit_new.data().get(), N, K);
cudaEventRecord(eventB);
cudaEventSynchronize(eventB);
float myOpKerneltime3; cudaEventElapsedTime(&amp;myOpKerneltime3, eventA, eventB);
std::cout &lt;&lt; &quot;myOpKerneltime3 &quot; &lt;&lt; myOpKerneltime3 &lt;&lt; &quot; ms\n&quot;;
cudaDeviceSynchronize();
assert(vals_new == vals2);
}
{
//1 thread per output position
thrust::device_vector&lt;double&gt; vals_new = h_vals;
thrust::device_vector&lt;double&gt; data_new = h_data;
thrust::device_vector&lt;double&gt; crit_new = h_crit;
thrust::device_vector&lt;int&gt;    nums_new = h_nums;
cudaEventRecord(eventA);
thrust::device_vector&lt;int&gt; numsPrefixSum(N+1);
numsPrefixSum[0] = 0;
thrust::inclusive_scan(
nums_new.begin(),
nums_new.end(),
numsPrefixSum.begin() + 1
);
const int totalNums = numsPrefixSum.back();
thrust::device_vector&lt;int&gt; indexForThread(totalNums, 0);
thrust::scatter_if(
thrust::make_counting_iterator(0),
thrust::make_counting_iterator(0) + N, 
numsPrefixSum.begin(),
thrust::make_transform_iterator(
nums_new.begin(), 
[] __host__ __device__ (int i){return i &gt; 0;}
),
indexForThread.begin()
);
thrust::inclusive_scan(
indexForThread.begin(), 
indexForThread.begin() + totalNums, 
indexForThread.begin(), 
thrust::maximum&lt;int&gt;{}
);
cudaEventRecord(eventB);
cudaEventSynchronize(eventB);
float myOpKernel4_setuptime; cudaEventElapsedTime(&amp;myOpKernel4_setuptime, eventA, eventB);
std::cout &lt;&lt; &quot;myOpKernel4_setuptime &quot; &lt;&lt; myOpKernel4_setuptime &lt;&lt; &quot; ms\n&quot;;
cudaEventRecord(eventA);
int Nthreads = 256;
int Nblocks = (totalNums + Nthreads - 1) / Nthreads;
myOpKernel4&lt;&lt;&lt;Nblocks,Nthreads&gt;&gt;&gt;(
vals_new.data().get(), 
data_new.data().get(), 
nums_new.data().get(), 
crit_new.data().get(),
numsPrefixSum.data().get(),
indexForThread.data().get(),
totalNums,
N, 
K
);
cudaEventRecord(eventB);
cudaEventSynchronize(eventB);
float myOpKerneltime4; cudaEventElapsedTime(&amp;myOpKerneltime4, eventA, eventB);
std::cout &lt;&lt; &quot;myOpKerneltime4 &quot; &lt;&lt; myOpKerneltime4 &lt;&lt; &quot; ms\n&quot;;
cudaDeviceSynchronize();
assert(vals_new == vals2);
}
cudaEventDestroy(eventA);
cudaEventDestroy(eventB);
return 0;
}

huangapple
  • 本文由 发表于 2023年5月17日 15:18:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76269455.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定