STL列出非常糟糕的表现

时间:2016-10-18 13:46:06

标签: c++ list stl

它认为" push_back"和" pop_front" STL列表的方​​法(实现为双链表)应该是常量O(1)。但是我们在linux上运行的应用程序中遇到了cpu问题,我们发现" pop_front"使用列表时,方法效率非常低。这是列表实现问题还是预期的行为?

这是示例代码:

class A {
public:
    A() { mA = rand(); mB = rand(); mC = rand(); mD = rand(); }
    u32 mA;
    u32 mB;
    u32 mC;
    u32 mD;
};

#define DELTA(t1, t0) ((t1.tv_sec - t0.tv_sec)*1000 + ((t1.tv_usec - t0.tv_usec)/1000))

int main(int argc, char* argv[]) {
    std::list<A> l;
    std::queue<A> q;
    std::deque<A> dq;
    printf("Creating nodes...");
    std::vector<A> v;
    for (int i = 0; i < 100000; ++i) {
        A a;
        v.push_back(a);
    }
    printf("OK\n");
    timeval t0, t1;

    printf("std::deque test: push back...");
    gettimeofday(&t0, NULL);
    for (std::vector<A>::const_iterator iter = v.begin(); iter != v.end(); ++iter) {
        dq.push_back(*iter);
    }
    gettimeofday(&t1, NULL);
    printf("Done in %d ms, size = %d\n", DELTA(t1, t0), dq.size());
    printf("std::deque test: pop front...");
    gettimeofday(&t0, NULL);
    while (dq.size() > 0) {
        A a = dq.front();
        dq.pop_front();
    }
    gettimeofday(&t1, NULL);
    printf("Done in %d ms, size = %d\n", DELTA(t1, t0), dq.size());

    printf("std::queue test: push back...");
    gettimeofday(&t0, NULL);
    for (std::vector<A>::const_iterator iter = v.begin(); iter != v.end(); ++iter) {
        q.push(*iter);
    }
    gettimeofday(&t1, NULL);
    printf("Done in %d ms, size = %d\n", DELTA(t1, t0), q.size());
    printf("std::queue test: pop front...");
    gettimeofday(&t0, NULL);
    while (q.size() > 0) {
        A a = q.front();
        q.pop();
    }
    gettimeofday(&t1, NULL);
    printf("Done in %d ms, size = %d\n", DELTA(t1, t0), q.size());

    printf("std::list test: push back...");
    gettimeofday(&t0, NULL);
    for (std::vector<A>::const_iterator iter = v.begin(); iter != v.end(); ++iter) {
        l.push_back(*iter);
    }
    gettimeofday(&t1, NULL);
    printf("Done in %d ms, size = %d\n", DELTA(t1, t0), l.size());
    printf("std::list test: pop front...");
    gettimeofday(&t0, NULL);
    while (l.size() > 0) {
        A a = l.front();
        l.pop_front();
    }
    gettimeofday(&t1, NULL);
    printf("Done in %d ms, size = %d\n", DELTA(t1, t0), l.size());
    return 0;
}

对于不同数量的节点,我们得到:

5000个节点:

std::deque test: push back...Done in 0 ms, size = 5000
std::deque test: pop front...Done in 0 ms, size = 0
std::queue test: push back...Done in 0 ms, size = 5000
std::queue test: pop front...Done in 0 ms, size = 0
std::list test: push back...Done in 0 ms, size = 5000
std::list test: pop front...Done in 202 ms, size = 0

10000个节点:

std::deque test: push back...Done in 0 ms, size = 10000
std::deque test: pop front...Done in 0 ms, size = 0
std::queue test: push back...Done in 0 ms, size = 10000
std::queue test: pop front...Done in 0 ms, size = 0
std::list test: push back...Done in 1 ms, size = 10000
std::list test: pop front...Done in 279 ms, size = 0

100000个节点:

std::deque test: push back...Done in 5 ms, size = 100000
std::deque test: pop front...Done in 4 ms, size = 0
std::queue test: push back...Done in 3 ms, size = 100000
std::queue test: pop front...Done in 4 ms, size = 0
std::list test: push back...Done in 12 ms, size = 100000
std::list test: pop front...Done in 31148 ms, size = 0

谢谢!

森特

2 个答案:

答案 0 :(得分:9)

如果要检查容器是否为空,请使用!c.empty(),而不是c.size() > 0

这对于std::list尤其重要,因为在某些实现中,size线性时间操作,而不是常量时间操作。

(尽管正如vsoftco在评论中指出的那样,C ++ 11强化了size对它确实是常量的要求 - 如果你有一个兼容的编译器/库,你可以尝试启用选项来编译那个标准或以后)

答案 1 :(得分:2)

所以,这里有一些实际的答案:你的测试完全错了。

首先,你的代码是非常C ++ 03风格的编写,并利用里面的邪恶C函数。你应该使用C ++ 11随机生成器,计时函数和C ++ 11样式范围循环。只有这样,C ++开发人员才能真正谈论您的代码。

其次,5000个元素太小数字实际上可以结束任何事情。尝试更大的数字,如1'000'000并在循环内多次进行相同的测试。只有这样你才能真正看到不同容器之间的区别。

第三,我怀疑gettimeofday实际上是否足以准确测量这种基准测试,你绝对应该使用一些C ++ 11计时函数或至少在linux上使用rdtsc命令。

第四,你需要隔离你的测试。有一个测试测试所有您的容器集是错误的。一次测试可能会导致缓存填满热数据,之后的测试只会使用这种具有错误性能提升的热数据。对不同容器使用不同的测试。

最后,我同意一般来说,链表不是有史以来最快的容器。在实际加速代码方面,复杂性有点儿。复杂性是一个数学极限。它没有考虑真正的CPU架构,只是假设一切都是基本步骤,这是错误的。

在典型的C ++应用程序中,主要的两个性能因素是:

  1. 缓存友好内存(此链表非常糟糕)
  2. 内存分配/解除分配的数量(链表很糟糕)
  3. 由于这两个原因,

    列表的性能非常糟糕。数据以非密集的方式传播,导致许多缓存错误,并且它还迫使应用程序导致许多小内存分配,这是C ++中的弱点之一。