这个数组比较问题的最佳算法是什么?

时间:2010-05-05 18:26:24

标签: c algorithm optimization cuda

解决以下问题的速度算法效率最高的是什么?

给定6个阵列,D1,D2,D3,D4,D5和D6,每个包含6个数字,如:

D1[0] = number              D2[0] = number      ......       D6[0] = number
D1[1] = another number      D2[1] = another number           ....
.....                       ....                ......       ....
D1[5] = yet another number  ....                ......       ....

给定第二个数组ST1,包含1个数字:

ST1[0] = 6

给出第三个数组ans,包含6个数字:

ans[0] = 3, ans[1] = 4, ans[2] = 5, ......ans[5] = 8

使用数组D1,D2,D3,D4,D5和D6的索引,从0到存储在ST1 [0]中的数字减1,在本例中为6,所以从0到6 -1,将ans数组与每个D数组进行比较。如果在相同索引处的任何D中找不到一个或多个ans数,则结果应为0,如果在相同索引处的某个D中找到所有ans数,则结果应为1。也就是说,如果某些ans [i]不等于任何D N [i]则返回0,并且如果每个ans [i]等于某些D ,则返回1 [i] ]

到目前为止我的算法是:
我试图尽可能地保持一切不受欢迎。

EML  := ST1[0]   //number contained in ST1[0]   
EML1 := 0        //start index for the arrays D 

While EML1 < EML
   if D1[ELM1] = ans[0] 
     goto two
   if D2[ELM1] = ans[0] 
     goto two
   if D3[ELM1] = ans[0] 
     goto two
   if D4[ELM1] = ans[0] 
     goto two
   if D5[ELM1] = ans[0] 
     goto two
   if D6[ELM1] = ans[0] 
     goto two

   ELM1 = ELM1 + 1

return 0     //If the ans[0] number is not found in either D1[0-6], D2[0-6].... D6[0-6] return 0 which will then exclude ans[0-6] numbers


two:

EML1 := 0      start index for arrays Ds 
While EML1 < EML
   if D1[ELM1] = ans[1] 
     goto three
   if D2[ELM1] = ans[1] 
     goto three
   if D3[ELM1] = ans[1] 
     goto three
   if D4[ELM1] = ans[1] 
     goto three
   if D5[ELM1] = ans[1] 
     goto three
   if D6[ELM1] = ans[1] 
     goto three
   ELM1 = ELM1 + 1

return 0    //If the ans[1] number is not found in either D1[0-6], D2[0-6]....  D6[0-6]  return 0 which will then exclude ans[0-6] numbers

three:

EML1 := 0      start index for arrays Ds 

While EML1 < EML
   if D1[ELM1] = ans[2] 
     goto four
   if D2[ELM1] = ans[2] 
     goto four
   if D3[ELM1] = ans[2] 
     goto four
   if D4[ELM1] = ans[2] 
     goto four
   if D5[ELM1] = ans[2] 
     goto four
   if D6[ELM1] = ans[2] 
     goto four
   ELM1 = ELM1 + 1

return 0   //If the ans[2] number is not found in either D1[0-6], D2[0-6]....  D6[0-6]  return 0 which will then exclude ans[0-6] numbers

four:

EML1 := 0      start index for arrays Ds 

While EML1 < EML
   if D1[ELM1] = ans[3] 
     goto five
   if D2[ELM1] = ans[3] 
     goto five
   if D3[ELM1] = ans[3] 
     goto five
   if D4[ELM1] = ans[3] 
     goto five
   if D5[ELM1] = ans[3] 
     goto five
   if D6[ELM1] = ans[3] 
     goto five
   ELM1 = ELM1 + 1

return 0 //If the ans[3] number is not found in either D1[0-6], D2[0-6]....  D6[0-6]  return 0 which will then exclude ans[0-6] numbers


five:

EML1 := 0      start index for arrays Ds 

While EML1 < EML
   if D1[ELM1] = ans[4] 
     goto six
   if D2[ELM1] = ans[4] 
     goto six
   if D3[ELM1] = ans[4] 
     goto six
   if D4[ELM1] = ans[4] 
     goto six
   if D5[ELM1] = ans[4] 
     goto six
   if D6[ELM1] = ans[4] 
     goto six
   ELM1 = ELM1 + 1

