【问题标题】:Best practice for function to handle 1-256 bytes处理 1-256 字节的函数的最佳实践
【发布时间】:2011-04-01 05:21:08
【问题描述】:

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

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

预计系统繁忙时,函数的内循环大概会占处理器执行时间的15%-30%;它有时用于少量字节,有时用于大字节。该函数使用的内存芯片具有每个事务的开销,我更喜欢让我的内存访问函数在内部执行 start-transaction/do-stuff/end-transaction 序列。

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

编辑 平台为PIC18Fxx(128K代码空间;3.5K RAM),连接SPI flash芯片;当预期更少时读取 256 字节可能会超出 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 的 eval 版本上编译我的项目时,它不起作用,我也没有弄清楚为什么——也许是关于可变布局的一些不同意我的 asm 例程——我一直在使用 PICC-18std。

顺便说一句,我刚刚发现 PICC-18 特别喜欢 do {} while(--bytevar);尤其不喜欢 do {} while(--intvar);我想知道编译器在生成后者时的“思想”是什么?

做 { 本地测试++; --lpw; } 而(lpw); 第2533章 第2534章 2535 0144A8 2AD9 incf fsr2l,f,c 第2536章 2537 0144AA 0E00 movlw 低 ?_var_test 2538 0144AC 6EE9 movwf fsr0l,c 2539 0144AE 0E01 movlw 高 ?_var_test 2540 0144B0 6EEA movwf fsr0h,c 2541 0144B2 06EE decf postinc0,f,c 2542 0144B4 0E00 移动 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-test(例如 while (--lpw,lpw))会产生合理的代码,但看起来有点难看。后递减运算符可以为向下计数循环生成最佳代码:

decf _lpw btfss _STATUS,0 ;如果进位(即不为零)则跳过下一个 inst decf _lpw+1 公元前循环;只有当 lpw 为零时进位才会被清除

但它会生成比 --lpw 更糟糕的代码。最好的代码是向上计数循环:

infsnz _lpw incfsz _lpw+1 文胸环

但编译器不会生成它。

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

【问题讨论】:

  • 除了浪费时间之外,读取 0 字节有什么真正的危险吗?
  • 如果要读取的数据量是以字节为单位存储的,并且需要有读取256字节的能力,那么将0解释为256是合理的。在这种情况下“读取0字节”实际上会读取可能尚未准备好读取的 256 个字节,并且可能会严重搞砸。
  • 您能分享一下您的平台信息吗?在这个时代找到面向字节的东西是相当有趣的。大多数嵌入式 CPU 选择严格对齐,因为这样可以在关键硅部件中节省少量电线。
  • @Dummy00001,PIC18是8位处理器;如果那样的话,RAM 通常只有几个 K 字节。
  • @Zardoz89: 是的,但是如果传递一个零值,do {...} while(--byteVar); 循环(或 ASM 等效项)将循环 256 次。在 1980 年代的计算机上,编写循环计数为零的例程可能会请求 256 个循环的例程与完全跳过循环的例程一样多。可以肯定的是,值 0 表示比最大可传递值“更大”的值有点奇怪,但它与一些接受无符号超时值但使用 0 作为特殊情况的 OS 延迟例程并不矛盾“无限期等待事件”。

标签: c embedded pic pic18


【解决方案1】:

你的内部函数应该复制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))

如果它在你的编译器中更快。

【讨论】:

  • 您可以将其包装在一个宏中,以便导出的界面是健全的。
【解决方案2】:

如果一个 int 参数需要 3 条指令,而一个 char 参数需要 1 条指令,那么您可以为缺少的额外 1 位传递一个额外的 char 参数。您的(可能是 16 位)int 需要的指令是 8 位字符的两倍多,这似乎很愚蠢。

【讨论】:

  • 编译器将允许在W寄存器中传递一个char参数;所有其他参数都有分配给它们的 RAM 位置。在许多情况下,如果编译器可以在 W 中传递字参数的 LSB,那将是有利的,但事实并非如此。实际上,如果我正在设计一个编译器,我可能会指定每个不接受 char 但接受单词的函数都有两个入口点;一个入口点会将 W 存储到 LSB,然后通过另一个入口点。其他例程可以调用最有利的例程。
  • 这太疯狂了。我认为图片应该是 RISCy。哦,好吧。
【解决方案3】:

FWIW,我会选择选项 #1 的一些变体。该函数的界面保持合理、直观,并且似乎不太可能被错误调用(您可能需要考虑如果传入大于 256 的值要做什么 - 仅调试构建断言可能是合适的)。

我不认为使用 8 位计数器循环正确次数的次要“hack”/微优化确实是一个维护问题,而且您似乎已经进行了大量分析来证明它的合理性。

如果有人喜欢它们,我不会反对包装器,但我个人会稍微倾向于选项 1。

但是,我反对让公共接口要求调用者传入比他们想要读取的值小 1 的值。

【讨论】:

  • @Micnael Burr:提供要处理的实际字节数,不管是什么,“感觉”是对的,尽管我讨厌浪费代码空间。当然它比通过n-1有更好的“感觉”。处理任意数量的字节可能不是不合理的,即使我不希望任何缓冲区大于 256 字节。像空白检查范围这样的例程可以很好地用于大于 256 字节的计数,并且如果内部循环仅使用 8 位变量,则接受更大的变量的成本不会很大。我会在上面发表一些进一步的想法。
猜你喜欢
  • 1970-01-01
  • 2013-08-25
  • 2020-10-21
  • 1970-01-01
  • 2012-10-07
  • 1970-01-01
  • 2015-09-25
  • 2011-05-22
  • 1970-01-01
相关资源
最近更新 更多