【问题标题】:std deque is surprisingly slowstd deque 出奇的慢
【发布时间】:2012-09-23 11:34:09
【问题描述】:

我刚刚发现,与使用预分配数组的“自制”堆栈版本相比,标准 std deque 真的很慢。
这是我的堆栈代码:

template <class T>
class FastStack
{    
public:
    T* st;
    int allocationSize;
    int lastIndex;

public:
    FastStack(int stackSize);
    FastStack();
    ~FastStack();

    inline void resize(int newSize);
    inline void push(T x);
    inline void pop();
    inline T getAndRemove();
    inline T getLast();
    inline void clear();
};

template <class T>
FastStack<T>::FastStack()
{
    lastIndex = -1;
    st = NULL;
}

template <class T>
FastStack<T>::FastStack(int stackSize)
{
    st = NULL;
    this->allocationSize = stackSize;
    st = new T[stackSize];
    lastIndex = -1;
}

template <class T>
FastStack<T>::~FastStack()
{
    delete [] st;
}

template <class T>
void FastStack<T>::clear()
{
    lastIndex = -1;
}

template <class T>
T FastStack<T>::getLast()
{
    return st[lastIndex];
}

template <class T>
T FastStack<T>::getAndRemove()
{
    return st[lastIndex--];
}

template <class T>
void FastStack<T>::pop()
{
    --lastIndex;
}

template <class T>
void FastStack<T>::push(T x)
{
    st[++lastIndex] = x;
}

template <class T>
void FastStack<T>::resize(int newSize)
{
    if (st != NULL)
        delete [] st;
    st = new T[newSize];
}

.
我为双端队列运行这个简单的基准测试:

std::deque<int> aStack;
int x;

HRTimer timer;
timer.Start();

for (int i = 0; i < 2000000000; i++)
{
    aStack.push_back(i);
    x = aStack.back();
    if (i % 100 == 0 && i != 0)
        for (int j = 0; j < 100; j++)
            aStack.pop_back();
}

double totalTime = timer.Stop();
stringstream ss;
ss << "std::deque " << totalTime;
log(ss.str());

.
使用带有向量的 std 堆栈作为容器(正如“Michael Kohne”所建议的那样)

std::stack<int, std::vector<int>> bStack;
int x;

HRTimer timer;
timer.Start();

for (int i = 0; i < 2000000000; i++)
{
    bStack.push(i);
    x = bStack.top();
    if (i % 100 == 0 && i != 0)
        for (int j = 0; j < 100; j++)
            bStack.pop();
}

double totalTime = timer.Stop();
stringstream ss;
ss << "std::stack " << totalTime;
log(ss.str());

.
我的 FastStack 也是如此:

FastStack<int> fstack(200);
int x;

HRTimer timer;
timer.Start();

for (int i = 0; i < 2000000000; i++)
{
    fstack.push(i);
    x = fstack.getLast();
    if (i % 100 == 0 && i != 0)
        for (int j = 0; j < 100; j++)
            fstack.pop();
}

double totalTime = timer.Stop();
stringstream ss;
ss << "FastStack " << totalTime;
log(ss.str());

.
4次运行后的结果如下:
双端队列 15.529
双端队列 15.3756
双端队列 15.429
双端队列 15.4778

堆栈 6.19099
堆栈 6.1834
堆栈 6.19315
堆栈 6.19841

FastStack 3.01085
FastStack 2.9934
FastStack 3.02536
快速堆栈 3.00937

结果以秒为单位,我在 Intel i5 3570k(默认时钟)上运行代码。我使用了具有所有可用优化的 VS2010 编译器。 我知道我的 FastStack 有很多限制,但是在很多情况下,可以使用它以及何时可以提供很好的提升! (我在一个项目中使用了它,与 std::queue 相比,我的速度提高了 2 倍)。
所以现在我的问题是:
C++ 中是否还有其他人人都在使用但没人知道的“抑制剂”?
编辑:我不想冒犯,我只是好奇你是否知道一些像这样的未知“加速”。

【问题讨论】:

  • 构建双端队列后,在其上调用resize。看看它是否会产生很大的速度差异。我的猜测是,获得/损失的速度的很大一部分来自试图通过改变大小来“智能”管理内存的双端队列类。
  • 如果“没有人知道他们”如何在这里举报?
  • 我会假装我没有看到您的最后一句话,并将其视为合法的性能问题。
  • 您的“堆栈”不提供随机访问或两端的快速插入。这是一个明智的比较吗?我敢肯定你的丰田汽车比我的油箱需要更少的燃料,而且它没有大炮。
  • std::deque 设计用于在两端进行恒定时间插入。更公平的比较是扩展您的堆栈类(当然,它不再是堆栈)以允许这样做。或者与std::vector比较。

