自下而上设置生成和订购

时间:2012-12-30 15:46:40

标签: c arrays algorithm cuda dynamic-programming

关于您认识的任何可能相关的数值方法的任何方法,请在此处发布!

背景

每个集合都有一个values数组,每个值的索引对应于值绑定的集合,因此我将一个集合表示为整数,其中元素表示位位置,例如。其中包含元素1的集合表示为...001,其中1LSB

因此集合只是一个索引而且从不存储,它是动态生成的,它是导致数组中代表集合值的索引的关键。

我所做的是给定一个集合,是任何成对不相交子集的总和值大于该集合的值。例如。如果设置0111的值为3,其中两个子集的值为0100 = 20011 = 2,则此拆分更有利。我为集合的所有子集执行此操作。

给定三个代理,排序是集合编号表示。

val[8] = {0,1,2,4,3,2,4,2} the values is not important, only how they are ordered
          0 0 0 0 1 1 1 1 MSB bit representation of the index
          0 0 1 1 0 0 1 1
          0 1 0 1 0 1 0 1 LSB

111的最佳分裂是011和100,总和为7。 因此,为了获得仅包含第一个元素ergo 001的集合的值,你输入val [1],对于元素1和3(101)的集合,你输入val [5]。

按基数分组时如何排序val数组

val[8] = {0,1,2,3,4,2,4,2}
          0 0 0 1 0 1 1 1 MSB bit representation of the index
          0 0 1 0 1 0 1 1
          0 1 0 0 1 1 0 1 LSB

这里你必须将索引转换为数组中的右边bin,因此对于只有第三个元素(100),val [translate(4)]的集合,它看起来像这样。认为大小> 2 ^ 25个元素的数组。 请查看Improving random memory access when random access is needed以获得进一步说明。

然而,即使我在基数之后对它们进行分组,这也会导致内存中的高级随机访问。目前按基数对它们进行分组,并且生成索引比在集合所代表的数字之后对它们进行排序要慢。

我使用基数分组的集合生成索引的方法是在常量内存中使用pascals三角形,如Determin the lexicographic distance between two integers中的答案所述

设置值在按顺序排列并按四个代理

分组时的位置
n index 1  2  4  8     3  5  6  9  10 12    7  11 13 14    15
        -----------------------------------------------------
MSB     0  0  0  1  |  0  0  0  1  1  1  |  0  1  1  1  |  1
        0  0  1  0  |  0  1  1  0  0  1  |  1  0  1  1  |  1
        0  1  0  0  |  1  0  1  0  1  0  |  1  1  0  1  |  1
LSB     1  0  0  0  |  1  1  0  1  0  0  |  1  1  1  0  |  1

n index表示如果没有以基数排序它将具有的索引。这只是为了显示每个集合的值所在的位置。

整数集表示值数组中的索引,可以通过直接索引(我当前正在做的,提供随机访问)或通过从集合到索引的转换。

想法

不是将一个集合拆分成子集,而是将底部生成集合。例如。而不是将0111拆分为所有成对不相交的子集,我会在某些时候从集合{0100,0011},{0010,0101},{0001,0110}生成。

它应该如何及为何起作用

假设我们想要评估基数为3的所有分裂,ergo集7,11,13,14。由于分割一组基数3的唯一方法是分成基数1和2的集合,我们需要评估基数1和2的所有不相交子集中的任何一个的总和是否大于这些集合的并集。

需要的符号(可能有点缺陷):

|C|=n,∀ a,b : a ∪ b = C , a ∩ b ={Ø}, |a|+|b| = n

因此,通过使用对每个线程的合并内存访问来读取值,对于形成一组基数n的每个子集,检查它的值是否大于形成的集合,如果是,则更新值。

简单示例,如果n = 2,那么您应该读入所有带基数为1的值,并执行这些集合的所有组合并相应地更新。这个例子很简单,因为所有集都是不相交的:

pseudo code for 4 threads, input card1 is pointer to array of sets |s| =1
__shared__ int value[4];
tid = threadIdx.x;
value[tid] = card1[tid]; // coalesced memory access
int thvalue = value[tid]; // holds the value for the thread, to avoid bank conflict
int rvalue[blockDim.x/2]= 0; //holds the sum
int i = blockDim.x;
int x = 0;
//reduction loop that dont generate duplicate sets
for(;i>0;i>>=1) {
    if(tid < i) {
        x++;
        rvalue[x-1] = value[(tid+x)%blockDim.x] + thvalue; 
    }
}
for(i = 0; i < x; i++) {
    int index = getindex(tid,i,1); //gets the index for the set it generated, 1 represent the cardinality
    if(output[index] < rvalue[i])
        output[index] = rvalue[i];
}

减少循环的迭代

Thread set specific for thread  first iteration second iteration 
0      0001                     0001 + 0010     0001 + 0100
1      0010                     0010 + 0100     0010 + 1000
2      0100                     0100 + 1000     none
3      1000                     1000 + 0001     none

如您所见,它已获取构成基数2的所有子集的所有值。

然而问题是,由于并非所有集合都是不相交的,因此生成大于2的基数集更加棘手。例如。 0001和0011不是不相交的。

