提高16位处理器上32位数学性能

3
我正在为一个嵌入式设备编写固件,使用的是一个运行在40 MIPS的16位PIC,并用C语言进行编程。该系统将控制两个步进电机的位置,并始终保持每个电机的步进位置。每个电机的最大位置约为125000步,因此我不能使用16位整数来跟踪位置,必须使用32位无符号整数(DWORD)。电机以每秒1000步的速度移动,我已经设计好了固件,使得步骤在定时器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,如果有人感兴趣的话。 - PICyourBrain
3
只要你只进行32位值的加减法操作,不进行乘除法(尤其是不要进行除法),对性能影响应该非常小。寻找其他原因造成的延迟,它们不是由32位加法/递增引起的。 - Ben Voigt
@Ben Voigt:减去32位数字和16位数字的指令有什么区别?会多两倍吗? - PICyourBrain
1
取决于指令集是否提供“带进位加”和相应的“借位减”指令。如果有,分别只需进行加法、带进位加法或减法、借位减法(2个指令)。如果没有,则必须模拟借位,这会多一点。另一件事是编译器可能做得很糟糕。即使如此,开关块似乎比增量/减量更昂贵。 - pmdj
编译器为这些32位数学运算生成了什么样的汇编代码? - bdonlan
显示剩余4条评论
5个回答

6

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

假设上述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),并使用示波器测量其持续时间。或者找出其他更容易的方法。测量程序各个部分花费的时间,然后仅在确实必要时进行优化,而不是在您之前认为需要优化的地方。


3
使用示波器作为剖析工具是个不错的选择,让我想起了大学时代的夜间黑客攻击。 - Albin Sunnanbo

1

16位和32位算术之间的差异不应该太大,我认为,因为你只使用增加和比较。但也许问题是每个32位算术操作都意味着一个函数调用(如果编译器不能/不愿意内联更简单的操作)。

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

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


在16位硬件上枚举32位加法是很便宜的(比跳转便宜得多)。如果你担心性能问题,那么乘法、除法和浮点运算是需要关注的,因为这些在软件实现中要昂贵得多。 - SecurityMatt
我所指的问题不是做数学运算的时间,而是函数调用的时间。我建议只有在编译器无法进行内联时才这样做。 - Fabio Ceconello
从技术上讲,这取决于编译器和硬件,但几乎没有人会通过函数调用在16位硬件上执行32位整数加法。在16位x86上,它将通过ADD紧接着ADC(带进位的加法)来完成。 - SecurityMatt
请注意,这个问题涉及嵌入式设备。在内存受限的环境中,使用数学库的函数调用更为普遍。 - Fabio Ceconello
那不会改变任何事情。在16位设备上,函数调用至少需要3个字节来编码,而32位加法只需要2个字节。因此,函数调用既慢又大。 - SecurityMatt

0

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


0

抱歉,但您正在使用糟糕的程序设计。

让我们来检查一下16位和32位PIC24或PIC33汇编代码之间的区别...

16位增量

inc    PosInt16               ;one cycle

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

32位增量

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

32增量需要三个周期。

总差异是2个周期或50纳秒。

简单的计算就可以告诉你全部。你每秒有1000步,并且40Mips DSP所以你每秒有40000个指令在1000步中运行。完全足够!


现在比较乘法的速度。 - SecurityMatt

0

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

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

很可能只是通过从16位更改为32位,某些操作就被编译成不同的方式,请在两组编译的ASM代码之间进行Diff,并查看实际上有什么不同,是否很多,还是只有几行。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接