return 0  //If the ans[4] number is not found in either D1[0-6], D2[0-6]....  D6[0-6]  return 0 which will then exclude ans[0-6] numbers

six:

EML1 := 0      start index for arrays Ds 

While EML1 < EML
   if D1[ELM1] = ans[5] 
     return 1            ////If the ans[1] number is not found in either D1[0-6].....  
   if D2[ELM1] = ans[5]      return 1 which will then include ans[0-6] numbers
     return 1
   if D3[ELM1] = ans[5] 
     return 1
   if D4[ELM1] = ans[5] 
     return 1
   if D5[ELM1] = ans[5] 
     return 1
   if D6[ELM1] = ans[5] 
     return 1
   ELM1 = ELM1 + 1

return 0 

作为选择的语言,它将是纯粹的c

4 个答案:

答案 0 :(得分:7)

我对原始海报提供的算法进行了直接的简单C实现。它是here

正如其他人提出的,首先要做的就是汇总代码。展开对速度并不是很好,因为它会导致代码缓存未命中。我开始滚动内循环并得到this。然后我滚动了外循环并删除了现在无用的gotos并得到了下面的代码。

编辑:我改变了几次C代码,因为即使它很简单,当JIT使用CUDA编译或执行它时似乎存在问题(并且CUDA似乎不是非常冗长的错误)。这就是为什么下面的代码使用全局...而这只是简单的实现。我们还没有加快速度。它说明了过早的优化。如果我们甚至无法使它工作,为什么还要费心?我想还有一些问题,因为如果我相信维基百科的文章,CUDA似乎对你可以工作的代码施加了很多限制。也许我们应该使用float而不是int?

#include <stdio.h>

int D1[6] = {3, 4, 5, 6, 7, 8};
int D2[6] = {3, 4, 5, 6, 7, 8};
int D3[6] = {3, 4, 5, 6, 7, 8};
int D4[6] = {3, 4, 5, 6, 7, 8};
int D5[6] = {3, 4, 5, 6, 7, 8};
int D6[6] = {3, 4, 5, 6, 7, 9};
int ST1[1] = {6};
int ans[6] = {1, 4, 5, 6, 7, 9};
int * D[6] = { D1, D2, D3, D4, D5, D6 };

/* beware D is passed through globals */
int algo(int * ans, int ELM){
    int a, e, p;

    for (a = 0 ; a < 6 ; a++){ 
        for (e = 0 ; e < ELM ; e++){
            for (p = 0 ; p < 6 ; p++){
                if (D[p][e] == ans[a]){
                    goto cont;
                }
            }
        }
        return 0; //bad row of numbers found
    cont:;
    }
    return 1;
}

int main(){
    int res;
    res = algo(ans, ST1[0]);
    printf("algo returned %d\n", res);
}

现在这很有趣,因为我们可以理解代码在做什么。顺便做这个包装工作,我纠正了原始问题中的几个奇怪的问题。我认为这是拼写错误,因为它在全球范围内根本不符合逻辑。 - 转到总是跳到两个(它应该有进展) - 最后一次测试检查了ans [0]而不是ans [5]

请注意,如果我错误地认为原始代码应该做什么并且您的原始算法没有拼写错误,请纠正我。

代码为ans中的每个值执行的操作检查它是否存在于二维数组中。如果任何数字未命中则返回0.如果找到所有数字,则返回1.

我要做的是获得真正的快速代码不是用C语言实现它,而是用python(或C ++)这样的另一种语言实现,其中set是标准库提供的基本数据结构。然后我将使用数组的所有值(即O(n))构建一个集合,并检查搜索的数字是否存在于集合中(即O(1))。至少从算法的角度来看,最终的实现应该比现有的代码更快。

Python示例如下所示,因为它非常简单(打印true / false而不是1/0但是你明白了):

ans_set = set(ans)
print len(set(D1+D2+D3+D4+D5+D6).intersection(ans_set)) == len(ans_set)

