代码之家  ›  专栏  ›  技术社区  ›  supercat

函数处理1-256字节的最佳实践

  •  8
  • supercat  · 技术社区  · 14 年前

    我有一些函数设计用来处理1-256个字节,在嵌入式C平台上运行,其中传递一个字节比传递一个int(一条指令对三条指令)快得多,也更紧凑,首选的编码方式是什么:

    1. 接受int,如果为零则提前退出,否则将count值的LSB复制到无符号字符中,并在do{}while(--count);循环中使用它(参数值256将转换为0,但将运行256次)
    2. 接受一个无符号字符,如果为0,则提前退出,并具有256字节的函数的特殊版本(这些情况将提前知道)。
    3. 有一个类似于上面的函数,但是通过包装器函数调用它,包装器函数的行为是(0-255)和(256)。
    4. 有一个类似于上面的函数,但是通过包装器宏调用它,包装器宏的行为是(0-255)和(256)。

    当系统繁忙时,预期函数的内环可能代表处理器执行时间的15%-30%;有时用于小字节数,有时用于大字节数。该函数使用的内存芯片有一个事务开销,我更喜欢让我的内存访问函数在内部执行启动事务/执行事务/结束事务序列。

    最有效的代码是简单地接受一个无符号字符,并将参数值0视为执行256字节的请求,依靠调用方避免任何意外尝试读取0字节。不过,这似乎有点危险。其他人在嵌入式系统上处理过这样的问题吗?他们是怎么处理的?

    编辑 该平台是一个PIC18Fxx(128K代码空间;3.5K RAM),连接到一个SPI闪存芯片;如果读取的字节数较少,则可能会超出PIC中的读取缓冲区。写入256字节而不是0会损坏闪存芯片中的数据。如果不检查忙状态,PIC的SPI端口被限制为每12个指令次一个字节;如果检查忙状态,则速度会变慢。典型的写事务除了要接收的数据外还需要发送4个字节;读事务需要额外的字节来实现“SPI周转”(访问SPI端口的最快方法是在发送下一个字节之前读取最后一个字节)。

    编译器为HiTech PICC-18std。

    我通常喜欢HiTech的PICC-16编译器;HiTech似乎把精力从PICC-18std产品转移到了他们的PICC-18pro系列上,该系列的编译时间更慢,似乎需要使用3字节的“const”指针,而不是2字节指针,并且对内存分配有自己的想法。也许我应该多看看PICC-18pro,但当我尝试在PICC-18pro的评估版上编译我的项目时,它不起作用,我也没有弄清楚确切的原因——也许是变量布局不符合我的asm例程——我只是一直使用PICC-18std。

      do
      {
        local_test++;
        --lpw;
      } while(lpw);
    
      2533                           ;newflashpic.c: 792: do
      2534                           ;newflashpic.c: 793: {
      2535  0144A8  2AD9                incf    fsr2l,f,c
      2536                           ;newflashpic.c: 795: } while(--lpw);
      2537  0144AA  0E00                movlw   low ?_var_test
      2538  0144AC  6EE9                movwf   fsr0l,c
      2539  0144AE  0E01                movlw   high ?_var_test
      2540  0144B0  6EEA                movwf   fsr0h,c
      2541  0144B2  06EE                decf    postinc0,f,c
      2542  0144B4  0E00                movlw   0
      2543  0144B6  5AED                subwfb  postdec0,f,c
      2544  0144B8  50EE                movf    postinc0,w,c
      2545  0144BA  10ED                iorwf   postdec0,w,c
      2546  0144BC  E1F5                bnz l242
    

    编译器加载指向变量的指针,甚至不使用LFSR指令(需要两个字),而是使用MOVLW/MOVWF的组合(需要四个字)。然后它使用这个指针进行递减和比较。尽管我承认do{}While(--wordvar);不能产生像do{}While(wordvar--)那样好的代码,但代码比后者实际生成的更好。执行单独的减量和while测试(例如while(--lpw,lpw))会产生合理的代码,但看起来有点难看。后减量运算符可以为倒计时循环生成最佳代码:

      decf _lpw
      btfss _STATUS,0 ; Skip next inst if carry (i.e. wasn't zero)
       decf _lpw+1
      bc    loop  ; Carry will be clear only if lpw was zero
    

    但它生成的代码却比lpw更糟糕。最好的代码是递增计数循环:

      infsnz  _lpw
       incfsz _lpw+1
       bra loop
    

    编辑2 我可能使用的另一种方法是:为字节数分配一个全局16位变量,并编写函数,以便在退出之前计数器总是归零。如果只需要8位值,则只需要加载8位。我会用宏来处理一些东西,这样就可以调整它们以获得最佳的效率。在PIC上,对一个已知为零的变量使用|=绝不比使用=,有时更快。例如,intvar |=15或intvar |=0x300将是两条指令(每种情况只需处理结果的一个字节,可以忽略另一个字节);intvar |=4(或2的任意幂)是一条指令。显然,在其他一些处理器上,intvar=0x300要比intvar |=0x300快;如果我使用宏,可以根据需要对其进行调整。

    3 回复  |  直到 14 年前
        1
  •  0
  •   Michael Burr    14 年前

    FWIW,我会选择选项1的一些变体。函数的接口仍然是合理的、直观的,并且似乎不太可能被错误地调用(如果传入的值大于256,您可能需要考虑您想要做什么-只进行调试的断言可能是合适的)。

    我不认为使用8位计数器循环正确次数的小“黑客”或微优化真的是一个维护问题,似乎你已经做了大量的分析来证明这一点。

    如果有人喜欢包装器,我不会反对,但我个人倾向于选项1,甚至有点倾向。

        2
  •  2
  •   Doug Currie    14 年前

    你的内在功能应该复制 count + 1

     do /* copy one byte */ while(count-- != 0);
    

    如果后减量很慢,其他替代方法是:

     ... /* copy one byte */
     while (count != 0) { /* copy one byte */; count -= 1; }
    

     for (;;) { /* copy one byte */; if (count == 0) break; count -= 1; }
    

    调用方/包装方可以执行以下操作:

    if (count > 0 && count <= 256) inner((uint8_t)(count-1))

    if (((unsigned )(count - 1)) < 256u) inner((uint8_t)(count-1))

    如果它在编译器中更快。

        3
  •  0
  •   nmichaels    14 年前

    如果int参数需要3条指令,char参数需要1条指令,则可以为缺少的额外1位传递额外的char参数。看起来相当愚蠢的是,int(大概是16位)占用的指令数是8位字符的两倍多。