2012-11-09 81 views
40

我正在处理一个统计应用程序,该应用程序在数组中包含大约10-30万个浮点值。我可以/应该在GPU上运行此代码吗?

几种方法在嵌套循环阵列上执行不同的,但是独立的,计算,例如:

Dictionary<float, int> noOfNumbers = new Dictionary<float, int>(); 

for (float x = 0f; x < 100f; x += 0.0001f) { 
    int noOfOccurrences = 0; 

    foreach (float y in largeFloatingPointArray) { 
     if (x == y) { 
      noOfOccurrences++; 
     } 
    } 

    noOfNumbers.Add(x, noOfOccurrences); 
} 

当前应用程序是用C#,运行的Intel的CPU上,并且需要几个小时来完成。我没有GPU编程概念和API的知识,所以我的问题是:

  • 使用GPU来加速此类计算是否可能(并且有意义)?
  • 如果是:有人知道任何教程或得到任何示例代码(编程语言无所谓)?

任何帮助将不胜感激。

+2

以任何机会,你有没有试过你的代码转换成C/C++?基于下面的代码片段,您正在使用C#。如果你的代码大部分时间都是为字典分配内存的话,我不会感到惊讶。 – Martin

+3

不,但为字典分配内存只需要几ms或更少的时间,并且CPU使用率始终在93% - 98%之间,所以我认为在这种情况下内存不是(主要)性能问题。 – Mike

+4

我真的认为你的代码应该在不使用GPU的情况下快速发展。您是否尝试过使用字典(预先分配所有内容)?不要使用foreach,而是使用。 GPU是矫枉过正的。用C重写整个事情,它会迫使你考虑内存分配。 – Martin

回答

76

UPDATE GPU版本

__global__ void hash (float *largeFloatingPointArray,int largeFloatingPointArraySize, int *dictionary, int size, int num_blocks) 
{ 
    int x = (threadIdx.x + blockIdx.x * blockDim.x); // Each thread of each block will 
    float y;           // compute one (or more) floats 
    int noOfOccurrences = 0; 
    int a; 

    while(x < size)   // While there is work to do each thread will: 
    { 
     dictionary[x] = 0;  // Initialize the position in each it will work 
     noOfOccurrences = 0;  

     for(int j = 0 ;j < largeFloatingPointArraySize; j ++) // Search for floats 
     {              // that are equal 
                  // to it assign float 
      y = largeFloatingPointArray[j]; // Take a candidate from the floats array 
      y *= 10000;      // e.g if y = 0.0001f; 
      a = y + 0.5;      // a = 1 + 0.5 = 1; 
      if (a == x) noOfOccurrences++;  
     }          

     dictionary[x] += noOfOccurrences; // Update in the dictionary 
              // the number of times that the float appears 

    x += blockDim.x * gridDim.x; // Update the position here the thread will work 
    } 
} 

这一次我只测试了更小的投入,因为我想知道我我的笔记本电脑。尽管如此,它确实奏效。但是,有必要进一步做睾丸检查。

UPDATE顺序版本

我只是做了,在不到20秒(已计数功能来生成数据)执行你的算法为3000万这个天真的版本。

基本上,它排序你的浮点数组。它将遍历已排序的数组,分析数组中连续出现的值的次数,然后将该值与其出现的次数一起放入字典中。

您可以使用排序映射,而不是我使用的unordered_map。

继承人的代码:

#include <stdio.h> 
#include <stdlib.h> 
#include "cuda.h" 
#include <algorithm> 
#include <string> 
#include <iostream> 
#include <tr1/unordered_map> 


typedef std::tr1::unordered_map<float, int> Mymap; 


void generator(float *data, long int size) 
{ 
    float LO = 0.0; 
    float HI = 100.0; 

    for(long int i = 0; i < size; i++) 
     data[i] = LO + (float)rand()/((float)RAND_MAX/(HI-LO)); 
} 

