这是使用Thrust 求向量均值和方差的解决方案:
template <typename T> struct square {
__host__ __device__ T operator()(const T& x) const {
return x * x;
}
};
template <typename T> void mean_and_var(T a, int n, double* p_mean, double* p_var) {
double sum = thrust::reduce(a, &a[n], 0.0, thrust::plus<double>());
double sum_square = thrust::transform_reduce(
a,
&a[n],
square<double>(),
0.0,
thrust::plus<double>()
);
double mean = sum / n;
*p_mean = mean;
*p_var = (sum_square / n) - mean*mean;
}
这是一个独立源文件中的解决方案以及一些分析信息,将 CPU 与 GPU 进行比较(注意完全按照要求回答问题,有必要在 6 个不同的位置上调用 mean_and_var 6 次长度为 8000 的向量,但这应该会给你要点):
#include "stdio.h"
#include <thrust/reduce.h>
#include <thrust/device_vector.h>
#define PROFILING_INIT \
cudaEvent_t start, stop; \
float elapsedTime;
#define PROFILING_START \
cudaEventCreate(&start); \
cudaEventCreate(&stop); \
cudaEventRecord(start, 0);
#define PROFILING_STOP \
cudaEventRecord(stop, 0); \
cudaEventSynchronize(stop); \
cudaEventElapsedTime(&elapsedTime, start, stop); \
printf("Time elapsed: %.3g ms\n", elapsedTime);
#define N (6*8000)
// #define N (6*8000*10)
// #define N (6*8000*100)
double a[N];
template <typename T> struct square {
__host__ __device__ T operator()(const T& x) const {
return x * x;
}
};
void mean_and_var_cpu(double* a, int n, double* p_mean, double* p_var) {
double sum = 0, sum_square = 0, mean;
for (int i = 0; i < n; i++) {
sum += a[i];
sum_square += (a[i] * a[i]);
}
mean = sum / n;
*p_mean = mean;
*p_var = (sum_square / n) - mean*mean;
}
template <typename T> void mean_and_var(T a, int n, double* p_mean, double* p_var) {
double sum = thrust::reduce(a, &a[n], 0.0, thrust::plus<double>());
double sum_square = thrust::transform_reduce(a, &a[n], square<double>(), 0.0, thrust::plus<double>());
double mean = sum / n;
*p_mean = mean;
*p_var = (sum_square / n) - mean*mean;
}
int main() {
for (int i = 0; i < N; i++) {
a[i] = i;
}
double mean, var;
PROFILING_INIT;
printf("With thrust:\n");
PROFILING_START;
mean_and_var<double*>(a, N, &mean, &var);
PROFILING_STOP;
printf("Mean = %f, var = %f\n", mean, var);
printf("With thrust, using device memory:\n");
thrust::device_vector<double> a_dev(N);
thrust::copy(a, &a[N], a_dev.begin());
PROFILING_START;
mean_and_var<thrust::device_ptr<double>>(&a_dev[0], N, &mean, &var);
PROFILING_STOP;
printf("Mean = %f, var = %f\n", mean, var);
printf("On CPU:\n");
PROFILING_START;
mean_and_var_cpu(a, N, &mean, &var);
PROFILING_STOP;
printf("Mean = %f, var = %f\n", mean, var);
}
对代码质量(我主要是一名 C 程序员,为了 Thrust 的目的而尝试适应 C++)和缺少 cmets 表示歉意。
在所有情况下,答案都与使用 Python statistics 模块计算的均值和总体方差一致:
import statistics
x = 8000*6
print(statistics.mean(list(range(x))))
# print(statistics.variance(list(range(x))))
print(statistics.pvariance(list(range(x))))
以下是一些分析信息,所有这些信息都是在 Jetson Nano 开发板上执行的:
| N |
Time taken for Thrust (ms) |
Time taken for Thrust using device memory (ms) |
Time taken for naive CPU implementation (ms) |
| 6*8000 |
11.2 |
2.38 |
1.11 |
| 6*8000*10 |
108 |
7.62 |
11.7 |
| 6*8000*100 |
1.12e+03 |
41.7 |
109 |
分析的结论:
- 如果您要使用 Thrust,请确保使用设备内存!
- 对于足够大的输入大小,GPU 比 CPU 快
- 对于较小的输入尺寸,取决于平台,使用 CPU 可能比使用 GPU 更快(如果有人能告诉我为什么/如何改进我的 Thrust 代码以使其在较小的输入尺寸下更快,那会很棒!)