以下是使用集合的可能的C ++实现:

#include <iostream>
#include <set>

int algo(int * D1, int * D2, int * D3, int * D4, int * D5, int * D6, int * ans, int ELM){
    int e, p;
    int * D[6] = { D1, D2, D3, D4, D5, D6 };
    std::set<int> ans_set(ans, ans+6);

    int lg = ans_set.size();

    for (e = 0 ; e < ELM ; e++){
        for (p = 0 ; p < 6 ; p++){
            if (0 == (lg -= ans_set.erase(D[p][e]))){
                // we found all elements of ans_set
                return 1;
            }
        }
    }
    return 0; // some items in ans are missing
}

int main(){
    int D1[6] = {3, 4, 5, 6, 7, 8};
    int D2[6] = {3, 4, 5, 6, 7, 8};
    int D3[6] = {3, 4, 5, 6, 7, 8};
    int D4[6] = {3, 4, 5, 6, 7, 8};
    int D5[6] = {3, 4, 5, 6, 7, 8};
    int D6[6] = {3, 4, 5, 6, 7, 1};

    int ST1[1] = {6};

    int ans[] = {1, 4, 5, 6, 7, 8};

    int res = algo(D1, D2, D3, D4, D5, D6, ans, ST1[0]);
    std::cout << "algo returned " << res << "\n";
}

我们做了一些表现假设:ans的内容应该排序,否则我们应该构建它,我们假设D1..D6的内容会在调用算法之间发生变化。因此,我们不打算为它构造一个集合(因为设置结构是O(n),如果D1..D6正在改变,我们将无法获得任何东西)。但是如果我们用相同的D1..D6多次调用算法,那就是改变我们应该做相反的事情并将D1..D6变换为一个我们保持可用的更大的集合。

如果我坚持C,我可以这样做:

  • 将D1 ... D6中的所有数字复制到一个唯一的数组中(每行使用memcpy)
  • 对此数组的内容进行排序
  • 使用二分搜索来检查号码是否可用

由于这里的数据量非常小,我们也可以尝试进行微观优化。它可以在这里付出更多。不确定。

EDIT2 :CUDA支持的C子集存在严格限制。最严格的一个是我们不应该使用指向主内存的指针。必须考虑到这一点。它解释了为什么当前代码不起作用。最简单的改变可能是依次为每个数组D1..D6调用它。为了保持简短并避免函数调用成本,我们可以使用宏或内联函数。我会发布一个提案。

答案 1 :(得分:1)

我对你的问题感到有点困惑,但我认为我已经足够帮助你开始了。

#define ROW 6
#define COL 6

int D[ROW][COL]; // This is all of your D arrays in one 2 dimensional array.

接下来你应该使用嵌套的for循环。每个循环将对应于D的维度。请记住,索引的顺序很重要。在C语言中保持直接的最简单方法是记住D[i]是一个有效的表达式,即使D具有多个维度(并且将评估为指向行的指针:子数组)。

如果无法将独立的D数组更改为一个多维数组,则可以轻松创建一个指针数组,其成员指向每个数组的头部并实现相同的效果。

然后,在确定当前D[i]ans不匹配后,您可以使用break语句中断内循环。

答案 2 :(得分:0)

只需比较36个值,最有效的方法是根本不使用CUDA。

只需使用CPU循环。

如果您更改输入,我会更改我的答案。

答案 3 :(得分:0)

如果数字的范围有限,那么制作位数组可能会更容易,如下所示:

int IsPresent(int arrays[][6], int ans[6], int ST1)
{
    uint32_t bit_mask = 0;
    for(int i = 0; i < 6; ++ i) {
        for(int j = 0; j < ST1; ++ j) {
            assert(arrays[i][j] >= 0 && arrays[i][j] < 32); // range is limited
            bit_mask |= 1 << arrays[i][j];
        }
    }
    // make a "list" of numbers that we have

    for(int i = 0; i < 6; ++ i) {
        if(((bit_mask >> ans[i]) & 1) == 0)
            return 0; // in ans, there is a number that is not present in arrays
    }
    return 1; // all of the numbers were found
}

