C++ std堆栈性能问题
最近,我尝试做一些性能基准测试,比较C++ std堆栈性能问题,c++,windows,performance,stack,C++,Windows,Performance,Stack,最近,我尝试做一些性能基准测试,比较std::stack和我自己的简单stack实现(使用预先分配的内存)。现在我经历了一些奇怪的行为 我想问的第一件事是堆栈基准代码中的这一行: // std::vector<int> magicVector(10); . 编辑:因为每个人都在谈论我的堆栈实现非常糟糕,所以我想纠正一下。我在几分钟内创建了这个堆栈,并且实现了我目前所需要的几个特性。它从来就不是std::stack:)的替代品,也不是保存以在所有情况下使用。唯一的目标是达到最高的速
std::stack
和我自己的简单stack实现(使用预先分配的内存)。现在我经历了一些奇怪的行为
我想问的第一件事是堆栈基准代码中的这一行:
// std::vector<int> magicVector(10);
.编辑:因为每个人都在谈论我的堆栈实现非常糟糕,所以我想纠正一下。我在几分钟内创建了这个堆栈,并且实现了我目前所需要的几个特性。它从来就不是std::stack:)的替代品,也不是保存以在所有情况下使用。唯一的目标是达到最高的速度和正确的结果。对于这个误解我很抱歉…我只想知道一些答案…您的方法实现都被破坏了。忽略复制构造函数和其他缺少的操作,如果推得太多,
push
将调用UB,而你的调整大小
显然被破坏了,因为它不会复制以前的数据,也不是异常安全的,你的推送也不是异常安全的,你调用了太多的副本,你的getAndRemove
也不是异常安全的,你没有破坏弹出的元素,也没有正确构造新元素,只分配它们,在创建时不必要地使用默认构造,我可能还没有找到更多
基本上,您的类在任何可以想象的方面都是极其可怕的不安全的,它会立即销毁用户的数据,在T
上调用所有错误的函数,并且在任何地方抛出异常时都会哭哭啼啼
这是一大堆糟糕的东西,它比std::stack“更快”这一事实是完全不相关的,因为你已经证明,如果你不需要满足要求,你可以随心所欲地快,这一点我们都知道
基本上,正如SBI所说,你显然不理解代码> STD::Stuts的语义,也不重要的C++方面,比如异常安全,以及代码无法正常工作的方式是什么使得它执行得更快。我的朋友,您还有很长的路要走。
许多评论(甚至回答)都集中在您的实现中的风险上。但问题依然存在 正如下面直接演示的那样,纠正感知到的代码缺陷不会改变性能的任何重要方面 以下是修改为(A)安全的OP代码,以及(B)支持与std::stack
相同的操作,以及(C)也为std::stack
保留缓冲区空间,以便为那些错误地认为这些东西对性能很重要的人澄清:
#define _SECURE_SCL 0
#define _SCL_SECURE_NO_WARNINGS
#include <algorithm> // std::swap
#include <iostream>
#include <vector>
#include <stack>
#include <stddef.h> // ptrdiff_t
#include <type_traits> // std::is_pod
using namespace std;
#undef UNICODE
#define UNICODE
#include <Windows.h>
typedef ptrdiff_t Size;
typedef Size Index;
template< class Type, class Container >
void reserve( Size const newBufSize, std::stack< Type, Container >& st )
{
struct Access: std::stack< Type, Container >
{
static Container& container( std::stack< Type, Container >& st )
{
return st.*&Access::c;
}
};
Access::container( st ).reserve( newBufSize );
}
class HighResolutionTimer
{
public:
HighResolutionTimer();
double GetFrequency() const;
void Start() ;
double Stop();
double GetTime() const;
private:
LARGE_INTEGER start;
LARGE_INTEGER stop;
double frequency;
};
HighResolutionTimer::HighResolutionTimer()
{
frequency = GetFrequency();
}
double HighResolutionTimer::GetFrequency() const
{
LARGE_INTEGER proc_freq;
if (!::QueryPerformanceFrequency(&proc_freq))
return -1;
return static_cast< double >( proc_freq.QuadPart );
}
void HighResolutionTimer::Start()
{
DWORD_PTR oldmask = ::SetThreadAffinityMask(::GetCurrentThread(), 0);
::QueryPerformanceCounter(&start);
::SetThreadAffinityMask(::GetCurrentThread(), oldmask);
}
double HighResolutionTimer::Stop()
{
DWORD_PTR oldmask = ::SetThreadAffinityMask(::GetCurrentThread(), 0);
::QueryPerformanceCounter(&stop);
::SetThreadAffinityMask(::GetCurrentThread(), oldmask);
return ((stop.QuadPart - start.QuadPart) / frequency);
}
double HighResolutionTimer::GetTime() const
{
LARGE_INTEGER time;
::QueryPerformanceCounter(&time);
return time.QuadPart / frequency;
}
template< class Type, bool elemTypeIsPOD = !!std::is_pod< Type >::value >
class FastStack;
template< class Type >
class FastStack< Type, true >
{
private:
Type* st_;
Index lastIndex_;
Size capacity_;
public:
Size const size() const { return lastIndex_ + 1; }
Size const capacity() const { return capacity_; }
void reserve( Size const newCapacity )
{
if( newCapacity > capacity_ )
{
FastStack< Type >( *this, newCapacity ).swapWith( *this );
}
}
void push( Type const& x )
{
if( size() == capacity() )
{
reserve( 2*capacity() );
}
st_[++lastIndex_] = x;
}
void pop()
{
--lastIndex_;
}
Type top() const
{
return st_[lastIndex_];
}
void swapWith( FastStack& other ) throw()
{
using std::swap;
swap( st_, other.st_ );
swap( lastIndex_, other.lastIndex_ );
swap( capacity_, other.capacity_ );
}
void operator=( FastStack other )
{
other.swapWith( *this );
}
~FastStack()
{
delete[] st_;
}
FastStack( Size const aCapacity = 0 )
: st_( new Type[aCapacity] )
, capacity_( aCapacity )
{
lastIndex_ = -1;
}
FastStack( FastStack const& other, int const newBufSize = -1 )
{
capacity_ = (newBufSize < other.size()? other.size(): newBufSize);
st_ = new Type[capacity_];
lastIndex_ = other.lastIndex_;
copy( other.st_, other.st_ + other.size(), st_ ); // Can't throw for POD.
}
};
template< class Type >
void reserve( Size const newCapacity, FastStack< Type >& st )
{
st.reserve( newCapacity );
}
template< class StackType >
void test( char const* const description )
{
for( int it = 0; it < 4; ++it )
{
StackType st;
reserve( 200, st );
// after this two loops, st's capacity will be 141 so there will be no more reallocating
for( int i = 0; i < 100; ++i ) { st.push( i ); }
for( int i = 0; i < 100; ++i ) { st.pop(); }
// when you uncomment this line, std::stack performance will magically rise about 18%
// std::vector<int> magicVector(10);
HighResolutionTimer timer;
timer.Start();
for( Index i = 0; i < 1000000000; ++i )
{
st.push( i );
(void) st.top();
if( i % 100 == 0 && i != 0 )
{
for( int j = 0; j < 100; ++j ) { st.pop(); }
}
}
double const totalTime = timer.Stop();
wcout << description << ": " << totalTime << endl;
}
}
int main()
{
typedef stack< Index, vector< Index > > SStack;
typedef FastStack< Index > FStack;
test< SStack >( "std::stack" );
test< FStack >( "FastStack" );
cout << "Done";
}
我怀疑衡量出的效率低下至少部分原因在于那里发生的所有事情,也许这也是自动生成的安全检查的问题
对于调试构建,std::stack
的性能非常不好,我放弃了等待任何结果
编辑:根据Xeo下面的评论,我更新了
push
,以检查缓冲区重新分配情况下的“自推”,将其作为一个单独的函数进行分解:
void push( Type const& x )
{
if( size() == capacity() )
{
reserveAndPush( x );
}
st_[++lastIndex_] = x;
}
奇怪的是,尽管在本测试中从未调用过reserveAndPush
,但它会影响性能——因为代码大小不适合缓存
[D:\dev\test\so\12704314]
> a
std::stack: 3.21623
std::stack: 3.30501
std::stack: 3.24337
std::stack: 3.27711
FastStack: 2.52791
FastStack: 2.44621
FastStack: 2.44759
FastStack: 2.47287
Done
[D:\dev\test\so\12704314]
> _
与使用
std::vector
的std::stack
相反,当堆栈耗尽空间时,它不会重新分配,而只是将地球炸毁。然而,分配是对性能的巨大消耗,因此跳过它肯定会提高性能
然而,在你的位置上,我会抓取一个成熟的
static\u vector
实现,并将其放入std::stack
中以代替std::vector
。通过这种方式,您可以跳过所有需要性能的动态内存处理,但是您有一个有效的堆栈实现,它下面有一个用于内存处理的容器,这很可能比您提出的要好得多 +1我读过的OP代码的最佳解构:P@klerikduh,你的堆栈做的和std堆栈不同(基本上,区别在于你的程序在微风吹拂下就坏了,而std程序却能正常工作。这就是为什么他们没有像其他人所说的那样具有相同的性能特征,很容易制作出一个输出垃圾的快速程序。-1关于安全的担忧与我们无关。需要什么。安全不会影响peR代码的性能。确实如此。调整大小时不必复制就可以节省周期。不必检查边界就可以节省周期。他只是通过不实现相同的功能来节省时间。缺乏安全检查很重要,这就像问为什么没有鸡蛋或黄油的蛋糕尝起来没有鸡蛋或黄油那么好吃一样,这是缓慢的一部分可能来自于检查向量本身的一个元素是否push_
ed到此向量中。这是需要的,否则v.push_(v[0])
将在向量必须重新分配时中断,但堆栈通常不必关心这一点,除非s.push(s.top())
被调用。在您的代码中,由于您的top
返回一个副本,这也不是问题……但同样,无法实现相同的功能。此外,正如我在聊天中提到的,您的FastStack
不会破坏元素(POD不需要),这与std::stack的功能不同。请尝试一个自定义分配器,该分配器在被要求构造时只进行分配,并且有一个noopdestroy
方法。对于倒数第二句话,“std::stack很慢,因为它进行了大量检查”。它也无法处理在OPs问题的关键点中,为什么这一行会对计时产生如此大的影响?使用
void push_back(const value_type& _Val)
{ // insert element at end
if (_Inside(_STD addressof(_Val)))
{ // push back an element
size_type _Idx = _STD addressof(_Val) - this->_Myfirst;
if (this->_Mylast == this->_Myend)
_Reserve(1);
_Orphan_range(this->_Mylast, this->_Mylast);
this->_Getal().construct(this->_Mylast,
this->_Myfirst[_Idx]);
++this->_Mylast;
}
else
{ // push back a non-element
if (this->_Mylast == this->_Myend)
_Reserve(1);
_Orphan_range(this->_Mylast, this->_Mylast);
this->_Getal().construct(this->_Mylast,
_Val);
++this->_Mylast;
}
}
void push( Type const& x )
{
if( size() == capacity() )
{
reserveAndPush( x );
}
st_[++lastIndex_] = x;
}
[D:\dev\test\so\12704314]
> a
std::stack: 3.21623
std::stack: 3.30501
std::stack: 3.24337
std::stack: 3.27711
FastStack: 2.52791
FastStack: 2.44621
FastStack: 2.44759
FastStack: 2.47287
Done
[D:\dev\test\so\12704314]
> _
void reserveAndPush( Type const& x )
{
Type const xVal = x;
reserve( capacity_ == 0? 1 : 2*capacity_ );
push( xVal );
}
void push( Type const& x )
{
if( size() == capacity() )
{
return reserveAndPush( x ); // <-- The crucial "return".
}
st_[++lastIndex_] = x;
}