【问题标题】:Increasing performance of 32bit math on 16bit processor提高 16 位处理器上 32 位数学的性能
【发布时间】:2011-12-22 08:45:19
【问题描述】:

我正在为嵌入式设备开发一些固件,该设备使用 16 位 PIC,以 40 MIPS 运行并用 C 语言编程。系统将控制两个步进电机的位置,并始终保持每个电机的步进位置。每个电机的最大位置大约是 125000 步,所以我不能使用 16 位整数来跟踪位置。我必须使用 32 位无符号整数 (DWORD)。电机以每秒 1000 步的速度移动,我设计了固件,以便在 Timer ISR 中处理步数。计时器 ISR 执行以下操作:

1) 将一个电机的当前位置与目标位置进行比较,如果它们相同,则将 isMoving 标志设置为 false 并返回。如果它们不同,则将 isMoving 标志设置为 true。

2) 如果目标位置大于当前位置,向前移动一步,然后增加当前位置。

3) 如果目标位置小于当前位置,则向后移动一步,然后递减当前位置。

代码如下:

void _ISR _NOPSV _T4Interrupt(void)
{
    static char StepperIndex1 = 'A';    

    if(Device1.statusStr.CurrentPosition == Device1.statusStr.TargetPosition)
    {
        Device1.statusStr.IsMoving = 0;
        // Do Nothing
    }   
    else if (Device1.statusStr.CurrentPosition > Device1.statusStr.TargetPosition)
    {
        switch (StepperIndex1)      // MOVE OUT
        {
            case 'A':
                SetMotor1PosB();
                StepperIndex1 = 'B';
                break;
            case 'B':
                SetMotor1PosC();
                StepperIndex1 = 'C';
                break;
            case 'C':
                SetMotor1PosD();
                StepperIndex1 = 'D';
                break;
            case 'D':
                default:
                SetMotor1PosA();
                StepperIndex1 = 'A';
                break;      
        }
        Device1.statusStr.CurrentPosition--;    
        Device1.statusStr.IsMoving = 1;
    }   
    else
    {
        switch (StepperIndex1)      // MOVE IN 
        {
            case 'A':
                SetMotor1PosD();
                StepperIndex1 = 'D';
                break;
            case 'B':
                SetMotor1PosA();
                StepperIndex1 = 'A';
                break;
            case 'C':
                SetMotor1PosB();
                StepperIndex1 = 'B';
                break;
            case 'D':
                default:
                SetMotor1PosC();
                StepperIndex1 = 'C';
                break;      
        }
        Device1.statusStr.CurrentPosition++;
        Device1.statusStr.IsMoving = 1;
    }   
    _T4IF = 0;          // Clear the Timer 4 Interrupt Flag.
}

目标位置在收到移动请求时在主程序循环中设置。 SetMotorPos 行只是用于打开/关闭特定端口引脚的宏。

我的问题是:有什么办法可以提高这段代码的效率吗?如果位置是 16 位整数,则代码功能正常,但作为 32 位整数,处理过多。该设备必须毫不犹豫地与 PC 通信,并且正如所写的那样,性能会受到明显影响。我真的只需要 18 位数学,但我不知道这样做的简单方法!任何建设性的意见/建议将不胜感激。

【问题讨论】:

  • 设备是PIC24HJ128GP204,以防有人感兴趣。
  • 编译器生成什么?您正在使用哪些优化标志?
  • 只要你只是加减 32 位值,而不是乘法或除法(尤其是不除法),对性能的影响应该很小。在别处寻找您的延迟,它们不是由 32 位加法/增量引起的。
  • @Ben Voigt:将 32 位数字与 16 位数字相减的指令有什么区别?会是两倍吗?
  • 取决于指令集是否提供“带进位加法”和相应的“借位减法”指令。如果是这样,它只是一个 add 和 add-with-carry 或一个 sub 和一个 sub-with-borrow ,分别(2条指令)。如果没有,必须模拟借位,这将是一个多一点。另一件事是编译器可能做得很糟糕。即便如此,您的 switch 块似乎更有可能比增量/减量更昂贵。

标签: c performance 32-bit pic 16-bit


【解决方案1】:

警告:所有数字都是虚构的......

假设上述 ISR 有大约 200 条(可能更少)编译代码指令,其中包括在 ISR 之前和之后保存/恢复 CPU 寄存器的指令,每个指令需要 5 个时钟周期(可能是 1 到 3)如果您每秒调用其中 2 个 1000 次,我们最终得到 2*1000*200*5 = 2 百万个时钟周期每秒或 2 MIPS。

