如何使用 Thrust 对矩阵的行进行排序?
How to use Thrust to sort the rows of a matrix?
我有一个 5000x500 矩阵,我想用 cuda 分别对每一行进行排序。我可以使用 arrayfire,但这只是 thrust::sort 上的一个 for 循环,效率不高。
https://github.com/arrayfire/arrayfire/blob/devel/src/backend/cuda/kernel/sort.hpp
for(dim_type w = 0; w < val.dims[3]; w++) {
dim_type valW = w * val.strides[3];
for(dim_type z = 0; z < val.dims[2]; z++) {
dim_type valWZ = valW + z * val.strides[2];
for(dim_type y = 0; y < val.dims[1]; y++) {
dim_type valOffset = valWZ + y * val.strides[1];
if(isAscending) {
thrust::sort(val_ptr + valOffset, val_ptr + valOffset + val.dims[0]);
} else {
thrust::sort(val_ptr + valOffset, val_ptr + valOffset + val.dims[0],
thrust::greater<T>());
}
}
}
}
有没有办法在 thrust 中融合操作,以便并行排序 运行?事实上,我正在寻找的是一种将 for 循环迭代融合到其中的通用方法。
我可以想到 2 种可能性,@JaredHoberock 已经建议了其中一种。我不知道在推力中融合 for 循环迭代的通用方法,但第二种方法是更通用的方法。我的猜测是,在这种情况下,第一种方法是两种方法中更快的一种。
使用矢量化排序。如果要通过嵌套 for 循环排序的区域不重叠,则可以使用 2 个背靠背稳定排序操作进行矢量化排序,如 here.
[=45 所述=]
Thrust v1.8(随 CUDA 7 RC 提供,或通过从 thrust github repository includes support for nesting thrust algorithms 直接下载,通过在传递给另一个推力算法的自定义仿函数中包含推力算法调用。如果您使用 thrust::for_each
操作来 select 您需要执行的单个排序,您可以 运行 通过在你传递给 thrust::for_each
.
的函子
这是 3 种方法之间的完整比较:
- 原始的循环排序方法
- vectorized/batch排序
- 嵌套排序
在每种情况下,我们都对相同的 16000 组进行排序,每组 1000 个整数。
$ cat t617.cu
#include <thrust/device_vector.h>
#include <thrust/device_ptr.h>
#include <thrust/host_vector.h>
#include <thrust/sort.h>
#include <thrust/execution_policy.h>
#include <thrust/generate.h>
#include <thrust/equal.h>
#include <thrust/sequence.h>
#include <thrust/for_each.h>
#include <iostream>
#include <stdlib.h>
#define NSORTS 16000
#define DSIZE 1000
int my_mod_start = 0;
int my_mod(){
return (my_mod_start++)/DSIZE;
}
bool validate(thrust::device_vector<int> &d1, thrust::device_vector<int> &d2){
return thrust::equal(d1.begin(), d1.end(), d2.begin());
}
struct sort_functor
{
thrust::device_ptr<int> data;
int dsize;
__host__ __device__
void operator()(int start_idx)
{
thrust::sort(thrust::device, data+(dsize*start_idx), data+(dsize*(start_idx+1)));
}
};
#include <time.h>
#include <sys/time.h>
#define USECPSEC 1000000ULL
unsigned long long dtime_usec(unsigned long long start){
timeval tv;
gettimeofday(&tv, 0);
return ((tv.tv_sec*USECPSEC)+tv.tv_usec)-start;
}
int main(){
cudaDeviceSetLimit(cudaLimitMallocHeapSize, (16*DSIZE*NSORTS));
thrust::host_vector<int> h_data(DSIZE*NSORTS);
thrust::generate(h_data.begin(), h_data.end(), rand);
thrust::device_vector<int> d_data = h_data;
// first time a loop
thrust::device_vector<int> d_result1 = d_data;
thrust::device_ptr<int> r1ptr = thrust::device_pointer_cast<int>(d_result1.data());
unsigned long long mytime = dtime_usec(0);
for (int i = 0; i < NSORTS; i++)
thrust::sort(r1ptr+(i*DSIZE), r1ptr+((i+1)*DSIZE));
cudaDeviceSynchronize();
mytime = dtime_usec(mytime);
std::cout << "loop time: " << mytime/(float)USECPSEC << "s" << std::endl;
//vectorized sort
thrust::device_vector<int> d_result2 = d_data;
thrust::host_vector<int> h_segments(DSIZE*NSORTS);
thrust::generate(h_segments.begin(), h_segments.end(), my_mod);
thrust::device_vector<int> d_segments = h_segments;
mytime = dtime_usec(0);
thrust::stable_sort_by_key(d_result2.begin(), d_result2.end(), d_segments.begin());
thrust::stable_sort_by_key(d_segments.begin(), d_segments.end(), d_result2.begin());
cudaDeviceSynchronize();
mytime = dtime_usec(mytime);
std::cout << "vectorized time: " << mytime/(float)USECPSEC << "s" << std::endl;
if (!validate(d_result1, d_result2)) std::cout << "mismatch 1!" << std::endl;
//nested sort
thrust::device_vector<int> d_result3 = d_data;
sort_functor f = {d_result3.data(), DSIZE};
thrust::device_vector<int> idxs(NSORTS);
thrust::sequence(idxs.begin(), idxs.end());
mytime = dtime_usec(0);
thrust::for_each(idxs.begin(), idxs.end(), f);
cudaDeviceSynchronize();
mytime = dtime_usec(mytime);
std::cout << "nested time: " << mytime/(float)USECPSEC << "s" << std::endl;
if (!validate(d_result1, d_result3)) std::cout << "mismatch 2!" << std::endl;
return 0;
}
$ nvcc -arch=sm_20 -std=c++11 -o t617 t617.cu
$ ./t617
loop time: 8.51577s
vectorized time: 0.068802s
nested time: 0.567959s
$
备注:
- 这些结果会因 GPU 的不同而有很大差异。
- "nested" time/method 在支持动态并行的 GPU 上可能会有很大差异,因为这会影响推力 运行 嵌套排序的功能。要使用动态并行性进行测试,请将编译开关从
-arch=sm_20
更改为 -arch=sm_35 -rdc=true -lcudadevrt
- 此代码需要 CUDA 7 RC。我用的是 Fedora 20。
- 嵌套排序方法也会从设备端分配,因此我们必须使用
cudaDeviceSetLimit
大幅增加设备分配堆。
- 如果您正在使用动态并行,并且根据您 运行 所使用的 GPU 类型,
cudaDeviceSetLimit
保留的内存量可能需要增加一个额外因素8.
我有一个 5000x500 矩阵,我想用 cuda 分别对每一行进行排序。我可以使用 arrayfire,但这只是 thrust::sort 上的一个 for 循环,效率不高。
https://github.com/arrayfire/arrayfire/blob/devel/src/backend/cuda/kernel/sort.hpp
for(dim_type w = 0; w < val.dims[3]; w++) {
dim_type valW = w * val.strides[3];
for(dim_type z = 0; z < val.dims[2]; z++) {
dim_type valWZ = valW + z * val.strides[2];
for(dim_type y = 0; y < val.dims[1]; y++) {
dim_type valOffset = valWZ + y * val.strides[1];
if(isAscending) {
thrust::sort(val_ptr + valOffset, val_ptr + valOffset + val.dims[0]);
} else {
thrust::sort(val_ptr + valOffset, val_ptr + valOffset + val.dims[0],
thrust::greater<T>());
}
}
}
}
有没有办法在 thrust 中融合操作,以便并行排序 运行?事实上,我正在寻找的是一种将 for 循环迭代融合到其中的通用方法。
我可以想到 2 种可能性,@JaredHoberock 已经建议了其中一种。我不知道在推力中融合 for 循环迭代的通用方法,但第二种方法是更通用的方法。我的猜测是,在这种情况下,第一种方法是两种方法中更快的一种。
使用矢量化排序。如果要通过嵌套 for 循环排序的区域不重叠,则可以使用 2 个背靠背稳定排序操作进行矢量化排序,如 here.
[=45 所述=]Thrust v1.8(随 CUDA 7 RC 提供,或通过从 thrust github repository includes support for nesting thrust algorithms 直接下载,通过在传递给另一个推力算法的自定义仿函数中包含推力算法调用。如果您使用
thrust::for_each
操作来 select 您需要执行的单个排序,您可以 运行 通过在你传递给thrust::for_each
. 的函子
这是 3 种方法之间的完整比较:
- 原始的循环排序方法
- vectorized/batch排序
- 嵌套排序
在每种情况下,我们都对相同的 16000 组进行排序,每组 1000 个整数。
$ cat t617.cu
#include <thrust/device_vector.h>
#include <thrust/device_ptr.h>
#include <thrust/host_vector.h>
#include <thrust/sort.h>
#include <thrust/execution_policy.h>
#include <thrust/generate.h>
#include <thrust/equal.h>
#include <thrust/sequence.h>
#include <thrust/for_each.h>
#include <iostream>
#include <stdlib.h>
#define NSORTS 16000
#define DSIZE 1000
int my_mod_start = 0;
int my_mod(){
return (my_mod_start++)/DSIZE;
}
bool validate(thrust::device_vector<int> &d1, thrust::device_vector<int> &d2){
return thrust::equal(d1.begin(), d1.end(), d2.begin());
}
struct sort_functor
{
thrust::device_ptr<int> data;
int dsize;
__host__ __device__
void operator()(int start_idx)
{
thrust::sort(thrust::device, data+(dsize*start_idx), data+(dsize*(start_idx+1)));
}
};
#include <time.h>
#include <sys/time.h>
#define USECPSEC 1000000ULL
unsigned long long dtime_usec(unsigned long long start){
timeval tv;
gettimeofday(&tv, 0);
return ((tv.tv_sec*USECPSEC)+tv.tv_usec)-start;
}
int main(){
cudaDeviceSetLimit(cudaLimitMallocHeapSize, (16*DSIZE*NSORTS));
thrust::host_vector<int> h_data(DSIZE*NSORTS);
thrust::generate(h_data.begin(), h_data.end(), rand);
thrust::device_vector<int> d_data = h_data;
// first time a loop
thrust::device_vector<int> d_result1 = d_data;
thrust::device_ptr<int> r1ptr = thrust::device_pointer_cast<int>(d_result1.data());
unsigned long long mytime = dtime_usec(0);
for (int i = 0; i < NSORTS; i++)
thrust::sort(r1ptr+(i*DSIZE), r1ptr+((i+1)*DSIZE));
cudaDeviceSynchronize();
mytime = dtime_usec(mytime);
std::cout << "loop time: " << mytime/(float)USECPSEC << "s" << std::endl;
//vectorized sort
thrust::device_vector<int> d_result2 = d_data;
thrust::host_vector<int> h_segments(DSIZE*NSORTS);
thrust::generate(h_segments.begin(), h_segments.end(), my_mod);
thrust::device_vector<int> d_segments = h_segments;
mytime = dtime_usec(0);
thrust::stable_sort_by_key(d_result2.begin(), d_result2.end(), d_segments.begin());
thrust::stable_sort_by_key(d_segments.begin(), d_segments.end(), d_result2.begin());
cudaDeviceSynchronize();
mytime = dtime_usec(mytime);
std::cout << "vectorized time: " << mytime/(float)USECPSEC << "s" << std::endl;
if (!validate(d_result1, d_result2)) std::cout << "mismatch 1!" << std::endl;
//nested sort
thrust::device_vector<int> d_result3 = d_data;
sort_functor f = {d_result3.data(), DSIZE};
thrust::device_vector<int> idxs(NSORTS);
thrust::sequence(idxs.begin(), idxs.end());
mytime = dtime_usec(0);
thrust::for_each(idxs.begin(), idxs.end(), f);
cudaDeviceSynchronize();
mytime = dtime_usec(mytime);
std::cout << "nested time: " << mytime/(float)USECPSEC << "s" << std::endl;
if (!validate(d_result1, d_result3)) std::cout << "mismatch 2!" << std::endl;
return 0;
}
$ nvcc -arch=sm_20 -std=c++11 -o t617 t617.cu
$ ./t617
loop time: 8.51577s
vectorized time: 0.068802s
nested time: 0.567959s
$
备注:
- 这些结果会因 GPU 的不同而有很大差异。
- "nested" time/method 在支持动态并行的 GPU 上可能会有很大差异,因为这会影响推力 运行 嵌套排序的功能。要使用动态并行性进行测试,请将编译开关从
-arch=sm_20
更改为-arch=sm_35 -rdc=true -lcudadevrt
- 此代码需要 CUDA 7 RC。我用的是 Fedora 20。
- 嵌套排序方法也会从设备端分配,因此我们必须使用
cudaDeviceSetLimit
大幅增加设备分配堆。 - 如果您正在使用动态并行,并且根据您 运行 所使用的 GPU 类型,
cudaDeviceSetLimit
保留的内存量可能需要增加一个额外因素8.