通过对 MATLAB 中 32 位整数的运算对固定大小的整数组合进行矢量化散列/排序

问题描述

我在 MATLAB 中动态创建了大量动态创建的表/矩阵,这些表/矩阵的第一维各不相同,其行表示 6 阶 1-50 范围内整数的(排序)组合。

我想为每个组合分配一个唯一值(哈希、排名),以便我可以检查相同的组合是否出现在不同的表中。不同的组合不允许分配相同的值,即没有冲突。我必须在很多这样的表之间进行很多这样的比较。因此,出于性能原因,我想通过 uint32 操作的矢量化来实现这一点,使其适用于 MATLAB 中的 GPU 加速。

到目前为止我想到的事情:

  1. 字典排序:不知道如何很好地向量化标准的快速递归算法,唯一的选择似乎是通过行parfor,这比其他选项慢。 IIRC,直接显式公式,虽然可矢量化,但需要计算二项式,这又需要 log Gamma 函数以避免巨大的阶乘 + double 类型以避免碰撞,如果我没记错的话,即较慢因为它“非常数字化”。
  2. Cantor 配对函数:可以连续应用 Cantor 配对,这很好,因为它是一个多项式表达式,但它产生远远超过 uint32 的巨大数字,并且绝对比其他选项慢。
  3. Base 51(无双关语)整数:将组合/行向量 (x_1,...,x_6) 发送到 x_1 + x_2 * 51 + ... + x_6 * 51^5。这是我目前最快的。它很容易矢量化,但不幸的是,对于 50 个元素的 6 级组合,仍然需要 uint64double,这比 uint32single 类型的操作慢。立>

所以,我想,我正在为这些组合寻找一个“聪明”的单射函数,它可以在 uint32 范围内计算并且也可以很好地向量化(在 MATLAB 中)。

任何帮助将不胜感激!

编辑:这里是一个例程,用于对 uint32singledouble 中的排名和搜索进行基准测试。我已经使用 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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...