当计数器达到某个值而没有if语句时调用函数

时间:2016-09-19 14:28:17

标签: c if-statement embedded

假设我有一个名为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_ONEVALUE_TWOVALUE_THREE等可能是非常大的值,而不会增加1。例如,它可能是:

#define VALUE_ONE 20
#define VALUE_TWO 1500
#define VALUE_THREE 99777

我的编译器版本是:gcc(GCC)4.4.7

4 个答案:

答案 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(123,...),现代编译器将自动生成跳转表,即使是部分继承( 12378等等,因此与手动创建的函数表一样快。如果不是,它仍然会产生类似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块。