gpt4 book ai didi

c - ARM 皮质 M0+ : How to use "Branch if Carry" instructions in C-code?

转载 作者:行者123 更新时间:2023-12-04 12:04:38 25 4
gpt4 key购买 nike

我有一些 C 代码可以逐位处理数据。简化示例:

// input data, assume this is initialized
uint32_t data[len];

for (uint32_t idx=0; idx<len; idx++)
{
uint32_t tmp = data[idx];

// iterate over all bits
for (uint8_t pos=0; pos<32; pos++)
{
if (tmp & 0b1)
{
// some code
}

tmp = tmp >> 1;
}
}
在我的应用程序中, len 比较大,所以我想尽可能地优化内部循环。 // some code 部分很小并且已经进行了大量优化。
我正在使用 ARM Cortex M0+ MCU,如果设置了进位位,该 MCU 具有分支指令(请参阅 cortex-m0+ manual ,第 45 页)。方便地移位位将 LSB(或 MSB)放入进位标志,因此理论上它可以在没有比较的情况下进行分支,如下所示:
// input data, assume this is initialized
uint32_t data[len];

for (uint32_t idx=0; idx<len; idx++)
{
uint32_t tmp = data[idx];

// iterate over all bits
for (uint8_t pos=0; pos<32; pos++)
{
tmp = tmp >> 1;

if ( CARRY_SET )
{
// some code
}
}
}
使用 C 代码和/或内联汇编程序对此进行存档的最佳方法是什么?理想情况下,为了简单和更好的可读性,我想将 // come code 保留在 C 中。

编辑 1:我已经使用 -O1、-O2 和 -03 在 GCC 5.4 GCC 6.3 上测试了此代码。对于每个设置,它都会生成以下汇编代码(请注意我尝试获取的专用 tst 指令):
        if (data & 0b1)             
00000218 movs r3, #1
0000021A tst r3, r6
0000021C beq #4

编辑 2:最小的可重现示例。我正在 Atmel Studio 7 中编写代码(因为它适用于 MCU)并检查内置调试器中的值。如果您使用不同的环境,您可能需要添加一些 IO 代码:
int main(void)
{
uint32_t tmp = 0x12345678;
volatile uint8_t bits = 0; // volatile needed in this example to prevent compiler from optimizing away all code.

// iterate over all bits
for (uint8_t pos=0; pos<32; pos++)
{
if (tmp & 1)
{
bits++; // the real code isn't popcount. Some compilers may transform this example loop into a different popcount algorithm if bits wasn't volatile.
}
tmp = tmp >> 1;
}

// read bits here with debugger
while(1);
}

最佳答案

我没有找到“简单”的解决方案,所以我不得不在汇编程序中编写我的简短算法。这是演示代码的样子:

// assume these values as initialized
uint32_t data[len]; // input data bit stream
uint32_t out; // algorithm input + output
uint32_t in; // algorithm input (value never written in asm)

for (uint32_t idx=0; idx<len; idx++)
{
uint32_t tmp = data[idx];

// iterate over all bits
for (uint8_t pos=0; pos<32; pos++)
{
// use optimized code only on supported devices
#if defined(__CORTEX_M) && (__CORTEX_M <= 4)
asm volatile // doesn't need to be volatile if you use the result
(
"LSR %[tmp], %[tmp], #1" "\n\t" // shift data by one. LSB is now in carry
"BCC END_%=" "\n\t" // branch if carry clear (LSB was not set)

/* your code here */ "\n\t"

"END_%=:" "\n\t" // label only, doesn't generate any instructions

: [tmp]"+l"(tmp), [out]"+l"(out) // out; l = register 0..7 = general purpose registers
: [in]"l"(in) // in;
: "cc" // clobbers: "cc" = CPU status flags have changed
// Add any other registers you use as temporaries, or use dummy output operands to let the compiler pick registers.
);
#else
if (tmp & 0b1)
{
// some code
}
tmp = tmp >> 1;
#endif
}
}
对于您的应用程序,在标记的位置添加您的汇编代码,并使用寄存器从 C 函数中输入数据。请记住,在 Thumb 模式下,许多指令只能使用 16 个通用寄存器中的 8 个,因此您不能传递更多的值。
内联汇编很容易以微妙的方式出错,这些方式看似有效,但在内联到不同的周围代码后可能会中断。 (例如,忘记声明一个clobber。) https://gcc.gnu.org/wiki/DontUseInlineAsm除非您需要(包括性能),但如果是这样,请确保您检查文档( https://stackoverflow.com/tags/inline-assembly/info )。
请注意,技术上正确的移位指令是 LSRS (带有 s 后缀来设置标志)。但是在 GCC 6.3 + GAS 写入 lsrs在 asm 代码中会导致在拇指模式下组装错误,但如果你写 lsr它成功组装成 lsrs操作说明。 (在 Cortex-M 不支持的 ARM 模式下, lsrlsrs 都按预期汇编为单独的指令。)

虽然我不能分享我的应用程序代码,但我可以告诉你这个变化有多少加速:



-O1
-O2
-O3


原来的
812us
780us
780us

带汇编
748us
686us
716us

带 asm + 一些循环展开
732us
606us
648us


因此,使用我的 ASM 代码和 -O2 而不是 -O1,我获得了 15% 的加速,并且通过额外的循环展开,我获得了 25% 的加速。
使用 __attribute__ ((section(".ramfunc"))) 将函数放在 RAM 中产生另外 1% 的改进。 (请务必在您的设备上对此进行测试,某些 MCU 的闪存缓存未命中惩罚很严重。)
有关更多通用优化的信息,请参阅下面的 old_timer 答案。

关于c - ARM 皮质 M0+ : How to use "Branch if Carry" instructions in C-code?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68601363/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com