在CUDA中获取多个数组的唯一元素
Get unique elements of multiple arrays in CUDA
问题来了:
有多个数组,例如2000个数组,但每个数组中只有256个整数。并且整数的范围相当大,例如[0, 1000000]。
我想获取每个数组的唯一元素,换句话说,删除重复的元素。
我有 2 个解决方案:
使用Thrust为每个数组获取唯一元素,所以我必须做2000次thrust::unique
。但是每个数组都比较小,这种方式可能不会获得很好的性能。
在cuda内核中实现hashtable,使用2000个块,每个块256个线程。并利用共享内存实现散列table,然后每一个块都会产生一个元素唯一的数组
以上两种方法看起来不专业,有没有CUDA优雅的解决方法?
如果您像在这个 SO 问题中那样修改数据,则可以使用 thrust::unique
:Segmented Sort with CUDPP/Thrust
为简化起见,我们假设每个数组包含 per_array
个元素,并且总共有 array_num
个数组。每个元素都在 [0,max_element]
.
范围内
带有 per_array=4
、array_num=3
和 max_element=2
的演示 data
可能如下所示:
data = {1,0,1,2},{2,2,0,0},{0,0,0,0}
为了表示每个元素对相应数组的成员资格,我们使用以下 flags
:
flags = {0,0,0,0},{1 1 1 1},{2,2,2,2}
为了获得分段数据集的每个数组的唯一元素,我们需要执行以下步骤:
变换data
使每个数组i
的元素都在唯一范围内[i*2*max_element,i*2*max_element+max_element]
data = data + flags*2*max_element
data = {1,0,1,2},{6,6,4,4},{8,8,8,8}
对转换后的数据进行排序:
data = {0,0,1,2},{4,4,6,6},{8,8,8,8}
应用thrust::unique_by_key
,使用data
作为键,flags
作为值:
data = {0,1,2}{4,6}{8}
flags = {0,0,0}{1,1}{2}
将data
转换回原始值:
data = data - flags*2*max_element
data = {0,1,2}{0,2}{0}
max_element
的最大值受用于表示 data
的整数大小的限制。如果它是 n
位的无符号整数:
max_max_element(n,array_num) = 2^n/(2*(array_num-1)+1)
鉴于您的 array_num=2000
,您将获得以下 32 位和 64 位无符号整数限制:
max_max_element(32,2000) = 1074010
max_max_element(64,2000) = 4612839228234447
下面的代码实现了上面的步骤:
unique_per_array.cu
#include <thrust/device_vector.h>
#include <thrust/extrema.h>
#include <thrust/transform.h>
#include <thrust/iterator/counting_iterator.h>
#include <thrust/functional.h>
#include <thrust/sort.h>
#include <thrust/unique.h>
#include <thrust/copy.h>
#include <iostream>
#include <cstdint>
#define PRINTER(name) print(#name, (name))
template <template <typename...> class V, typename T, typename ...Args>
void print(const char* name, const V<T,Args...> & v)
{
std::cout << name << ":\t";
thrust::copy(v.begin(), v.end(), std::ostream_iterator<T>(std::cout, "\t"));
std::cout << std::endl;
}
int main()
{
typedef uint32_t Integer;
const std::size_t per_array = 4;
const std::size_t array_num = 3;
const std::size_t total_count = array_num * per_array;
Integer demo_data[] = {1,0,1,2,2,2,0,0,0,0,0,0};
thrust::device_vector<Integer> data(demo_data, demo_data+total_count);
PRINTER(data);
// if max_element is known for your problem,
// you don't need the following operation
Integer max_element = *(thrust::max_element(data.begin(), data.end()));
std::cout << "max_element=" << max_element << std::endl;
using namespace thrust::placeholders;
// create the flags
// could be a smaller integer type as well
thrust::device_vector<uint32_t> flags(total_count);
thrust::counting_iterator<uint32_t> flags_cit(0);
thrust::transform(flags_cit,
flags_cit + total_count,
flags.begin(),
_1 / per_array);
PRINTER(flags);
// 1. transform data into unique ranges
thrust::transform(data.begin(),
data.end(),
thrust::counting_iterator<Integer>(0),
data.begin(),
_1 + (_2/per_array)*2*max_element);
PRINTER(data);
// 2. sort the transformed data
thrust::sort(data.begin(), data.end());
PRINTER(data);
// 3. eliminate duplicates per array
auto new_end = thrust::unique_by_key(data.begin(),
data.end(),
flags.begin());
uint32_t new_size = new_end.first - data.begin();
data.resize(new_size);
flags.resize(new_size);
PRINTER(data);
PRINTER(flags);
// 4. transform data back
thrust::transform(data.begin(),
data.end(),
flags.begin(),
data.begin(),
_1 - _2*2*max_element);
PRINTER(data);
}
编译和 运行 产量:
$ nvcc -std=c++11 unique_per_array.cu -o unique_per_array && ./unique_per_array
data: 1 0 1 2 2 2 0 0 0 0 0 0
max_element=2
flags: 0 0 0 0 1 1 1 1 2 2 2 2
data: 1 0 1 2 6 6 4 4 8 8 8 8
data: 0 1 1 2 4 4 6 6 8 8 8 8
data: 0 1 2 4 6 8
flags: 0 0 0 1 1 2
data: 0 1 2 0 2 0
还有一件事:
在thrust development version there is an improvement implemented for thrust::unique*
which improves performance by around 25 %。如果您希望获得更好的性能,您可能想尝试这个版本。
我认为 thrust::unique_copy() 可以帮助您做到这一点。
问题来了: 有多个数组,例如2000个数组,但每个数组中只有256个整数。并且整数的范围相当大,例如[0, 1000000]。
我想获取每个数组的唯一元素,换句话说,删除重复的元素。 我有 2 个解决方案:
使用Thrust为每个数组获取唯一元素,所以我必须做2000次
thrust::unique
。但是每个数组都比较小,这种方式可能不会获得很好的性能。在cuda内核中实现hashtable,使用2000个块,每个块256个线程。并利用共享内存实现散列table,然后每一个块都会产生一个元素唯一的数组
以上两种方法看起来不专业,有没有CUDA优雅的解决方法?
如果您像在这个 SO 问题中那样修改数据,则可以使用 thrust::unique
:Segmented Sort with CUDPP/Thrust
为简化起见,我们假设每个数组包含 per_array
个元素,并且总共有 array_num
个数组。每个元素都在 [0,max_element]
.
带有 per_array=4
、array_num=3
和 max_element=2
的演示 data
可能如下所示:
data = {1,0,1,2},{2,2,0,0},{0,0,0,0}
为了表示每个元素对相应数组的成员资格,我们使用以下 flags
:
flags = {0,0,0,0},{1 1 1 1},{2,2,2,2}
为了获得分段数据集的每个数组的唯一元素,我们需要执行以下步骤:
变换
data
使每个数组i
的元素都在唯一范围内[i*2*max_element,i*2*max_element+max_element]
data = data + flags*2*max_element data = {1,0,1,2},{6,6,4,4},{8,8,8,8}
对转换后的数据进行排序:
data = {0,0,1,2},{4,4,6,6},{8,8,8,8}
应用
thrust::unique_by_key
,使用data
作为键,flags
作为值:data = {0,1,2}{4,6}{8} flags = {0,0,0}{1,1}{2}
将
data
转换回原始值:data = data - flags*2*max_element data = {0,1,2}{0,2}{0}
max_element
的最大值受用于表示 data
的整数大小的限制。如果它是 n
位的无符号整数:
max_max_element(n,array_num) = 2^n/(2*(array_num-1)+1)
鉴于您的 array_num=2000
,您将获得以下 32 位和 64 位无符号整数限制:
max_max_element(32,2000) = 1074010
max_max_element(64,2000) = 4612839228234447
下面的代码实现了上面的步骤:
unique_per_array.cu
#include <thrust/device_vector.h>
#include <thrust/extrema.h>
#include <thrust/transform.h>
#include <thrust/iterator/counting_iterator.h>
#include <thrust/functional.h>
#include <thrust/sort.h>
#include <thrust/unique.h>
#include <thrust/copy.h>
#include <iostream>
#include <cstdint>
#define PRINTER(name) print(#name, (name))
template <template <typename...> class V, typename T, typename ...Args>
void print(const char* name, const V<T,Args...> & v)
{
std::cout << name << ":\t";
thrust::copy(v.begin(), v.end(), std::ostream_iterator<T>(std::cout, "\t"));
std::cout << std::endl;
}
int main()
{
typedef uint32_t Integer;
const std::size_t per_array = 4;
const std::size_t array_num = 3;
const std::size_t total_count = array_num * per_array;
Integer demo_data[] = {1,0,1,2,2,2,0,0,0,0,0,0};
thrust::device_vector<Integer> data(demo_data, demo_data+total_count);
PRINTER(data);
// if max_element is known for your problem,
// you don't need the following operation
Integer max_element = *(thrust::max_element(data.begin(), data.end()));
std::cout << "max_element=" << max_element << std::endl;
using namespace thrust::placeholders;
// create the flags
// could be a smaller integer type as well
thrust::device_vector<uint32_t> flags(total_count);
thrust::counting_iterator<uint32_t> flags_cit(0);
thrust::transform(flags_cit,
flags_cit + total_count,
flags.begin(),
_1 / per_array);
PRINTER(flags);
// 1. transform data into unique ranges
thrust::transform(data.begin(),
data.end(),
thrust::counting_iterator<Integer>(0),
data.begin(),
_1 + (_2/per_array)*2*max_element);
PRINTER(data);
// 2. sort the transformed data
thrust::sort(data.begin(), data.end());
PRINTER(data);
// 3. eliminate duplicates per array
auto new_end = thrust::unique_by_key(data.begin(),
data.end(),
flags.begin());
uint32_t new_size = new_end.first - data.begin();
data.resize(new_size);
flags.resize(new_size);
PRINTER(data);
PRINTER(flags);
// 4. transform data back
thrust::transform(data.begin(),
data.end(),
flags.begin(),
data.begin(),
_1 - _2*2*max_element);
PRINTER(data);
}
编译和 运行 产量:
$ nvcc -std=c++11 unique_per_array.cu -o unique_per_array && ./unique_per_array
data: 1 0 1 2 2 2 0 0 0 0 0 0
max_element=2
flags: 0 0 0 0 1 1 1 1 2 2 2 2
data: 1 0 1 2 6 6 4 4 8 8 8 8
data: 0 1 1 2 4 4 6 6 8 8 8 8
data: 0 1 2 4 6 8
flags: 0 0 0 1 1 2
data: 0 1 2 0 2 0
还有一件事:
在thrust development version there is an improvement implemented for thrust::unique*
which improves performance by around 25 %。如果您希望获得更好的性能,您可能想尝试这个版本。
我认为 thrust::unique_copy() 可以帮助您做到这一点。