请注意,我不会将设置存储在任何位置,只会存储设置的值。

最后

如果考虑到这一点,您将如何创建一个读取内存合并的算法,并从不相交的子集生成所有集合。在不检查子集是否不相交的情况下,它应该是完全确定的。

赏金

该算法应该被描述为具有标记出的不同步骤的文本,或伪代码。

应该通过实例证明它有效。并不是说这个算法最多可以达到n ^ 32套,所以需要很好地扩展。

允许将算法吐出到两个或更多个实例,例如。一个用于偶数,一个用于奇数。

我很乐意参考有关您使用的技术的消息来源。

算法应使用尽可能少的分配和指令,并应避免任何分歧。但是如果你认为你有一个偶数 - 虽然你有很多这个,尝试发布,我会很高兴任何信息。

如果以其他方式订购,但它仍然按照我的描述运作,我建议您在此处发布,任何帮助都非常有用

请询问是否有任何不清楚的内容。

TL / DR简单说明

我有一个带有值的数组Zi中的索引Z[i]代表一个整数集,具体取决于Z的排序。值按以下方式分组:基数,并通过二进制词典排列排序 - &gt;集合值所在的位置1,2,4,3,5,6,7&lt; - 所以我使用一个函数(我实现了这个函数)将索引转换为正确的索引。例如。设置3->指数4。

通过使用基数分组的集合的值,我想要的是,想要查看任何成对不相交集合值是否大于它们形成的集合。

E.g。 |a| = 3, |b|+|c| =3, b ∩ c ={Ø}, |b| =1因此,请X bX类型的值以及c类型b数量的值,查找{{1}的所有不相交子集来自类型c(基数3的集合)的{}和a并得到它们的总和。继续,直到所有集合已生成&#34;

供参考

Hamming weight based indexing

Determin the lexicographic distance between two integers

Improving random memory access when random access is needed

2 个答案:

答案 0 :(得分:1)

我不知道这是否会对你有所帮助,但我在 Hacker's Delight中找到了无分支计数 - 所有1位字的功能 似乎可能有助于您确定集合的基数:

int pop(unsigned int x) {
    x = x - ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    x = x + (x >> 8);
    x = x + (x >> 16);
    return x & 0x0000003F;
}

在文中,Warren声称上述序列可以编译为少至21条指令。 但是,在i7开发机器上使用MSVC 2010时,我检查了这个功能的反汇编,发现它实际计算的时钟大约为22条,总共有33条指令(计数堆栈操作)。在现代CPU或GPU上,它应该非常快,因为它没有分支。

答案 1 :(得分:1)

尝试利用ternany编号

对于术语,我将“值”称为您的设定评估函数,并“定位”您的目标函数,该函数是每个二进制分区的最大值之和。

每次将二进制数B分成两个不相交的部分L和R,可以用三进制数C表示,其中

B = L | R   (bitwise OR)
L ^ R = 0   (bitwise XOR)

C[i] = 0 means B[i] = 0 and L[i] = R[i] = 0
C[i] = 1 means B[i] = 1 and L[i] = 1
C[i] = 2 means B[i] = 2 and R[i] = 1

然后“简单地”枚举三元组中从1到3 ** n的数字:例如(n = 3):000,001,002,010,011,012,020,......

好吧,实际上,当你手头的所有东西都是二进制时,有效计算三元并不是完全无关紧要的。但请耐心等待,我会在超越高级算法后解释这一点......

所以你按顺序计算三元数,并给出三元数C,你得到L和R - 怎么样?我也会在下面解释一下,相信我:)

鉴于L和R,您现在可以在L和R处查找您的估值并在B处更新目标:目标[B] = max(val [L],val [R])。

好的,这是高级算法。我无法在如此短的时间内证明它,但它似乎具有非常好的缓存局部性属性。换句话说,值[L]和值[R]将倾向于一次保留在少量高速缓存行中。 此外,我认为并行化的最佳选择是将i拆分为模3的值,或模9的值,等等。

二进制有效三元计数

我们如何有效地计算三元?请尝试以下操作:在基数4中计数,然后跳过一些。

换句话说,三位数将由两位表示,我们将禁止组合11

 repr | value
 0 0  | 0
 0 1  | 1
 1 0  | 2
 1 1  | *undefined*

现在,我们如何有效地知道何时跳过?好吧,增量模式很容易弄明白:

1 1 2 1 1 2 1 1 6 1 1 2 1 1 2 1 1 6 1 1 2 1 1 2 1 1 22 1 1 2 ...

我的建议是将一大块大小预先设置为3的幂(例如3 ** 7 = 2187)并偶尔计算3的n次幂[暗示:它与n的立方体有关] ..]。

所以你从00.00.00开始。你加1是00.00.01。你加1是00.00.10。现在你必须添加2才能跳过11组合,这将留下00.01.00。等

如何从C获得L和R

现在我们的三元四元表示中的C实际上只是L和R交错。为了有效地恢复L和R,您可以检查this S/O question的答案或应用其他一些麻烦的黑客。

<强>事后

总而言之,我不确定我们是否真的使用过3号底座或4号底座。好吧......

玩得开心,祝你好运!