【问题标题】:Why int is preffered rather than (unsigned) char for small integers in MSP430-GCC为什么在 MSP430-GCC 中对于小整数优先使用 int 而不是 (unsigned) char
【发布时间】:2023-03-14 06:47:01
【问题描述】:

在 msgpcc(用于 MSP430 微控制器的 GCC)manual 作者写道:

如果你想在函数中使用一个小整数,请使用 int 而不是 char 或 unsigned char。生成的代码会更高效,并且在大多数情况下实际上并没有浪费存储空间。

为什么int效率更高?

UPD. 以及为什么定义为(unsigned) char 的mspgcc 中的(u)int_fast8_t,而不是(unsigned) int。据我了解,(u)int_fast*_t 应该定义为具有足够大小的最有效类型。

【问题讨论】:

  • afaik MSP430 在 8 位和 16 位操作数之间的指令延迟方面没有差异。我也很好奇。
  • 我想补充一点,我喜欢(u)int_(fast|least)(8|16|32)_t 的美妙之处在于,您可以指定您想要的优化类型,并且通常让编译器处理它。如果您想使用尽可能少的空间并且需要最多表示 20,000,请使用 (u)int_least16_t,但如果您正在使用需要快速计算的繁重计算,您可以在很大程度上使用 (u)int_fast16_t 而不必担心什么键入它最终在引擎盖下使用。

标签: c assembly microcontroller msp430 mspgcc


【解决方案1】:

一般的经验法则是 CPU 在处理其原生字长的整数时速度最快

这当然完全取决于架构,有关这一点的更多说明,请参阅this similar question 的答案。

【讨论】:

    【解决方案2】:

    TI 已针对其 Tiva-C(原名 Stellaris)MCU 发布了有关该主题的应用说明。

    在“简介”部分,一个表格列出了影响性能和大小的因素。 因素标签变量大小指出使用小于最佳值的变量可能意味着额外的指令来签署或取消签署扩展......

    此外,在“变量大小”部分下,它指出:

    "当局部变量小于寄存器大小时,通常需要额外的代码。在 Stellaris 部分,这意味着大小字节和半字(分别为 char 和 short int)的局部变量需要额外的代码。因为代码从 8 位或 16 位微控制器移植可能已将本地变量转换为较小的大小(以避免太大的问题),这意味着此类代码将运行得更慢并占用比所需更多的代码空间。”

    请看:http://www.ti.com/lit/an/spma014/spma014.pdf

    以下由编译器处理,但仍与手头的问题相关:

    MSP430 是一个 16 位微处理器。一个字符只有 8 位,需要打包以确保所有字都对齐。例如,3 个字符不会在内存中正确对齐。相反,请使用一个 16 位的整数,并且始终对齐。

    当您使用 16 的倍数(例如 16 和 32)的可变大小时,您还可以更有效地利用内存。您最终不会使用填充来对齐内存。

    【讨论】:

    • 编译器的工作是确保没有对齐问题,并且它不会比使用 8 位变量可以工作的 16 位变量浪费更多的空间。您实际上已将填充从隐式过程移至显式过程。
    • @CoryNelson 我同意 - 它与编译器相关,而不是运行时。但是,您的说法不正确,即它不会占用更多内存。您是否尝试过同时使用 GCC 和 IAR 进行编译?它们都以不同的方式填充,如果您不小心(忽略优化),您最终会得到完全不同的代码大小。这似乎是由于填充。
    【解决方案3】:

    通常,不一定特定于该处理器,它与符号扩展和屏蔽有关,需要额外的指令才能忠实地实现 C 源代码。 16 位或 32 位或 64 位处理器中的有符号 8 位值可能涉及符号扩展的附加指令。在 32 位处理器上添加 8 位可能会涉及到 0xFF 等额外指令。

    你应该做一些简单的实验,它需要几次迭代,但我很快就发现了一些不同的东西。

    unsigned int fun ( unsigned int a, unsigned int b )
    {
        return(a+b)<<3;
    }
    
    unsigned char bfun ( unsigned char a, unsigned char b )
    {
        return(a+b)<<3;
    }
    
    
     int sfun (  int a,  int b )
    {
        return(a+b)<<3;
    }
    
     char sbfun (  char a,  char b )
    {
        return(a+b)<<3;
    }
    

    生产

    00000000 <fun>:
       0:   0f 5e           add r14,    r15 
       2:   0f 5f           rla r15     
       4:   0f 5f           rla r15     
       6:   0f 5f           rla r15     
       8:   30 41           ret         
    
    0000000a <bfun>:
       a:   4f 5e           add.b   r14,    r15 
       c:   4f 5f           rla.b   r15     
       e:   4f 5f           rla.b   r15     
      10:   4f 5f           rla.b   r15     
      12:   30 41           ret         
    
    00000014 <sfun>:
      14:   0f 5e           add r14,    r15 
      16:   0f 5f           rla r15     
      18:   0f 5f           rla r15     
      1a:   0f 5f           rla r15     
      1c:   30 41           ret         
    
    0000001e <sbfun>:
      1e:   8f 11           sxt r15     
      20:   8e 11           sxt r14     
      22:   0f 5e           add r14,    r15 
      24:   0f 5f           rla r15     
      26:   0f 5f           rla r15     
      28:   0f 5f           rla r15     
      2a:   4f 4f           mov.b   r15,    r15 
      2c:   30 41           ret         
    

    msp430 具有指令的字和字节版本,因此简单的加法或减法不必执行您在使用小于寄存器大小的变量时所期望的剪裁或符号扩展。作为一名程序员,我们可能知道我们只会提供给 sbfun 一些非常小的数字,但编译器不会并且必须忠实地按照编写的代码实现我们的代码,从而在 sfun 和 sbfun 之间生成更多代码。用不同的编译器和处理器做这些实验并不难看到这一点,唯一的诀窍是创建处理器没有简单指令来解决的代码。

    另一个例子

    unsigned int fun ( unsigned int a, unsigned int b )
    {
        return(a+b)>>1;
    }
    
    unsigned char bfun ( unsigned char a, unsigned char b )
    {
        return(a+b)>>1;
    }
    

    生产

    00000000 <fun>:
       0:   0f 5e           add r14,    r15 
       2:   12 c3           clrc            
       4:   0f 10           rrc r15     
       6:   30 41           ret         
    
    00000008 <bfun>:
       8:   4f 4f           mov.b   r15,    r15 
       a:   4e 4e           mov.b   r14,    r14 
       c:   0f 5e           add r14,    r15 
       e:   0f 11           rra r15     
      10:   4f 4f           mov.b   r15,    r15 
      12:   30 41           ret         
    

    【讨论】:

    • +1,虽然值得注意的是,使用较大类型的参数可能会导致堆栈使用量增加,我认为这可能是资源受限时的问题。
    • 是的,这完全是一场性能和优化游戏...想播下一些小种子,说明为什么越小不一定越好(相对而言)。
    【解决方案4】:

    int 匹配相关处理器的本机大小(16 位),因此当您请求存储到 unsigned char 变量时,编译器可能必须发出额外的代码以确保该值在 0 之间和 255。

    【讨论】:

    • 我认为你错了。编译器不会生成从 255 环绕到 0 的代码。MSP430 具有面向字节的指令,编译器只会使用 add.b 指令而不是 add
    • @Corvus 没错。 MSP430 有 27 条指令,大多数指令以 .B(8 位)和 .W(16 位)后缀版本提供。
    • 好的,在这种情况下很好,在其他处理器上不是这样。太久没搞MSP430了!我敢肯定还有其他情况(不仅仅是简单的增量)需要发出额外的 AND ......