在C中使用大量数据/内存进行Quicksort

时间:2014-07-23 20:21:53

标签: c algorithm sorting segmentation-fault quicksort

我有以下代码设置:

#define TSIZE 32
#define TNUM 24000000
#define CORES 4

/* Byte-wise swap two items of size SIZE. */
#define SWAP(a, b, size)                    \
  do                                        \
    {                                       \
      size_t __size = (size);               \
      char *__a = (a), *__b = (b);          \
      do                                    \
      {                                     \
        char __tmp = *__a;                  \
        *__a++ = *__b;                      \
        *__b++ = __tmp;                     \
      } while (--__size > 0);               \
  } while (0)

char* TWEETS;

size_t partition(void* arr, size_t left, size_t right, int (*compar)(const void* , const void*))
{
    char* cArr = (char*) arr;

    size_t i;
    size_t pivotIndex = (left+right)/2;
    char* pivotValue = &cArr[(size_t)TSIZE * right];
    size_t index = left;

    SWAP(&cArr[(size_t)TSIZE * pivotIndex], &cArr[(size_t)TSIZE * right], (size_t)TSIZE);

    for(i = left; i < right; i++) {
        if(compar((void*) &cArr[(size_t)TSIZE * i], (void*) pivotValue) < 0) {
            SWAP(&cArr[(size_t)TSIZE * i], &cArr[(size_t)TSIZE * index], (size_t)TSIZE);
            index++;
        }
    }
    SWAP(&cArr[(size_t)TSIZE * index], &cArr[(size_t)TSIZE * right], (size_t)TSIZE);
    return index;
}

void quicksort(void* base, size_t left, size_t right, int (*compar)(const void* , const void*))
{
    if(left < right) {
        size_t pivot = partition(base, left, right, compar);

        #pragma omp task
        quicksort(base, left, pivot-1, compar);

        #pragma omp task
        quicksort(base, pivot+1, right, compar);
    }
}

int main(int argc, char** argv) {
    omp_set_dynamic(0);
    omp_set_num_threads(CORES);
    TWEETS = (char*) malloc((size_t)TNUM * (size_t)TSIZE * (size_t)CORES * (size_t)sizeof(char));
    if(TWEETS == NULL) exit(1);

    readData();

    #pragma omp parallel
    {
        #pragma omp single
        quicksort(TWEETS, 0, ((size_t)CORES*(size_t)TNUM)-(size_t)1, compare);
    }

    free(TWEETS);
}

首先,请原谅大量的(size_t)演员表,我绝望地做了这件事。

我在这做什么
我正在阅读一个包含2400万行文本的文本文件,每行包含32个字节的字符。然后根据比较函数对行进行排序,这里我省略了。我可以保证这个功能有效并且不是我麻烦的原因。它始终返回-1,0或1 我也试图并行化quicksort算法。代码行与我使用的内核数量一起增长,例如: 1核= 2400万,2核= 4800万等等。

什么在起作用
只要文件大小低于4800万行文本,工作已经在对文件进行排序,使用1到8个核心。

我的问题是什么
我的问题是,一旦我尝试使用7200万行或更多文本对文件进行排序,快速排序算法会遇到分段错误。我已经尽可能地用gdb调试了代码,而错误的代码是这一行:

SWAP(&cArr[(size_t)TSIZE * i], &cArr[(size_t)TSIZE * index], (size_t)TSIZE);

这是for循环中分区函数的交换调用。我还可以看到,此时,变量“right”的值为18446744073709551615(2 ^ 64-1),这是分段错误的原因。 “右”的最大值应该是TSIZE * TNUM * CORES。由于数字很大,我唯一的猜测是在算法的某处发生了溢出。

好吧,这就是问题:算法和整个程序在保持&lt; = 4800万行文本时完美无缺。一旦我超越了segfault就会发生。我还确保读取数据有效,这意味着在读取有关3gb RAM的数据的过程中正在使用。 segfault在char数组的排序过程中肯定会发生。

那么为什么它可以处理多达48.000.000行的文本,为什么在拥有更多文本时会出现分段错误?我的错误在哪里?

2 个答案:

答案 0 :(得分:2)

您的算法中有一个未考虑的边缘情况。

