如何改进基数排序的这种实现?

时间:2013-07-14 06:57:25

标签: c algorithm sorting radix-sort

我正在实现一个2字节的基数排序。概念是使用Counting Sort,对整数的低16位进行排序,然后对高16位进行排序。这允许我在2次迭代中运行排序。我的第一个概念是试图找出如何处理否定。由于符号位将被翻转为负数,然后以十六进制形式,这将使负数大于正数。为了解决这个问题,我在符号位为正时将其翻转,以便使[0,2 bil] = [128 000 000 000,255 255 ...)。当它是负数时,我将所有位翻转,使其范围为(000 000 ..,127 255 ..)。这site帮我提供了这些信息。为了完成它,我会根据传递将整数分成顶部或底部16位。以下是允许我这样做的代码。

static uint32_t position(int number, int pass) {
    int mask;
    if (number <= 0) mask = 0x80000000;
    else mask = (number >> 31) | 0x80000000;
    uint32_t out = number ^ mask;
    return pass == 0 ? out & 0xffff : (out >> 16) & 0xffff;
}

要开始实际的Radix排序,我需要形成一个大小为65536个元素的直方图。我遇到的问题是输入的元素数量非常大。创建直方图需要一段时间,所以我使用进程和共享内存并行实现它。我将数组分区为大小为8的子部分。然后在大小为65536 * 8的共享内存数组中,我让每个进程创建自己的直方图。之后,我将它们总结在一起形成一个直方图。以下是代码:

for (i=0;i<8;i++) {
    pid_t pid = fork();
    if (pid < 0) _exit(0);
    if (pid == 0) {
        const int start = (i * size) >> 3;
        const int stop  = i == 7 ? size : ((i + 1) * size) >> 3;
        const int curr  = i << 16;
        for (j=start;j<stop;++j)
            hist[curr + position(array[j], pass)]++;
        _exit(0);
    }
}
for (i=0;i<8;i++) wait(NULL);

for (i=1;i<8;i++) {
    const int pos = i << 16;
    for (j=0;j<65536;j++)
        hist[j] += hist[pos + j];
}

接下来的部分是我花费大部分时间来分析缓存如何影响前缀和的性能的地方。使用8位和11位通过基数排序,所有直方图都适合L1缓存。使用16位,它只适合L2缓存。最后,16位直方图最快地运行总和,因为我只需要运行2次迭代。我还根据CUDA网站的建议并行运行前缀和。在2.5亿个元素中,这比16位整数慢了大约1.5秒。所以我的前缀总和看起来像这样:

for (i=1;i<65536;i++)
    hist[i] += hist[i-1];

唯一剩下的就是向后遍历数组并将所有元素放入temp数组中各自的位置。因为我只需要经历两次,而不是从temp复制到数组,并再次运行代码。我首先使用数组作为输入运行排序,并使用temp作为输出。然后第二次运行它,使用temp作为输入,使用数组作为输出。这使我无法将mem-copy复制回数组。对于实际排序,代码看起来像这样:

histogram(array, size, 0, hist);
for (i=size-1;i>=0;i--)
    temp[--hist[position(array[i], 0)]] = array[i];

memset(hist, 0, arrSize);
histogram(temp, size, 1, hist);
for (i=size-1;i>=0;i--)
    array[--hist[position(temp[i], 1)]] = temp[i];

This link包含我到目前为止的完整代码。我针对quicksort运行了一个测试,它使用整数和浮点数运行速度提高了5到10倍,使用8字节数据类型运行速度提高了大约5倍。有没有办法改善这个?

1 个答案:

答案 0 :(得分:0)

我的猜测是在操作过程中处理整数的符号是​​不值得的。它会使代码变得复杂并减慢速度。我会先进行unsigned的第一次排序,然后做第二条路径,重新排序这两个部分并反转其中一个底片。

同样来自您的代码,我不知道您如何让不同的进程一起运行。你如何收集父母的直方图?你有一个进程共享变量?无论如何,在这里使用ptrhead会更合适。