【发布时间】:2011-04-01 05:21:08
【问题描述】:
我有一些旨在处理 1-256 字节的函数,在嵌入式 C 平台上运行,其中传递一个字节比传递一个 int 更快、更紧凑(一条指令对三条指令),什么是首选的方式编码:
- 接受一个 int,如果为零则提前退出,否则将计数值的 LSB 复制到一个无符号字符并在 do {} while(--count); 中使用它循环(参数值 256 将被转换为 0,但会运行 256 次)
- 接受一个无符号字符,如果为零则提前退出,并有一个用于 256 字节的特殊版本的函数(这些情况将提前知道)。
- 接受一个无符号字符,如果为零则运行 256 次。
- 具有与上述类似的函数,但通过行为为 (0-255) 和 (256) 的包装函数调用它。
- 具有与上述类似的函数,但通过行为为 (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 延迟例程并不矛盾“无限期等待事件”。