这将始终以O(6 * ST1 + 6)运行。现在这样做的缺点是首先要通过多达36个数组,然后检查六个值。如果有一个强大的先决条件,数字将主要存在,可以扭转测试并提前提前退出:

int IsPresent(int arrays[][6], int ans[6], int ST1)
{
    uint32_t bit_mask = 0;
    for(int i = 0; i < 6; ++ i) {
        assert(ans[i][j] >= 0 && ans[i][j] < 32); // range is limited
        bit_mask |= 1 << ans[i];
    }
    // make a "list" of numbers that we need to find

    for(int i = 0; i < 6; ++ i) {
        for(int j = 0; j < ST1; ++ j)
            bit_mask &= ~(1 << arrays[i][j]); // clear bits of the mask

        if(!bit_mask) // check if we have them all
            return 1; // all of the numbers were found
    }

    assert(bit_mask != 0);
    return 0; // there are some numbers remaining yet to be found
}

如果第一个数组中的第一个数字涵盖ans(和ans的全部,那么这将最多在O(6 * ST1 + 6)中运行,最好在O(6 + 1)中运行是相同数字的六倍)。请注意,位掩码为零的测试可以在每个数组之后(就像现在一样),也可以在每个元素之后(这种方式涉及更多检查,但在找到所有数字时也会提前截止)。在CUDA的上下文中,算法的第一个版本可能会更快,因为它涉及更少的分支,并且大多数循环(除了ST1的循环)可以自动展开。

但是,如果数字的范围是无限的,我们可以做其他事情。由于ans和所有数组中只有最多7 * 6 = 42个不同的数字,因此可以将这些数字映射到42个不同的数字,并使用64位整数作为位掩码。但可以说,这种数字到整数的映射已经足够用于测试,并且可以完全跳过这个测试。

另一种方法是对数组进行排序,并简单地计算各个数字的覆盖范围:

int IsPresent(int arrays[][6], int ans[6], int ST1)
{
    int all_numbers[36], n = ST1 * 6;
    for(int i = 0; i < 6; ++ i)
        memcpy(&all_numbers[i * ST1], &arrays[i], ST1 * sizeof(int));
    // copy all of the numbers into a contiguous array

    std::sort(all_numbers, all_numbers + n);
    // or use "C" standard library qsort() or a bitonic sorting network on GPU
    // alternatively, sort each array of 6 separately and then merge the sorted
    // arrays (can also be done in parallel, to some level)

    n = std::unique(all_numbers, all_numbers + n) - all_numbers;
    // this way, we can also remove duplicate numbers, if they are
    // expect to occur frequently and make the next test faster.
    // std::unique() actually moves the duplicates to the end of the list
    // and returns an iterator (a pointer in this case) to one past
    // the last unique element of the list - that gives us number of
    // unique items.

    for(int i = 0; i < 6; ++ i) {
        int *p = std::lower_bound(all_numbers, all_numbers + n, ans[i]);
        // use binary search to find the number in question
        // or use "C" standard library bfind()
        // or implement binary search yourself on GPU

        if(p == all_numbers + n)
            return 0; // not found
        // alternately, make all_numbers array of 37 and write
        // all_numbers[n] = -1; before this loop. that will act
        // as a sentinel and will save this one comparison (assuming
        // that there is a value that is guaranteed not to occur in ans)

        if(*p != ans[i])
            return 0; // another number found, not ans[i]
        // std::lower_bound looks for the given number, or for one that
        // is greater than it, so if the number was to be inserted there
        // (before the bigger one), the sequence would remain ordered.
    }

    return 1; // all the numbers were found
}

这将在O(n)中进行复制,O(36 log 36)用于排序,可选地O(n)用于unique(其中n是6 * ST1)和O(n log n)搜索(如果使用unique,则n可以小于6 * ST1)。因此整个算法以线性时间运行。请注意,这不涉及任何动态内存分配,因此即使对于GPU平台也是如此(必须实现排序和端口std::unique()std::lower_bound(),但所有这些都是非常简单的功能)。 / p>