cuda中的双音排序会对某些值排序错误

Bitonic sorting in cuda misorders some values

本文关键字:排序 错误 cuda      更新时间:2023-10-16

我正在为一个更大的项目在CUDA上做一个排序算法,我决定实现一个Bitonic排序。我要排序的元素个数总是2的幂,实际上是512。我需要一个具有最终位置的数组,因为此方法将用于对表示另一个解的质量矩阵的数组进行排序。

fitness是我要排序的数组,numElements是元素的数量,而order最初是一个包含numElements位置的空数组,它将在一开始以这种方式填充:orden[i]=i。事实上,秩序与这个问题无关,但我保留了它。

我的问题是一些值没有正确排序,直到现在我还无法弄清楚我有什么问题。

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <ctime>
#include <cuda.h>
#include <curand.h>
#include <curand_kernel.h>
#include <device_functions.h>
#include "float.h"

__global__ void sorting(int * orden, float * fitness, int numElements);
// Populating array with random values for testing purposes
__global__ void populate( curandState * state, float * fitness{
    curandState localState = state[threadIdx.x];
    int a = curand(&localState) % 500;
    fitness[threadIdx.x] = a;
}
//Curand setup for the populate method 
__global__ void setup_cuRand(curandState * state, unsigned long seed)
{
    int id = threadIdx.x;
    curand_init(seed, id, 0, &state[id]);
}
int main()
{
    float * arrayx;
    int numelements = 512;
    int * orden;
    float arrayCPU[512] = { 0 };
    curandState * state;
    cudaDeviceReset();
    cudaSetDevice(0);
    cudaMalloc(&state, numelements * sizeof(curandState));
    cudaMalloc((void **)&arrayx, numelements*sizeof(float));
    cudaMalloc((void **)&orden, numelements*sizeof(int));



    setup_cuRand << <1, numelements >> >(state, unsigned(time(NULL)));
    populate << <1, numelements >> > (state, arrayx);
    cudaMemcpy(&arrayCPU, arrayx, numelements * sizeof(float), cudaMemcpyDeviceToHost);
    for (int i = 0; i < numelements; i++)
        printf("fitness[%i] = %fn", i, arrayCPU[i]);
    sorting << <1, numelements >> >(orden, arrayx, numelements);
    printf("nn");
    cudaMemcpy(&arrayCPU, arrayx, numelements * sizeof(float), cudaMemcpyDeviceToHost);
    for (int i = 0; i < numelements; i++)
        printf("fitness[%i] = %fn", i, arrayCPU[i]);

    cudaDeviceReset();

    return 0;
}
__device__ bool isValid(float n){
    return !(isnan(n) || isinf(n) || n != n || n <= FLT_MIN || n >= FLT_MAX);
}
__global__ void sorting(int * orden, float * fitness, int numElements){
    int i = 0;
    int j = 0;
    float f = 0.0;
    int aux = 0;
    //initial orden registered (1, 2, 3...)
    orden[threadIdx.x] = threadIdx.x;
    //Logarithm on base 2 of numElements
    for (i = 2; i <= numElements; i = i * 2){
        // descending from i reducing to half each iteration
        for (j = i; j >= 2; j = j / 2){
            if (threadIdx.x % j  < j / 2){
                __syncthreads();
                // ascending or descending consideration using (threadIdx.x % (i*2) < i) 
                if ((threadIdx.x % (i * 2) < i) && (fitness[threadIdx.x] >  fitness[threadIdx.x + j / 2] || !isValid(fitness[threadIdx.x])) ||
                    ((threadIdx.x % (i * 2) >= i) && (fitness[threadIdx.x] <= fitness[threadIdx.x + j / 2] || !isValid(fitness[threadIdx.x + j / 2])))){
                    aux = orden[threadIdx.x];
                    orden[threadIdx.x] = orden[threadIdx.x + j / 2];
                    orden[threadIdx.x + j / 2] = aux;
                    //Se reubican los fitness
                    f = fitness[threadIdx.x];
                    fitness[threadIdx.x] = fitness[threadIdx.x + j / 2];
                    fitness[threadIdx.x + j / 2] = f;
                }
            }
        }
    }
}

例如,随机执行时得到的输出:

随机执行

这是我的双音排序的表示:

双元排序模式,箭头指向比较最差值的位置

以下是我发现的问题:

  1. 在你发布的代码中,这不会编译:

    __global__ void populate( curandState * state, float * fitness{
                                                                  ^
                                                       missing close parenthesis
    

  2. 在这些cudaMemcpy语句中不需要取数组的地址:

    cudaMemcpy(&arrayCPU, arrayx, numelements * sizeof(float), cudaMemcpyDeviceToHost);
    ....
    cudaMemcpy(&arrayCPU, arrayx, numelements * sizeof(float), cudaMemcpyDeviceToHost);
    

    数组名已经是数组的地址,所以我去掉了&号。如果使用动态分配的数组,这种用法将被打破。

  3. 你的__syncthreads()在这里的使用被打破了:

    for (j = i; j >= 2; j = j / 2){
        if (threadIdx.x % j  < j / 2){
            __syncthreads();
    
    在条件语句中使用

    __syncthreads()通常是不正确的,除非条件语句在线程块中统一求值。这在文档中有介绍。我们只需稍加改动就可以达到预期的效果:

    for (j = i; j >= 2; j = j / 2){
        __syncthreads();
        if (threadIdx.x % j  < j / 2){
    

有了上面的改变,你的代码对我来说在大多数情况下都是正确运行的。如果您打算正确排序0(或任何负值),那么在有效性检查中使用FLT_MIN也是有问题的。一般来说,FLT_MIN是一个非常小的数字,接近于零。如果你认为这是一个很大的负数,它不是。因此,零是随机数生成器的可能输出,它不会被正确排序。我将把这个问题留给你来解决,它应该很简单,但这取决于你最终想要实现什么。(如果您只想对正的非零浮点值进行排序,测试可能是可以的,但在这种情况下,您的随机数生成器可能会返回0。)