我始终认为,foo2
以下的功能在测试后比foo3
更快。
以下所有代码:
#include <iostream>
#include <boost/timer.hpp>
#include <boost/lexical_cast.hpp>
#include <stdint.h>
struct session {
bool operator==(const session& r) const;
uint8_t proto;
uint16_t sport;
uint16_t dport;
uint32_t sip;
uint32_t dip;
};
bool session::operator==(const session& r) const {
return proto == r.proto && sport == r.sport && dport == r.dport
&& sip == r.sip && dip == r.dip;
}
// my L1,L2,L3 total cache size is 16MB, so set it 32MB to overflow all 16MB caches.
static const int SIZE = 32 * 1024 * 1024 / sizeof(session);
int sum;
void foo1(session* p) {
session s = {1, 2, 3, 4, 5};
for (int i = 0; i < SIZE; i++)
if (p[i] == s)
sum++;
}
void foo2(session* p) {
session s = {1, 2, 3, 4, 5};
int n = SIZE - SIZE % 4;
int i;
for (i = 0; i < n; i += 4) {
if (p[i + 0] == s)
sum++;
if (p[i + 1] == s)
sum++;
if (p[i + 2] == s)
sum++;
if (p[i + 3] == s)
sum++;
}
/*
for (; i < SIZE; i++)
if (p[i] == s)
sum++;
*/
}
void foo3(session* p) {
session s = {1, 2, 3, 4, 5};
int n = SIZE - SIZE % 4;
int i;
for (i = 0; i < n; i += 4) {
if (p[i + 0] == s)
sum++;
else if (p[i + 1] == s)
sum++;
else if (p[i + 2] == s)
sum++;
else if (p[i + 3] == s)
sum++;
}
/*
for (; i < SIZE; i++)
if (p[i] == s)
sum++;
*/
}
int main(int argc, char* argv[]) {
if (argc < 2)
return -1;
int n = boost::lexical_cast<int>(argv[1]);
session* p = new session[SIZE];
boost::timer t;
for (int i = 0; i < n; i++)
foo1(p);
std::cout << t.elapsed() << std::endl;
t.restart();
for (int i = 0; i < n; i++)
foo2(p);
std::cout << t.elapsed() << std::endl;
t.restart();
for (int i = 0; i < n; i++)
foo3(p);
std::cout << t.elapsed() << std::endl;
delete [] p;
return 0;
}
测试1000次,./a.out 1000
输出:
4.36
3.98
3.96
我的机器:
CPU:Intel(R)Xeon(R)CPU E5-2420 0 @ 1.90GHz
缓存:
L1d缓存:32K
L1i缓存:32K
二级缓存:256K
L3缓存:15360K
在测试中,foo2
和foo3
具有等效性能。由于foo2
可能的情况
CPU并行执行所有展开的表达式,因此foo3
是相同的。那对吗?如果是这样,else if
语法违反了C / C ++基本else if
语义。
有人解释一下吗?非常感谢。
更新
我的编译器是gcc 4.4.6 ins RedHat
g ++ -Wall -O2 a.cpp
答案 0 :(得分:3)
在某些情况下,我希望foo3更快,因为它可能会短路(会发生一些小于或等于4的分支,而在foo2中,总会发生4个分支)。在s
不等于4个数组元素中的任何一个的情况下(在这种情况下非常可能),foo2和foo3基本上是相同的代码。在这种情况下,两个函数都会发生4个分支。
考虑一下foo3的真实情况(就分支而言):
if (p[i + 0] == s)
sum++;
else
if (p[i + 1] == s)
sum++;
else
if (p[i + 2] == s)
sum++;
else
if (p[i + 3] == s)
sum++;
这应该表明,只要if
不断出现错误,子分支就会发生。这意味着在没有ifs为真的情况下,它将执行与foo2相同数量的操作(虽然功能不同)。
考虑它的粗略方式就好像每个if
都有成本(不是if的实体,实际的if)。换句话说,每次在执行流程中达到if
时,都需要一定的成本。这是因为必须完成分支。以这种方式思考,很清楚地看到当foo3的流不短路时(当遇到foo3
s if
的所有4个时),每个函数的成本是相同的。 (正如KillianDS指出的那样,如果分支预测是错误的,那么foo3实际上需要更长的时间,因为错误的分支必须被倒回而正确的分支被执行。虽然总是选择正确的分支,但似乎对你来说。)
有点像下面的代码片段可以具有相同的性能:
if (short_runtime()) {}
和
if (short_runtime() && long_runtime()) {}
如果short_runtime
返回true,那么具有第二个函数调用的那个显然会花费更长的时间。如果short_runtime()
返回为false,则long_runtime()
调用将永远不会发生,因此运行时间将相同(或至少极相似)。
要测试这个理论,你可以做到p[i + 0] == s
为真。只需初始化数组(session* p = new session[SIZE]();
),并在本地使用session s = {1, 2, 3, 4, 5};
。
关于循环展开的目的/结果似乎有些混乱。这样做可以减少必须发生的跳跃。如果必须完成n
事件,而不是每次迭代1次操作发生n
次迭代(跳转),则可以发生n/k
次迭代(跳转)。当一切都可以放入缓存时,这可以提供速度提升(如果它不能适应缓存,它实际上可以扼杀性能!)。
指令不会同时发生(如果是,sum
需要一个互斥量,这将是非常昂贵的)。它们只是以4组而不是1组发生。
答案 1 :(得分:2)
使用你的程序我得到这些速度(这里foo3有点慢,g ++ 4.8):
7.57
0.63
0.99
现在会发生什么?您没有初始化您的初始会话数组,因为session
中的所有变量都是POD,它们不会默认初始化并且基本上包含垃圾。因此,代码中的if
将很快收敛到始终预测未采用的分支。在这种情况下,foo3
和foo2
非常相似
lar,foo2
将无条件地执行所有,foo3
将执行它,因为它是预测的。我真的不明白为什么foo3
仍然有点慢,我将不得不查看反汇编代码。
现在看看如果添加以下默认构造函数会发生什么:
session() : proto(1), sport(2), dport(3), sip(4), dip(5) {}
我当然还必须将foo
中的会话变量更改为session s;
现在我的时间变为:
9.7
1.5
0.75
突然foo3
快了很多。仅仅因为现在分支将被大多(正确地)预测为“被采取”。在foo3
的情况下,这意味着只执行第一个条件并且函数快速退出。 foo2
仍需要评估所有分支,即使预测良好,这显然会使其变慢。
答案 2 :(得分:0)