你真的会在其他地方消耗剩下的 38 MIPS 吗?

这里唯一可能很重要但我看不到的是 SetMotor*Pos*() 函数内部所做的事情。他们会做任何复杂的计算吗?他们是否与电机进行一些缓慢的通信,例如?等待他们响应发送给他们的命令?

无论如何,这样简单的代码在处理 32 位整数时会明显比处理 16 位更慢是值得怀疑的。

如果您的代码运行缓慢,请找出花费时间和花费的时间,然后对其进行分析。在 ISR 中生成一个方脉冲信号(当 ISR 启动时变为 1,当 ISR 即将返回时变为 0)并用示波器测量其持续时间。或者做任何更容易找到它的事情。衡量在程序的所有部分花费的时间,然后在真正需要的地方进行优化,而不是在您之前认为的地方进行优化。

【讨论】:

  • +1 使用示波器作为分析器,让我想起了很好的大学之夜黑客。
【解决方案2】:

我认为 16 位和 32 位算术之间的差异不应该那么大,因为您只使用增量和比较。但问题可能在于每个 32 位算术运算都意味着一个函数调用(如果编译器不能/不愿意做更简单操作的内联)。

一个建议是自己进行算术运算,将 Device1.statusStr.CurrentPosition 分成两部分,例如 Device1.statusStr.CurrentPositionH 和 Device1.statusStr.CurrentPositionL。然后使用一些宏来进行操作,比如:

#define INC(xH,xL) {xL++;if (xL == 0) xH++;}

【讨论】:

  • 在 16 位硬件上枚举 32 位添加很便宜(而且比跳转便宜很多)。如果您担心性能问题,那么乘法、除法和浮点数就是要寻找的东西,因为在软件中实现它们的成本要高得多。
  • 我指的问题不是计算的时间,而是函数调用的时间。我建议仅在编译后不会进行内联时才这样做。
  • 技术上它取决于编译器和硬件,但几乎没有人会通过函数调用在 16 位硬件上进行 32 位整数相加。在 16 位 x86 上,这将由 ADD 完成,然后是 ADC(带进位相加)。
  • 请注意,问题是关于嵌入式设备的。在内存受限的环境中,对数学库进行函数调用更为常见。
  • 这不会改变任何事情。 16 位设备上的函数调用至少需要 3 个字节进行编码,而 32 位加法可以在 2 个字节中完成。因此,函数调用更慢并且更大。
【解决方案3】:

我会去掉StepperIndex1 变量,而是使用CurrentPosition 的两个低位来跟踪当前步长索引。或者,跟踪完整旋转(而不是每一步)的当前位置,以便它可以放入 16 位变量中。移动时,您仅在移动到“A”阶段时增加/减少位置。当然,这意味着您只能针对每个完整的旋转,而不是每个步骤。

【讨论】:

    【解决方案4】:

    抱歉,您使用了糟糕的程序设计。

    让我们来看看 16 位和 32 位 PIC24 或 PIC33 asm 代码的区别...

    16 位增量

    inc    PosInt16               ;one cycle
    

    所以 16 位增量需要一个周期

    32 位增量

    clr    Wd                     ;one cycle
    inc    low PosInt32           ;one cycle
    addc   high PosInt32, Wd      ;one cycle
    

    32 增量需要三个周期。 总差异为 2 个周期或 50ns(纳秒)。

    简单的计算会告诉你一切。您每秒有 1000 步和 40Mips DSP因此在每秒 1000 步的情况下每步有 40000 条指令。绰绰有余!

    【讨论】:

    • 现在比较乘法速度。
    【解决方案5】:

    当您将其从 16 位更改为 32 位时,您是否更改任何编译标志以告诉它编译为 32 位应用程序。

    您是否尝试过使用 32 位扩展但仅使用 16 位整数进行编译。你还会遇到这样的性能下降吗?

    很可能只是通过从 16 位更改为 32 位,某些操作的编译方式不同,也许在两组编译的 ASM 代码之间做一个 Diff,看看实际上有什么不同,是很多还是只有几行.

    解决方案可能不是使用 32 位整数,而是使用两个 16 位整数, 当 valueA 为 int16.Max 时,将其设置为 0,然后将 valueB 增加 1,否则只需将 ValueA 增加 1,当值 B >= 3 时,您然后检查 valueA >= 26696(或类似的值,具体取决于您使用无符号或有符号int16) 然后在 12500 处检查电机。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-02
      • 2015-06-19
      • 1970-01-01
      • 1970-01-01
      • 2010-09-19
      • 2018-03-31
      相关资源
      最近更新 更多