【发布时间】:2010-12-31 03:15:20
【问题描述】:
我有一个性能非常重要的方法(我知道过早的优化是万恶之源。我知道我应该并且我确实分析了我的代码。在这个应用程序中,我节省的每十分之一秒都是一个巨大的胜利。 ) 此方法使用不同的启发式方法来生成和返回元素。启发式顺序使用:使用第一个启发式直到它不能再返回元素,然后使用第二个启发式直到它不能再返回元素,依此类推,直到使用完所有启发式。在每次调用该方法时,我都会使用一个开关来移动到正确的启发式。这很丑陋,但效果很好。这是一些伪代码
class MyClass
{
private:
unsigned int m_step;
public:
MyClass() : m_step(0) {};
Elem GetElem()
{
// This switch statement will be optimized as a jump table by the compiler.
// Note that there is no break statments between the cases.
switch (m_step)
{
case 0:
if (UseHeuristic1())
{
m_step = 1; // Heuristic one is special it will never provide more than one element.
return theElem;
}
m_step = 1;
case 1:
DoSomeOneTimeInitialisationForHeuristic2();
m_step = 2;
case 2:
if (UseHeuristic2())
{
return theElem;
}
m_step = 3;
case 3:
if (UseHeuristic3())
{
return theElem;
}
m_step = 4; // But the method should not be called again
}
return someErrorCode;
};
}
正如我所说,这很有效,因为在每次调用时,执行都会跳到应有的位置。如果启发式不能提供元素,则 m_step 会递增(因此下次我们不再尝试此启发式)并且因为没有 break 语句,所以会尝试下一个启发式。另请注意,某些步骤(如步骤 1)永远不会返回元素,而是为下一个启发式方法进行一次初始化。
初始化并非全部预先完成的原因是它们可能永远不需要。 GetElem 在它返回一个元素后不再被调用是有可能的(并且很常见),即使它仍然有可以返回的元素。
虽然这是一个有效的实现,但我觉得它真的很难看。案例陈述是一个黑客;不间断地使用它也很骇人听闻;即使每个启发式都封装在自己的方法中,该方法也会变得很长。
我应该如何重构这段代码,使其更具可读性和优雅,同时尽可能保持高效?
【问题讨论】:
-
重构通常不会让代码更高效——只是更易读和更易于维护。两者不应该同时进行。优化通常会降低代码的可读性和可维护性。
-
感觉就像你手动实现了“收益回报”。很酷!
-
@Larry Watanabe:你说得对,优化和可读性通常是相互竞争的目标。但是这段代码对我来说似乎很老套,我想知道是否有任何改进方法。
-
Mathieu:我知道你说过你分析了它,但很难相信很多 CPU 时间真的花在了这个函数上......除非调用者和启发式方法是微不足道的。
-
如果每秒调用大约 2000 万次,那么它的性能显然还不错。每次迭代只有 150 个周期。对于进行多个函数调用并具有多个分支的东西来说,这似乎还不错。我认为以这种方式使用 switch 语句已经足够普遍了,这并非不合理。
标签: c++ algorithm oop refactoring