void print_array(float *data, long int size) 
{ 

    for(long int i = 2; i < size; i++) 
     printf("%f\n",data[i]); 

} 

std::tr1::unordered_map<float, int> fill_dict(float *data, int size) 
{ 
    float previous = data[0]; 
    int count = 1; 
    std::tr1::unordered_map<float, int> dict; 

    for(long int i = 1; i < size; i++) 
    { 
     if(previous == data[i]) 
      count++; 
     else 
     { 
      dict.insert(Mymap::value_type(previous,count)); 
      previous = data[i]; 
      count = 1;   
     } 

    } 
    dict.insert(Mymap::value_type(previous,count)); // add the last member 
    return dict; 

} 

void printMAP(std::tr1::unordered_map<float, int> dict) 
{ 
    for(std::tr1::unordered_map<float, int>::iterator i = dict.begin(); i != dict.end(); i++) 
    { 
    std::cout << "key(string): " << i->first << ", value(int): " << i->second << std::endl; 
    } 
} 


int main(int argc, char** argv) 
{ 
    int size = 1000000; 
    if(argc > 1) size = atoi(argv[1]); 
    printf("Size = %d",size); 

    float data[size]; 
    using namespace __gnu_cxx; 

    std::tr1::unordered_map<float, int> dict; 

    generator(data,size); 

    sort(data, data + size); 
    dict = fill_dict(data,size); 

    return 0; 
} 

如果您已经安装在你的机器图书馆推力,你应该这样做:

#include <thrust/sort.h> 
thrust::sort(data, data + size); 

,而不是这个

sort(data, data + size); 

为了确保它会更快。

原贴

“我的工作具有大阵containin 10个统计应用 - 30百万浮点值的”。

“利用GPU加速这样的计算有可能(而且有意义)吗?”

是的。一个月前,我完全在GPU上进行了分子动力学模拟。内核之一计算粒子对之间的力,每个粒子接受6个阵列,每个阵列有500,000个双打,共有3百万双打(22 MB)。

所以你打算把30百万浮点数,这是约114 MB的全球内存,所以这不是一个问题,即使我的笔记本电脑有250MB。

在你的情况下计算的数量可能是一个问题?基于我对分子动态(MD)的经验,我说不。顺序MD版本需要大约25小时才能完成,而GPU需要45分钟。你说你的应用程序花了几个小时,也是基于你的代码示例,它看起来比分子动态更软。

这里的力计算示例:

__global__ void add(double *fx, double *fy, double *fz, 
        double *x, double *y, double *z,...){ 

    int pos = (threadIdx.x + blockIdx.x * blockDim.x); 

    ... 

    while(pos < particles) 
    { 

     for (i = 0; i < particles; i++) 
     { 
       if(//inside of the same radius) 
       { 
       // calculate force 
       } 
     } 
    pos += blockDim.x * gridDim.x; 
    }   
    } 

的CUDA中的码的一个简单例子可以是两个二维数组的总和:

在C:

for(int i = 0; i < N; i++) 
    c[i] = a[i] + b[i]; 

CUDA中:

__global__ add(int *c, int *a, int*b, int N) 
{ 
    int pos = (threadIdx.x + blockIdx.x) 
    for(; i < N; pos +=blockDim.x) 
     c[pos] = a[pos] + b[pos]; 
} 

CUDA中你基本上把每个迭代并除以每个线程,

1) threadIdx.x + blockIdx.x*blockDim.x; 

每个块具有一个编号从0到N-1(N数最大的块),并且每个块具有螺纹的X个ID从0到X-1。

1)为每个线程根据id和线程所在的块ID计算迭代,blockDim.x是块所具有的线程数。

所以,如果你有2个街区,每一个有10个线程和N = 40,则:

Thread 0 Block 0 will execute pos 0 
Thread 1 Block 0 will execute pos 1 
... 
Thread 9 Block 0 will execute pos 9 
Thread 0 Block 1 will execute pos 10 
.... 
Thread 9 Block 1 will execute pos 19 
Thread 0 Block 0 will execute pos 20 
... 
Thread 0 Block 1 will execute pos 30 
Thread 9 Block 1 will execute pos 39 

寻找到你的代码,我做这个的,这可能是它在CUDA草案:

__global__ hash (float *largeFloatingPointArray, int *dictionary) 
    // You can turn the dictionary in one array of int 
    // here each position will represent the float 
    // Since x = 0f; x < 100f; x += 0.0001f 
    // you can associate each x to different position 
    // in the dictionary: 

    // pos 0 have the same meaning as 0f; 
    // pos 1 means float 0.0001f 
    // pos 2 means float 0.0002f ect. 
    // Then you use the int of each position 
    // to count how many times that "float" had appeared 


    int x = blockIdx.x; // Each block will take a different x to work 
    float y; 

while(x < 1000000) // x < 100f (for incremental step of 0.0001f) 
{ 
    int noOfOccurrences = 0; 
    float z = converting_int_to_float(x); // This function will convert the x to the 
              // float like you use (x/0.0001) 

    // each thread of each block 
    // will takes the y from the array of largeFloatingPointArray 

    for(j = threadIdx.x; j < largeFloatingPointArraySize; j += blockDim.x) 
    { 
     y = largeFloatingPointArray[j]; 
     if (z == y) 
     { 
      noOfOccurrences++; 
     } 
    } 
    if(threadIdx.x == 0) // Thread master will update the values 
     atomicAdd(&dictionary[x], noOfOccurrences); 
    __syncthreads(); 
} 

您必须使用atomicAdd,因为不同块的不同线程可能会同时写入/读取noOfOccurrences,因此您必须确定互斥。

这只是一种方法,您甚至可以将外部循环的迭代转换为线程而不是块。

教程

的医生多布斯杂志由罗布农夫系列CUDA: Supercomputing for the masses优越,占地面积只是在其14个分期付款的一切。它也开始相当温和,因此相当适合初学者。

和anothers:

采取的最后一项一看,你会发现很多链接了解CUDA。

的OpenCL:OpenCL Tutorials | MacResearch

+13

现在,这是我加入SO的答案类型......荣誉! – DarkWanderer

+6

那么,我能说什么呢,这是我所见过的最好的答案。你是天才男人,谢谢你,还有:vielen Dank! :-)怎么样OpenCL和AMD ATI,你有这种组合的经验,你的意见是什么? – Mike

+6

非常感谢,theres在这里绝对是更高质量的答案。我从来没有尝试OpenCL,说实​​话我只是与cuda和NVIDIA Devices(例如Tesla C2050)一起工作,因为它在我的工作中得到的集群中有什么:)。 – dreamcrash

11

我对平行处理或GPGPU没有太多了解,但对于此特定示例,您可以通过对输入数组进行一次遍历而不是循环一百万次来节省大量时间。对于大型数据集,如果可能的话,您通常会希望一次完成所有操作。即使你正在做多个独立的计算,如果它在同一个数据集上,你也可以在同一遍中完成所有这些操作,因为你可以通过这种方式获得更好的局部性。但它可能不值得您的代码中增加的复杂性。

此外,您确实不希望重复添加少量浮点数,舍入误差会加起来,您将无法得到您想要的结果。我在下面的示例中添加了if语句,以检查输入是否与迭代模式相匹配,但如果实际上不需要,则省略它。

我不知道任何C#,但你的样品的单次执行将是这个样子:

Dictionary<float, int> noOfNumbers = new Dictionary<float, int>(); 

foreach (float x in largeFloatingPointArray) 
{ 
    if (math.Truncate(x/0.0001f)*0.0001f == x) 
    { 
     if (noOfNumbers.ContainsKey(x)) 
      noOfNumbers.Add(x, noOfNumbers[x]+1); 
     else 
      noOfNumbers.Add(x, 1); 
    } 
} 

