我有一行代码,它消耗了我应用程序运行时间的 25% - 30%。它是 std::set 的小于比较器(该集合是用红黑树实现的)。它在 28 秒内被调用了大约 1.8 亿次。
struct Entry {
const float _cost;
const long _id;
// some other vars
Entry(float cost, float id) : _cost(cost), _id(id) {
}
};
template<class T>
struct lt_entry: public binary_function <T, T, bool>
{
bool operator()(const T &l, const T &r) const
{
// Most readable shape
if(l._cost != r._cost) {
return r._cost < l._cost;
} else {
return l._id < r._id;
}
}
};
条目应按成本排序,如果成本相同,则按其 id 排序。对于最小值的每次提取,我都有很多插入。我考虑过使用 Fibonacci-Heaps,但有人告诉我,它们在理论上很好,但会受到高常数的影响并且实现起来相当复杂。并且由于插入在 O(log(n)) 中,因此运行时间增加在 n 较大时几乎是恒定的。所以我认为坚持一套是可以的。
为了提高性能,我尝试用不同的形状来表达它:
return l._cost < r._cost || r._cost > l._cost || l._id < r._id;
return l._cost < r._cost || (l._cost == r._cost && l._id < r._id);
即使这样:
typedef union {
float _f;
int _i;
} flint;
//...
flint diff;
diff._f = (l._cost - r._cost);
return (diff._i && diff._i >> 31) || l._id < r._id;
但是编译器似乎已经足够聪明了,因为我无法改进运行时。
我也考虑过SSE,但是这个问题确实不太适用于SSE...
程序集看起来有点像这样:
movss (%rbx),%xmm1
mov $0x1,%r8d
movss 0x20(%rdx),%xmm0
ucomiss %xmm1,%xmm0
ja 0x410600 <_ZNSt8_Rb_tree[..]+96>
ucomiss %xmm0,%xmm1
jp 0x4105fd <_ZNSt8_Rb_[..]_+93>
jne 0x4105fd <_ZNSt8_Rb_[..]_+93>
mov 0x28(%rdx),%rax
cmp %rax,0x8(%rbx)
jb 0x410600 <_ZNSt8_Rb_[..]_+96>
xor %r8d,%r8d
我对汇编语言有一点经验,但不是很多。
我认为挤出一些性能是最好的(唯一?)点,但这真的值得付出努力吗?你能看到任何可以节省一些周期的捷径吗?
代码将运行的平台是在多核英特尔机器上使用 gcc 4.6 (-stl=c++0x) 的 ubuntu 12。唯一可用的库是 boost、openmp 和 tbb。30 秒的基准测试是在我使用了 4 年的旧笔记本电脑(core 2 duo)上进行的。
我真的被困在这个上,它看起来很简单,但需要那么多时间。几天以来,我一直在绞尽脑汁思考如何改进这条线......
你能给我一个如何改进这部分的建议,还是它已经处于最佳状态?
编辑 1:使用 Jerrys 的建议后,我实现了约 4.5 秒的加速。编辑 2:在尝试提升斐波那契堆之后,比较到 174 次 Mio 调用小于函数。