如果原始序列 的底部(左边缘)分区遇到 no swaps (即每个值都是“更大 - 或 - 等于“比枢轴”,然后index,从零(0)开始,将保持原样。索引i将一直走到尽头。然后将临时存储在最右侧插槽中的数据透视值交换到位(即cArr[0]cArr[right]交换),然后从函数返回0。换句话说,这个:

size_t pivot = partition(base, left, right, compar);

#pragma omp task
quicksort(base, left, pivot-1, compare);
// here ================^

执行时pivot从前一次调用返回为零,将pivot-1作为right传递并导致下溢。这将为您提供完全您错误时获得的right的值。 (在我曾经使用的每个平台上都是2 ^ 64-1。)

你需要考虑到这一点(或者永远不要让它发生在一起)。在代码中是否发生这种情况完全取决于使用left=0处理的每个分区的内容。它可能不会在第一次,第二次等发生。但是,将正确的数据交换到不断减少的分区空间,最终可能发生。


未经测试,但值得一看

我首先不喜欢quicksort()的C实现中的leftright分区标记。该语言支持指针数学,因此使用 并围绕你知道具体的东西(基数和长度)。我没有测试过以下内容,只有一次曾经不得不处理OMP,但简化了,我的意思是这样的:

void quicksort(void* base, size_t len, int(*compar)(const void*, const void*))
{
    if (len < 2)
        return;

    char* cArr = (char*)base;
    char* pivotValue = cArr + ((size_t)TSIZE * (len - 1));
    SWAP(cArr + ((size_t)TSIZE * (len / 2)), pivotValue, TSIZE);

    size_t i;
    size_t pivot = 0;

    for (i = 0; i < len; ++i)
    {
        if (compar(cArr + ((size_t)TSIZE * i), ) < 0)
        {
            SWAP(cArr + ((size_t)TSIZE * i), cArr + ((size_t)TSIZE * pivot), (size_t)TSIZE);
            ++pivot;
        }
    }
    SWAP(cArr + ((size_t)TSIZE * pivot), pivotValue, (size_t)TSIZE);

#pragma omp task
    quicksort(cArr, pivot++, compar);

#pragma omp task
    quicksort(cArr+((size_t)TSIZE * pivot), len-pivot, compar);
}

我希望很明显如何调用它。

答案 1 :(得分:0)

一次通过分区操作既简单又可爱,但它比需要更多更多交换。正如所写的那样,在一个小小的测试中,我发现它需要两倍半交换所需的数量!

'可爱'分区有两个问题:

  1. indexi相同时,值为&lt;枢轴值,然后它自己交换自己。如果交换费用昂贵,那么对“自我”的测试会节省一点。

  2. 当它交换时,它会将index向前的值移动到i,并踩到两者。如果index到达该值,它可能必须再次向前交换 - 进行“额外”(不必要的)交换,在推进的“指数”之前随机改变值。

  3. 考虑对五个值进行分区:9 3 5 1 4。首先,将选择5作为枢轴值,并与4交换,得到9 3 4 1 5。然后,从index == i == left开始,9大于枢轴,因此请离开index并前进i。现在3小于枢轴,所以我们交换9和3并推进indexi,得到3 9 4 1 5。现在4小于枢轴,所以再次交换 ,将9向前移动,给出3 4 9 1 5。并且1也小于枢轴,因此再次交换 ,给出3 4 1 9 5。最后,将透视值交换到位可以完成提交3 4 1 5 9的过程。

    所以,这样做 3 交换以改变9,同时只需要 1 交换。

    一种常见但不那么简单的方法是先从左侧开始,然后从右侧进行分区扫描,查找需要上下交换的值,以便以最小的交换次数完成此过程

    我尝试了50个整数的向量,每个值从1..50随机选择。我运行了'可爱'分区和一个更“传统”的分区,并计算了超过20,000次试验的掉期。我得到了:

    Average 'trad' swaps  =  9.8
    Average 'cute' swaps  = 23.0
    Average 'cute' selfs  =  2.9
    Average 'cute' extra  = 13.0
    

    其中'trad'最小化掉期数量。 “自我”是交换,其中index == i,而extras是值多次转发的位置。 '可爱'互换计数包括'额外',但不包括'自我'(因为'自我'是微不足道的消除)。

    交换计数包括交换枢轴值并再次交换它 - 因为这两种算法的相同。折扣这两个互换,'可爱'算法做了23.9 / 7.8或三次所需的交换(平均来说,在我的小测试中)。

    因此,无论多么可爱,简单的一次性分区都是垃圾。


    为了完整起见,这是一个更“传统”的分区:

      /* 'left' and 'right' are indexes into 'data', and there are
       * are 'right' - 'left' + 1 items in the partition.
       *
       * 'pivot' is the index of the chosen pivot-value, and is set
       * to the (new) index for that when the partition completes.
       */
      pv = data[pivot] ;
    
      swap(data, pivot, right) ;
    
      l = left ;
      r = right ;
    
      while (l < r)
        {
          --r ;
    
          while ((l < r) && (data[l] <= pv))
            ++l ;
    
          while ((l < r) && (data[r] >= pv))
            --r ;
    
          if (l == r)
            {
              if (data[r] < pv)
                ++r ;
              break ;
            } ;
    
          swap(data, l, r) ;
    
          ++l ;
        } ;
    
      pivot = r ;
    
      swap(data, pivot, right) ;