是什么让这个桶排序功能变慢?

时间:2010-10-17 14:57:36

标签: c++ algorithm performance stl

该功能定义为

void bucketsort(Array& A){
  size_t numBuckets=A.size();
  iarray<List> buckets(numBuckets);

  //put in buckets
  for(size_t i=0;i!=A.size();i++){
    buckets[int(numBuckets*A[i])].push_back(A[i]);
  }

  ////get back from buckets
  //for(size_t i=0,head=0;i!=numBuckets;i++){
  //size_t bucket_size=buckets[i].size();
  //for(size_t j=0;j!=bucket_size;j++){
  //  A[head+j] = buckets[i].front();
  //  buckets[i].pop_front();
  //}
  //head += bucket_size;
  //}
 for(size_t i=0,head=0;i!=numBuckets;i++){
   while(!buckets[i].empty()){
     A[head]          = buckets[i].back();
     buckets[i].pop_back();
     head++;
   }
 }

  //inseration sort
  insertionsort(A);
}

其中List在STL中只是list<double>

数组的内容在[0,1)中随机生成。理论上,对于O(n),理论上的bucket排序应该比quicksort更快,但是它会失败,如下图所示

alt text

我使用google-perftools在10000000双数组上对其进行分析。它报告如下

alt text

看来我不应该使用STL列表,但我想知道为什么? std_List_node_base_M_hook做了什么?我应该自己编写列表类吗?

PS:实验与改进
 我试过保留放入水桶的代码,这解释了大部分时间用于建造水桶 进行了以下改进: - 使用STL向量作为桶并为桶保留合理的空间 - 使用两个辅助数组来存储构建存储桶中使用的信息,从而避免使用链表,如下面的代码所示

void bucketsort2(Array& A){
  size_t    numBuckets = ceil(A.size()/1000);
  Array B(A.size());
  IndexArray    head(numBuckets+1,0),offset(numBuckets,0);//extra end of head is used to avoid checking of i == A.size()-1

  for(size_t i=0;i!=A.size();i++){
    head[int(numBuckets*A[i])+1]++;//Note the +1
  }
  for(size_t i=2;i<numBuckets;i++){//head[1] is right already
    head[i] += head[i-1];
  }

  for(size_t i=0;i<A.size();i++){
    size_t  bucket_num         = int(numBuckets*A[i]);
    B[head[bucket_num]+offset[bucket_num]] = A[i];
    offset[bucket_num]++;
  }
  A.swap(B);

  //insertionsort(A);
  for(size_t i=0;i<numBuckets;i++)
    quicksort_range(A,head[i],head[i]+offset[i]);
}

结果如下图所示 alt text 使用list作为存储区的行以列表开头,使用向量作为存储区的向量开始,使用辅助数组启动2.最后使用默认插入排序,有些使用快速排序,因为存储桶大小很大。
注意“列表”和“列表,仅放入”,“矢量,保留8”和“矢量,保留2”几乎重叠。
我将尝试小尺寸并保留足够的内存。

5 个答案:

答案 0 :(得分:2)

在我看来,这里最大的瓶颈是内存管理功能(例如newdelete)。

Quicksort(其中STL可能使用优化版本)可以就地对数组进行排序,这意味着它绝对不需要堆分配。这就是为什么它在实践中表现如此出色。

存储桶排序依赖于额外的工作空间,假设理论上可以随时使用(即假设存储器分配根本没有时间)。实际上,内存分配可能需要从(大)恒定时间到所请求内存大小的线性时间(例如,在分配页面时需要花费时间将页面内容归零)。这意味着标准链表实现将受到影响,并占据您排序的运行时间。

尝试使用为大量项目预先分配内存的自定义列表实现,您应该看到排序运行得更快。

答案 1 :(得分:1)

链接列表不是数组。它们执行查找操作的速度要慢得多。 STL排序可能有一个列表的特定版本,考虑到这一点并优化它 - 但你的功能一味地忽略了它正在使用的容器。您应该尝试使用STL向量作为数组。

答案 2 :(得分:1)

iarray<List> buckets(numBuckets);

你基本上创建了很多列表,这可能会花费你很多钱,特别是在内存访问方面,它在理论上是线性的,但实际情况并非如此。

尝试减少存储桶的数量。

验证我的断言只需创建列表即可分析代码速度。

另外,要迭代列表的元素,不应使用.size(),而应使用

//get back from buckets
for(size_t i=0,head=0;i!=numBuckets;i++)
  while(!buckets[i].empty())
  {
    A[head++] = buckets[i].front();
    buckets[i].pop_front();
  }

在某些实现中,.size()可以在O(n)中。不太可能但是......

<小时/> 经过一番研究后我发现了 this page解释std :: _ List_node_base :: hook的代码是什么。

似乎只是在列表中的给定位置插入元素。不应该花很多钱。

答案 3 :(得分:1)

我想也许有趣的问题是,你为什么要创建一个非常大量的存​​储桶?

考虑输入{1,2,3}, numBuckets = 3。包含buckets[int(numBuckets*A[i])].push_back(A[i]);的循环将展开

buckets[3].push_back(1);  
buckets[6].push_back(2);  
buckets[9].push_back(3);  

真的?九个值的九个桶......

考虑您是否通过了范围1..100的排列。您将创建10,000个存储桶,并且仅使用1%的存储桶。 ...并且每个未使用的存储桶都需要在其中创建一个List。 ...并且必须迭代然后在读出循环中丢弃。

更令人兴奋的是,对列表1..70000进行排序,并观察您的堆管理器爆炸尝试创建49亿个列表。

答案 4 :(得分:0)

我没有真正设法了解你的代码细节,因为我在学习的这一点上对Java知之甚少,所以我在算法和C编程方面有一些经验,所以这是我的看法:

Bucket Sort假设数组上元素的公平分散,这实际上更像是你的存储桶排序在O(n)上运行的条件,注意在最坏的情况下,可能是你把大量的元素放在1上你的桶,因此在下一次迭代中,你将处理几乎同样的问题,因为你在第一时间试图修复,导致你的表现不佳。

请注意,Bucket排序的ACTUALL时间复杂度为O(n + k),其中k是桶的数量,您是否计算了桶?是k = O(n)?

桶分类中浪费问题的最多时间是分区到分区后的空桶结束,当连接分类的桶时,你无法判断桶是否为空而没有实际测试它。

希望我帮忙。