它认为" 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
谢谢!
森特
答案 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 ++应用程序中,主要的两个性能因素是:
列表的性能非常糟糕。数据以非密集的方式传播,导致许多缓存错误,并且它还迫使应用程序导致许多小内存分配,这是C ++中的弱点之一。