C++ 在这种情况下,为什么STL优先级_队列不会比multiset快很多?

C++ 在这种情况下,为什么STL优先级_队列不会比multiset快很多?,c++,performance,stl,priority-queue,multiset,C++,Performance,Stl,Priority Queue,Multiset,我比较了STL(g++)优先级_队列的性能,发现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

我比较了STL(g++)优先级_队列的性能,发现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();
}
#包括
#包括
使用名称空间std;
typedef多集IntSet;
void testMap()
{
srand(0);
IntSet iSet;
对于(尺寸i=0;i<1000;++i)
{
iSet.insert(rand());
}
用于(尺寸i=0;i<100000;++i)
{
int v=*(iSet.begin());
iSet.erase(iSet.begin());
v=兰德();
iSet.插入(v);
}
}
typedef priority_queue IntQueue;
void testPriorityQueue()
{
srand(0);
intq;
对于(尺寸i=0;i<1000;++i)
{
q、 推(rand());
}
用于(尺寸i=0;i<100000;++i)
{
intv=q.top();
q、 pop();
v=兰德();
q、 推(v);
}
}
int main(int,char**)
{
testMap();
testPriorityQueue();
}
我编译了这个-O3,然后运行valgrind--tool=callgrind,KCachegrind testMap占用了总CPU的54% testPriorityQueue占用44%的CPU

(没有-O3,testMap比testPriorityQueue快得多) 调用testPriorityQueue似乎占用大部分时间的函数

void std::__adjust_heap<__gbe_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, long, int, std::less<int> >
void std::\u调整\u堆
该函数似乎是从pop()调用中调用的


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

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

该集合通常实现为,min元素将是最左侧的节点(因此要么是叶节点,要么最多有一个子节点)。因此,它最多有一个子项需要移动,根据允许的不平衡程度,可在多个
pop
调用中摊销重新平衡


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

我实现了一个优先级队列,在使用-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;
}

堆缓存不是很不友好吗?至少这是我的总体印象。我认为它们以不可预测的方式分支很多。该函数似乎是堆“冒泡”的原因,即每次删除元素以保持其顺序时必须在堆上执行的日志(n)操作。CPU%不是测试性能或速度的有用方法<代码>\u调整\u堆“重新平衡”优先级队列,是处理优先级队列时唯一缓慢的操作。这是prioriy队列的固有特性,我能想到的唯一替代方法是
std::set
,它必须以类似的方式进行平衡。今天下午我做了一个简单的优先级队列模板,在Linux 64位中编译时,它的速度几乎是std::priority\u队列的两倍-O3。min元素不必是叶子-它可能有一个正确的子元素。不幸的是,您的
pop()
方法不正确:向下移动新的头部节点时,必须与其最小的子节点交换。否则,将立即违反heap属性。
fast_priority_queue: 260000
std::multiset: 620000
std::priority_queue: 490000