9

我正在比较 STL (g++) priority_queue 的性能,发现 push 和 pop 没有我预期的那么快。请参阅以下代码:

#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 总量的 54% testPriorityQueue 占 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> >

该函数似乎是从 pop() 调用中调用的。

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

4

2 回答 2

9

优先级队列被实现为一个堆:每次删除头元素时都必须“重新平衡” 。在链接描述中,delete-min是一个O(log n)操作,实际上是因为min(或头)元素是扁平二叉树的根。

该集合通常实现为红黑树,最小元素将是最左边的节点(所以要么是叶子,要么最多有一个右孩子)。pop因此,它最多有 1 个要移动的子节点,并且根据允许的不平衡程度,重新平衡可以分摊到多个调用中。

请注意,如果堆有任何优势,它很可能位于引用位置(因为它是连续的而不是基于节点的)。这正是 callgrind可能更难以准确测量的那种优势,所以我建议在接受这个结果之前也运行一些经过的实时基准测试。

于 2012-08-03T17:49:26.047 回答
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 标志编译时:64 位 Linux 上的 -O3 这给了我:

fast_priority_queue: 260000
std::multiset: 620000
std::priority_queue: 490000
于 2012-08-03T20:00:37.843 回答