2012-08-03 46 views
9

我正在比较STL(g ++)priority_queue的性能,发现push和pop没有我期望的那么快。见下面的代码:为什么在这种情况下,STL的priority_queue比multiset快不了多少?

#include <set> 
#include <queue> 

using namespace std; 

typedef multiset<int> IntSet; 

void testMap() 
{ 
    srand(0); 

    IntSet iSet; 

    for (size_t i = 0; i < 1000; ++i) 
    { 
     iSet.insert(rand()); 
    } 

    for (size_t i = 0; i < 100000; ++i) 
    { 
     int v = *(iSet.begin()); 
     iSet.erase(iSet.begin()); 
     v = rand(); 
     iSet.insert(v); 
    } 
} 

typedef priority_queue<int> IntQueue; 

void testPriorityQueue() 
{ 
    srand(0); 
    IntQueue q; 

    for (size_t i = 0; i < 1000; ++i) 
    { 
     q.push(rand()); 
    } 

    for (size_t i = 0; i < 100000; ++i) 
    { 
     int v = q.top(); 
     q.pop(); 
     v = rand(); 
     q.push(v); 
    } 
} 

int main(int,char**) 
{ 
    testMap(); 
    testPriorityQueue(); 
} 

我编译这个-O3然后跑去的valgrind --tool = callgrind,KCachegrind testMap花费总CPU testPriorityQueue的54%需要CPU

的44%(无 - O3 testMap比testPriorityQueue) 似乎把大部分时间用于testPriorityQueue该功能被称为

void std::__adjust_heap<__gbe_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, long, int, std::less<int> > 

这个功能似乎从弹出称为快得多()打电话。

这个函数到底做了什么?有没有办法通过使用不同的容器或分配器来避免它?

+0

是不是堆缓存不友好?至少这是我的一般印象。 – Mehrdad 2012-08-03 17:37:10

+0

我认为他们以不可预知的方式分支了很多。该函数看起来像是什么负责堆“冒泡”,这是log(n)操作,每次删除元素以保持其顺序时,必须在堆上执行该操作。 – Wug 2012-08-03 17:46:21

+1

CPU%不是测试性能或速度的有用方法。 '__adjust_heap'“重新平衡”优先级队列,并且是处理优先级队列时唯一缓慢的操作。这是先验队列的内在特征,我能想到的唯一选择是'std :: set',它必须以类似的方式进行平衡。 – 2012-08-03 17:50:33

回答

9

优先级队列实现为heap:必须每隔“重新平衡”时间删除head元素。在链接描述中,delete-minO(log n)的操作,实际上是因为min(或head)元素是扁平二叉树的

该集合通常以red-black tree的形式实现,min元素将是最左边的节点(所以无论是叶子还是最多有一个正确的子节点)。因此,最多只有1个孩子可以被转移,并且可以根据允许的不平衡程度,在多个pop呼叫中平摊余额。

请注意,如果堆具有任何优势,那么它很可能位于参考位置(因为它是连续的而不是基于节点的)。这正是那种可以让callgrind准确测量的优势,所以我建议运行一些经过实时的基准测试,然后再接受这个结果。

+0

最小元素不一定是一片叶子 - 它可能有一个正确的孩子。 – 2012-08-03 18:21:09

+0

好点,谢谢:我会更正我的回答 – Useless 2012-08-03 18:27:11

2

我已经实现了一个优先级队列,使用-O3进行编译时似乎运行得更快。 也许只是因为编译器能够内联多于STL情况?

#include <set> 
#include <queue> 
#include <vector> 
#include <iostream> 

using namespace std; 

typedef multiset<int> IntSet; 

#define TIMES 10000000 

void testMap() 
{ 
    srand(0); 

    IntSet iSet; 

    for (size_t i = 0; i < 1000; ++i) { 
     iSet.insert(rand()); 
    } 

    for (size_t i = 0; i < TIMES; ++i) { 
     int v = *(iSet.begin()); 
     iSet.erase(iSet.begin()); 
     v = rand(); 
     iSet.insert(v); 
    } 
} 

typedef priority_queue<int> IntQueue; 

void testPriorityQueue() 
{ 
    srand(0); 
    IntQueue q; 

    for (size_t i = 0; i < 1000; ++i) { 
     q.push(rand()); 
    } 

    for (size_t i = 0; i < TIMES; ++i) { 
     int v = q.top(); 
     q.pop(); 
     v = rand(); 
     q.push(v); 
    } 
} 


template <class T> 
class fast_priority_queue 
{ 
public: 
    fast_priority_queue() 
     :size(1) { 
     mVec.resize(1); // first element never used 
    } 
    void push(const T& rT) { 
     mVec.push_back(rT); 
     size_t s = size++; 
     while (s > 1) { 
      T* pTr = &mVec[s]; 
      s = s/2; 
      if (mVec[s] > *pTr) { 
       T tmp = mVec[s]; 
       mVec[s] = *pTr; 
       *pTr = tmp; 
      } else break; 
     } 
    } 
    const T& top() const { 
     return mVec[1]; 
    } 
    void pop() { 
     mVec[1] = mVec.back(); 
     mVec.pop_back(); 
     --size; 
     size_t s = 1; 
     size_t n = s*2; 
     T& rT = mVec[s]; 
     while (n < size) { 
      if (mVec[n] < rT) { 
       T tmp = mVec[n]; 
       mVec[n] = rT; 
       rT = tmp; 
       s = n; 
       n = 2 * s; 
       continue; 
      } 
      ++n; 
      if (mVec[n] < rT) { 
       T tmp = mVec[n]; 
       mVec[n] = rT; 
       rT = tmp; 
       s = n; 
       n = 2 * s; 
       continue; 
      } 
      break; 
     } 
    } 
    size_t size; 
    vector<T> mVec; 
}; 

typedef fast_priority_queue<int> MyQueue; 

void testMyPriorityQueue() 
{ 
    srand(0); 
    MyQueue q; 

    for (size_t i = 0; i < 1000; ++i) { 
     q.push(rand()); 
    } 

    for (size_t i = 0; i < TIMES; ++i) { 
     int v = q.top(); 
     q.pop(); 
     v = rand(); 
     q.push(v); 
    } 
} 


int main(int,char**) 
{ 
    clock_t t1 = clock(); 
    testMyPriorityQueue(); 
    clock_t t2 = clock(); 
    testMap(); 
    clock_t t3 = clock(); 
    testPriorityQueue(); 
    clock_t t4 = clock(); 

    cout << "fast_priority_queue: " << t2 - t1 << endl; 
    cout << "std::multiset: " << t3 - t2 << endl; 
    cout << "std::priority_queue: " << t4 - t3 << endl; 
} 

当G ++ 4.1.2标志编译:-O3在64位Linux这给了我:

fast_priority_queue: 260000 
std::multiset: 620000 
std::priority_queue: 490000 
+1

不幸的是,你的'pop()'方法是不正确的:当向下移动新的头节点时,它必须与其**最小的**子交换。否则,堆属性将立即被违反。 – ph4nt0m 2015-08-20 22:46:13

相关问题