问题描述
我在 MATLAB 中动态创建了大量动态创建的表/矩阵,这些表/矩阵的第一维各不相同,其行表示 6 阶 1-50 范围内整数的(排序)组合。
我想为每个组合分配一个唯一值(哈希、排名),以便我可以检查相同的组合是否出现在不同的表中。不同的组合不允许分配相同的值,即没有冲突。我必须在很多这样的表之间进行很多这样的比较。因此,出于性能原因,我想通过 uint32
操作的矢量化来实现这一点,使其适用于 MATLAB 中的 GPU 加速。
到目前为止我想到的事情:
- 字典排序:不知道如何很好地向量化标准的快速递归算法,唯一的选择似乎是通过行
parfor
,这比其他选项慢。 IIRC,直接显式公式,虽然可矢量化,但需要计算二项式,这又需要log Gamma
函数以避免巨大的阶乘 +double
类型以避免碰撞,如果我没记错的话,即较慢因为它“非常数字化”。 - Cantor 配对函数:可以连续应用 Cantor 配对,这很好,因为它是一个多项式表达式,但它产生远远超过
uint32
的巨大数字,并且绝对比其他选项慢。 - Base 51(无双关语)整数:将组合/行向量
(x_1,...,x_6)
发送到x_1 + x_2 * 51 + ... + x_6 * 51^5
。这是我目前最快的。它很容易矢量化,但不幸的是,对于 50 个元素的 6 级组合,仍然需要uint64
或double
,这比uint32
或single
类型的操作慢。立>
所以,我想,我正在为这些组合寻找一个“聪明”的单射函数,它可以在 uint32
范围内计算并且也可以很好地向量化(在 MATLAB 中)。
任何帮助将不胜感激!
编辑:这里是一个例程,用于对 uint32
、single
和 double
中的排名和搜索进行基准测试。我已经使用 MATLAB 的 gputimeit
产生准确的结果。
% testing parameters:
N = 26;
ord = 5;
% base-(N+1):
basevec_uint32 = gpuArray(repmat(uint32(N+1),1,ord).^cast(0:ord-1,'uint32'));
basevec_single = cast(basevec_uint32,'single').';
basevec_double = cast(basevec_uint32,'double').';
% highest hash value:
max_hash_value = gpuArray(cast(N-ord+1:N,'single'))*basevec_single
% benchmark GPU-accelerated base-(N+1) ranking for uint32,single,and double:
X_uint16 = randi(N,15000000,ord,'uint16','gpuArray');
X_uint32 = cast(X_uint16,'uint32');
X_single = cast(X_uint16,'single');
X_double = cast(X_uint16,'double');
Y_uint16 = randi(N,5000000,'gpuArray');
Y_uint32 = cast(Y_uint16,'uint32');
Y_single = cast(Y_uint16,'single');
Y_double = cast(Y_uint16,'double');
ranking_uint32 = @() sum(bsxfun(@times,X_uint32,basevec_uint32),2,'native');
ranking_single = @() X_single*basevec_single;
ranking_double = @() X_double*basevec_double;
disp('ranking in uint32:'); gputimeit(ranking_uint32,1)
disp('ranking in single:'); gputimeit(ranking_single,1)
disp('ranking in double:'); gputimeit(ranking_double,1)
% benchmark GPU-accelerated searching in uint32,and double matrices:
X_uint32_ranks = myfun_uint32(); Y_uint32_ranks = sum(bsxfun(@times,Y_uint32,'native');
X_single_ranks = myfun_single(); Y_single_ranks = Y_single*basevec_single;
X_double_hash = myfun_double(); Y_double_ranks = Y_double*basevec_double;
search_uint32 = @() ismember(X_uint32_ranks,Y_uint32_ranks);
search_single = @() ismember(X_single_ranks,Y_single_ranks);
search_double = @() ismember(X_double_ranks,Y_double_ranks);
disp('searching in uint32:'); gputimeit(search_uint32,1)
disp('searching in single:'); gputimeit(search_single,1)
disp('searching in double:'); gputimeit(search_double,1)
我使用的是 nVidia GTX 1060,它在 Windows 下为我提供了以下基准测试结果(但我不知道 WDDM 对 gputimeit
的影响有多大):
>> test1
max_rank_value =
gpuArray single
14327680
ranking in uint32:
ans =
0.0129
ranking in single:
ans =
0.0063
ranking in double:
ans =
0.0108
searching in uint32:
ans =
0.1572
searching in single:
ans =
0.1577
searching in double:
ans =
0.1599
结论:类型的选择不会通过 ismember
(预期)影响 GPU 加速搜索,但是来自矩阵乘法的排名速度有明显差异。 single
中的排名几乎是 double
中排名的两倍,而 uint32
中的排名与 double
中的排名几乎相同。 MATLAB 当前不支持 GPU 加速的 uint32
矩阵乘法,因此我为此使用了替代函数。因此,尝试将 50 个 6 阶元素的组合编码为 single
值更为合理,如果我没记错的话,它的位刚好足以容纳所有 15890700 个组合为 single
整数.
解决方法
对于上一个想法,您几乎已经获得了足够的部分,因此您只需根据订单将其挤出一些即可。由于整个序列已排序,因此每一对也已排序。因此,使用 50×50 查找表将已排序的 (1st,2nd)、(3rd,4th)、(5th,6th) 对映射到 0-1274 之间的数字。
或者,如果您不需要表,有相当简单的显式函数可以将一对 (i,j) 与 j>=i 映射到线性索引。查找上三角或下三角矩阵索引以获取有关这些的详细信息。 (这将是类似于
n*(n+1)/2 - (n-i)*(n-i-1)/2 + j
根据 base-0 或 base-1 索引,会抛出一些 +/-1,在您的情况下,n=50,但我敢肯定,如果直接写出来,我会弄错。)
无论如何,一旦你得到了 0-1274 的三个数字,基数为 1275 的想法就会适合 uint32
。