希望这有助于。

+4

您可以使用TryGet代替ContainsKey,然后使用noOfNumbers [x]来改进您的代码。使用TryGet可以节省一个字典查找,它是O(1)分期付款(即不总是O(1)),并且是一个昂贵的O(1),因为字典是一个相当复杂的数据类型。无论如何+1 –

+3

谢谢你们的帮助。非常感谢,您的建议很快就会添加到我的应用程序中。不幸的是,我有近100种其他方法,我认为这些方法不能进行更多的优化。即使我使用代码优化将这种计算速度提高了90%,仍可能需要几个小时才能完成快速CPU。 – Mike

+3

请给我们一个有限的数据集(和你自己的基准)的完整方法。这将使我们有能力为您提供更多帮助。根据我目前在代码中看到的内容,我确信在开始使用GPU之前,我可以将代码的速度提高一倍。 – Martin

6

除了上述海报的建议,还可以在适当情况下使用TPL(任务并行库)在多个内核上并行运行。

上面的例子可以使用Parallel.Foreach和ConcurrentDictionary,但是更复杂的map-reduce安装程序将数组分割成块,每个块生成一个字典,然后将其简化为单个字典,这样可以为您提供更好的结果。

我不知道你所有的计算是否正确映射到GPU功能,但是你必须使用map-reduce算法将计算映射到GPU核心,然后将部分结果减少到单个结果,因此在转向不太熟悉的平台之前,您最好还是先在CPU上做这件事。

+3

感谢您的建议。我已经在使用TPL,但在更高层次上。这意味着我的应用程序调用了几种并行的方法,似乎很好(CPU使用率超过90%)。 – Mike

6

由于需要从内存中检索 'largerFloatingPointArray'的值,我不确定使用GPU是否会很好匹配。我的理解是,GPU更适合自包含计算。

我认为将这个单一的进程应用程序转换为运行在许多系统上的分布式应用程序并调整算法应该可以大幅度提高速度,具体取决于有多少系统可用。

您可以使用经典的“分而治之”的方法。我将采取的一般方法如下。

使用一个系统将“largeFloatingPointArray”预处理为散列表或数据库。这将通过一次完成。它将使用浮点值作为键,并将数组中出现的次数作为值。最坏的情况是每个值只出现一次,但这不太可能。如果每次运行应用程序时,largeFloatingPointArray都会不断变化,那么内存中的哈希表就很有意义。如果它是静态的,那么表可以保存在Berkeley DB这样的键值数据库中。我们称之为'查找'系统。

在另一个系统上,我们称之为'main',创建大量工作并将工作项目分散到N个系统上,并在结果可用时收集结果。例如,一个工作项目可以像两个数字一样简单,表示系统应该工作的范围。当一个系统完成工作时,它会发回一批事件,并准备好处理另一个工作。

性能提高了,因为我们不会一直迭代largeFloatingPointArray。如果查找系统成为瓶颈,那么它可以根据需要复制到尽可能多的系统上。

如果系统数量足够多,并行工作,应该可以将处理时间缩短到几分钟。

我正在致力于针对基于多核的系统(通常称为微服务器)的C语言中的并行编程编译器,这些系统将/或将使用多个“片上系统”模块构建系统。 ARM模块供应商包括Calxeda,AMD,AMCC等。英特尔可能也会有类似的产品。

我有一个编译器的工作版本,可以用于这样的应用程序。基于C函数原型的编译器生成实现跨系统进程间通信代码(IPC)的C网络代码。其中一种可用的IPC机制是socket/tcp/ip。

如果您在实施分布式解决方案时需要帮助,我很乐意与您讨论。

新增11月16日,2012年

我想多一点对算法,我想这应该做一个单程。它用C语言编写,与现有的相比,它应该非常快。

/* 
* Convert the X range from 0f to 100f in steps of 0.0001f 
* into a range of integers 0 to 1 + (100 * 10000) to use as an 
* index into an array. 
*/ 