标签: c++ performance stack deque


【解决方案1】:

你在比较苹果和橘子。出队是双端的,这要求它在内部与您实现的简单堆栈有很大不同。尝试针对std::stack&lt;int, std::vector&lt;int&gt; &gt; 运行您的基准测试,看看您是如何做的。 std::stack 是一个容器适配器,如果你使用向量作为底层容器,它应该几乎和你自己的实现一样快。

一个缺点是 std::stack 实现没有办法让您预先设置大小,所以在您知道最大大小需要的情况下,它会有点最初较慢,因为它必须重新分配几次。之后就差不多了。

【讨论】:

  • 使用向量作为堆栈容器的相同基准:6.19099 6.1834 6.19315 6.19841。基准测试同时仅将 100 个数字放入堆栈,因此重新分配应该不是那么大的问题。
  • 因此使用 std::stack 进行一次迭代(设置大小),然后 then 进行定时循环(使用相同的预增长堆栈)。现在怎么样了?
  • 好的,所以我做了一个循环并添加 100 个数字,然后另一个循环并删除它们(就在“HRTimer timer”行之前)。使用调试器,我在这些循环之后检查堆栈容量,它是 141,并且一直保持 141(因为同时只添加了 100 个元素),结果更糟 - 6.65823。我做了一些实验,所以我将循环更改为 1000,然后结果为 6.34,然后我将其更改为 1000000,结果为 6.35(容量为 1049869)。我不知道为什么更改未使用的容量空间时结果会发生变化(我的意思是 100 和 1000 之间的差异)。
  • 我又做了一个实验,我把 Sleep(5000) 放在循环之后,现在当循环增加到 100 时,结果会好一点:6.129(平均)。
  • 我希望 std::stack 比你的实现慢,记住它必须考虑在 push_back 时间扩展容器的可能性。再次查看您的代码,我对您仍然快得多并不感到惊讶,因为我们仍然没有将苹果与苹果进行比较 - 您的容器没有支持自动扩展所需的所有开销,甚至没有不跑到相邻内存块的开销。所以是的,您的容器应该更快,如果这对您的应用程序很重要,那就太好了!只是不要搞砸尺寸,否则你就完蛋了。
【解决方案2】:

如果您知道在任何给定时间将在堆栈中的最大元素数量,您应该使用 std::vector 预先保留容量。即使您不知道容量,对于堆栈,您应该使用std::vector,它会增长几次(增长时比预分配的向量成本更高)但永远不会缩小,所以一段时间后它将停止分配内存。

性能问题是std::deque 会根据需要分配块,并在不再需要时释放它们(遵循一些策略),因此如果您填充并清除std::deque,通常会有分配和释放连续不断。

【讨论】:

  • 你是对的。性能从 15 秒到 6 秒变得更好,但与我制作的简单“堆栈”类相比,它仍然非常慢。我知道我的版本没有错误处理或其他功能,但它更快,并且还允许由于公共成员而随机访问...
  • @klerik:你使用什么编译器和标志? std::vector 比您的手工代码慢的唯一原因是您使用检查的迭代器,或者在没有函数内联的调试模式下编译。性能测试并非易事,您必须真正了解自己在做什么以及测试的含义,否则您可能无法正确解释结果。
  • 我使用的是 VS2010,所有基准测试都在 Release 版本上运行,参数为 /O2、/Oi、/Ot、/Oy-、/GL 和 /Ob2,因此任何合适的函数都应该是内联的代码优化应该起作用。我使用调试只是为了找出堆栈容量。但我使用的是检查迭代器,所以我现在关闭了它,但看起来它根本没有区别。
  • @klerik:您的实现和使用reserve 预分配的向量是相同的解决方案。性能不应有任何明显变化。我建议您尝试分析性能影响在哪里,因为这确实是出乎意料的。
  • 我创建了新问题,甚至其他用户也有一些结果...link
猜你喜欢
  • 1970-01-01
  • 2015-12-12
  • 2015-01-15
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 2013-04-10
  • 2021-09-10
  • 2010-09-25
相关资源
最近更新 更多