假设我有一个名为loop()
的函数。在此loop()
中,我增加了一个计数器count
我的功能很少,A()
,B()
,C()
等等。
当计数器达到某个值(每个函数不同)时,我想调用这些函数中的每一个。 我目前的代码如下:
static unsigned int count = 0;
void loop(){
if (count == VALUE_ONE)
A();
if (count == VALUE_TWO)
B();
if (count == VALUE_THREE)
C();
..... //more cases
if (count == MAX_VAL)
count = 0;
else
count++;
}
VALUE_*
为#define
,因此在计划期间不会更改。
现在我使用常规if
语句来检查计数器值。但我想避免使用if
语句来避免分支错误预测。
有更好的方法吗?什么能够真正避免分支错误预测等?
这里的目标是优化这部分代码以使其更快,因为现在它有时直到它应该完成。我知道函数A()
,B()
等可能存在问题,但是现在我问的是这个具体情况。
要说清楚,VALUE_ONE
,VALUE_TWO
,VALUE_THREE
等可能是非常大的值,而不会增加1
。例如,它可能是:
#define VALUE_ONE 20
#define VALUE_TWO 1500
#define VALUE_THREE 99777
我的编译器版本是:gcc(GCC)4.4.7
答案 0 :(得分:4)
首先将优化保留给编译器。专注于编写人类可读的代码。仅在遇到计时问题时以及在对代码进行概要分析后进行优化。然后专注于热点。如果某些代码适用于分支预测,很难用现代CPU预测。
使用switch
(为了便于阅读,请查看好的C书)声明,以使代码更易读:
switch ( count ) {
case VALUE_ONE:
f1();
break;
case VALUE_TWO:
f2();
break;
...
default:
// be aware to catch illegal/forgotten values, unless you
// are absolutely sure they can be ignored safely.
// still having a default label is good style to signal "I
// though about it".
break;
}
这不仅是最易读的版本,而且还为编译器提供了优化代码的最佳机会。
如果值只是增加1(1
,2
,3
,...),现代编译器将自动生成跳转表,即使是部分继承( 1
,2
,3
,7
,8
等等,因此与手动创建的函数表一样快。如果不是,它仍然会产生类似if ... else if ... else if ...
构造的东西。
请注意,case-labels必须是 constant-expressions 。
修改:在您明确说明这些值可能不是很明显之后,我的答案仍然适用。根据比较值的数量,switch
仍然是最佳解决方案,除非 prooved 错误。首先尝试使用此配置文件,然后仅在必要时进行优化。哈希表可能不值得付出努力。
即使您使用哈希函数,上面的switch
也会派上用场。只需使用哈希值而不是count
。
答案 1 :(得分:3)
为什么世界上你担心分支错误预测?你有工作计划吗?它运行得太慢了吗?你有没有把问题缩小到你提出的代码中的错误预测?除非每个问题的答案都是“是”,否则你就会过早优化。
此外,您提供的代码中的条件分支似乎是高度可预测的,至少如果计数器通常需要达到数十或数十万或更多的值,正如更新的示例代码所示。大约0.00001或更低的错误预测率(大约是您所期望的)将不会产生可衡量的性能影响。实际上,处理代码就像你提出的那样是分支预测的基础。你很难要求一个对分支预测单元更友好的案例。
无论如何,既然你担心分支错误预测,你的问题一定不是特别避免if
语句,而是一般地避免条件逻辑。因此,switch
构造可能并不是更好,至少不是因为你描述的情况,你想要仅为少数函数将看到的大量不同值调用函数,在广泛的范围内范围。虽然编译器原则上可以通过跳转表实现这样的switch
,但是由于需要的表有多大,并且有多少元素与一个用于默认情况。
还讨论了一个哈希表,但这并不是更好,因为你需要条件逻辑来区分缓存命中和缓存未命中,否则你的哈希表必须为每个输入提供一个函数(指针)来调用。在每次迭代中调用函数将比您现在所做的要昂贵得多。
此外,您需要一个完美的哈希函数来避免HT实现中的条件逻辑。如果你的计数器的可能值受到足够小的数字限制,可以使用散列表/完美散列来避免条件逻辑,那么一个普通的函数指针数组将比散列表更轻,并且可以服务于同样的目的。但是,它仍会遇到与函数调用开销相同的问题。如果你坚持避免使用条件逻辑,那么这可能是解决你的特定问题的最佳方法。但不要。
答案 2 :(得分:1)
我怀疑原始功能是瓶颈还是优化的有效位置。但是,嘿,我喜欢谜题......
鉴于计数递增且匹配值增加,您实际上只需要测试即将到来的匹配值。虽然您不能将匹配值用作数组索引,但您可以创建可用作数组索引的状态。尝试这样的事情。
static unsigned int count = 0;
typedef enum
{
WAITING_FOR_VALUE_ONE = 0,
WAITING_FOR_VALUE_TWO,
WAITING_FOR_VALUE_THREE,
...,
WAITING_FOR_MAX_VALUE,
MAX_STATES
} MyStates;
static MyStates state = WAITING_FOR_VALUE_ONE;
void waitForValueOne()
{
if (count == VALUE_ONE)
{
A();
state++;
}
}
void waitForValueTwo()
{
if (count == VALUE_TWO)
{
B();
state++;
}
}
void waitForMaxValue()
{
if (count == MAX_VAL)
{
count = 0;
state = 0;
}
}
void (*stateHandlers[MAX_STATES]) () =
{
waitForValueOne,
waitForValueTwo,
waitForValueThree,
...
waitForMaxValue
}
void loop()
{
(*stateHandlers[state])();
count++;
}
在count达到MAX_VAL之后,你的原始实现将运行count = 0的下一个循环,而我的实现将运行count = 1的下一个循环。但是我确信如果它很重要,你可以解决它。
<强>更新强>
我不喜欢loop
如何调用状态处理程序。它确实只需要在匹配时调用状态处理程序。并且如果在loop
中执行,则不需要在每个状态处理函数中重复比较。以下是一些实现此改进的编辑。
static MyStates state = WAITING_FOR_VALUE_ONE;
static unsigned int matchValue = VALUE_ONE;
void waitForValueOne()
{
A();
state++;
matchValue = VALUE_TWO;
}
void waitForValueTwo()
{
B();
state++;
matchValue = VALUE_THREE;
}
void waitForMaxValue()
{
count = 0;
state = 0;
matchValue = VALUE_ONE;
}
void loop()
{
if (count == matchValue)
{
(*stateHandlers[state])();
}
count++;
}
答案 3 :(得分:0)
在您的情况下,我看不出任何优化的理由 但是如果你的中断每20μs被触发一次,你的处理程序会占用整个cpu时间的50%,当你检查aginst 200值时,那么你才能改变代码。
对于递增计数器,您只需要一个if
,因为您始终知道哪个值将成为下一个值。
void isr(void)
{
count++;
if (count == nextValue)
{
if ( count == VALUE_ONE )
{
A();
nextValue=VALUE_TWO;
}
else if ( count == VALUE_TWO )
{
B();
nextValue=VALUE_THREE;
}
...
}
}
在99%的情况下,ISR()只需要递增计数器并检查是否未达到该值。
实际上,我会使用一系列操作和时间,而不是if else if
块。