#define X_MAX   (1 + (100 * 10000)) 

/* 
* Number of floats in largeFloatingPointArray needs to be defined 
* below to be whatever your value is. 
*/ 

#define LARGE_ARRAY_MAX (1000) 

main() 
{ 
    int j, y, *noOfOccurances; 
    float *largeFloatingPointArray; 

    /* 
    * Allocate memory for largeFloatingPointArray and populate it. 
    */ 

    largeFloatingPointArray = (float *)malloc(LARGE_ARRAY_MAX * sizeof(float));  
    if (largeFloatingPointArray == 0) { 
     printf("out of memory\n"); 
     exit(1); 
    } 

    /* 
    * Allocate memory to hold noOfOccurances. The index/10000 is the 
    * the floating point number. The contents is the count. 
    * 
    * E.g. noOfOccurances[12345] = 20, means 1.2345f occurs 20 times 
    * in largeFloatingPointArray. 
    */ 

    noOfOccurances = (int *)calloc(X_MAX, sizeof(int)); 
    if (noOfOccurances == 0) { 
     printf("out of memory\n"); 
     exit(1); 
    } 

    for (j = 0; j < LARGE_ARRAY_MAX; j++) { 
     y = (int)(largeFloatingPointArray[j] * 10000); 
     if (y >= 0 && y <= X_MAX) { 
      noOfOccurances[y]++; 
     } 
    } 
} 
+3

工作可以在第二次在一个机器网络中拆分;但恕我直言,便宜(通常巨大)的改进使用GPU的功率要好得多。至于你的框架与MPI相比如何? :) – Pragmateek

+0

感谢所有的信息和C代码。也许我为我的问题找到了一个很好的解决方案:http://bit.ly/Ta4aSL [PDF]听起来很有希望...您怎么看? – Mike

+0

Mike,这是一个有趣的方式来利用DirectX,而不会被束缚在特定的GPU上。 我在考虑副作用,如果有的话。在DirectX被积极使用的同时,其他应用程序是否会将图形渲染到显示器上? 尝试播放带有或不带有应用程序的YouTube或Windows Media Player视频,看看是否发现正在播放的视频质量有任何恶化。 另外,你知道如果将来你可能需要扩展工作站的能力吗? 由于它是Windows环境的所有部分,我认为这是值得一试的。 –

8

是否有可能(和是否有意义),以利用GPU来加速 这样的计算?

  • 绝对YES,这种算法通常是针对海量数据并行处理的理想人选,事情GPU是在这么好。

如果是:没有人知道任何教程或有任何示例代码 (编程语言并不重要)?

  • 当你想要去的GPGPU方式,你有两个选择:CUDA的OpenCL

    CUDA很成熟,有很多工具,但是以NVidia GPU为中心。

    OpenCL是一个在NVidia和AMD GPU以及CPU上运行的标准。所以你应该很喜欢它。

  • 对于教程,您必须在CodeProject上一个优秀的系列罗布·法伯http://www.codeproject.com/Articles/Rob-Farber#Articles

  • 为了您的具体使用情况存在的直方图与OpenCL的层楼高大量样品的(注意,很多都是图像直方图但原则是相同的)。

  • 当您使用C#,您可以使用绑定像OpenCL.NetCloo

  • 如果阵列太大而无法存储在GPU内存中,则可以对其进行块分区并轻松地为每个部分重新运行OpenCL内核。

+2

有效的直方图算法的额外资源... http://users.cecs.anu.edu.au/~ramtin/cuda.htm – kineticfocus

+2

谢谢你的帮助!非常感激。你对DirectX有什么看法?似乎有一个很好的SDK for C#www.sharpdx.org – Mike

+2

做了一些额外的研究。 OpenCL非常有趣,因为它还支持至强Phi和现代英特尔CPU的集成GPU,请参阅http://bit.ly/Ta29ab – Mike

相关问题