我有一个概念性问题:什么是“高”代码密度?为什么它如此重要?
关于代码密度及其定义
代码密度泛指执行请求的操作需要多少条微处理器指令,以及每条指令占用多少空间。一般来说,一条指令占用的空间越少,微处理器可以完成的每条指令的工作量越多,它的代码就越密集。
我注意到您已用“arm”标签标记了您的问题;我可以使用 ARM 指令来说明代码密度。
假设您要将一块数据从内存中的一个位置复制到另一个位置。从概念上讲,您的高级代码看起来像这样:
void memcpy(void *dest, void *source, int count_bytes)
{
char *s, *d;
s = source; d = dest;
while(count_bytes--) { *d++ = *s++; }
}
现在,用于简单微处理器的简单编译器可以将其转换为如下内容:
movl r0, count_bytes
movl r1, s
movl r2, d
loop: ldrb r3, [r1]
strb [r2], r3
movl r3, 1
add r1, r3
add r2, r3
sub r0, r3
cmp r0, 0
bne loop
(我的 ARM 有点生锈,但你明白了)
现在这将是一个非常简单的编译器和一个非常简单的微处理器,但您可以从示例中看到,我们正在查看每次循环迭代的 8 条指令(如果我们将“1”移动到另一个寄存器并移动负载,则为 7循环外)。这根本不是很稠密。代码密度也会影响性能;如果您的循环较长,因为代码不密集,您可能需要更多指令缓存来保存循环。更多的缓存意味着更昂贵的处理器,但是复杂的指令解码又意味着更多的晶体管来破译请求的指令,所以这是一个经典的工程问题。
ARM 在这方面做得很好。每条指令都可以是有条件的,大多数指令可以增加或减少寄存器的值,并且大多数指令可以选择性地更新处理器标志。在 ARM 上并使用适度有用的编译器,相同的循环可能看起来像这样:
movl r0, count_bytes
movl r1, s
movl r2, d
loop: ldrb r3, [r1++]
strb [r2++], r3
subs r0, r0, 1
bne loop
如您所见,主循环现在是 4 条指令。代码更密集,因为主循环中的每条指令执行更多。这通常意味着您可以使用给定的内存量做更多的事情,因为更少的内存用于描述如何执行工作。
现在原生 ARM 代码经常抱怨它不是超级密集的;这是由于两个主要原因:首先,32 位是一条非常“长”的指令,因此对于更简单的指令似乎浪费了很多位,其次,由于 ARM 的性质,代码变得臃肿:每条指令都是 32位长,无一例外。这意味着有大量的 32 位文字值,您不能只将它们加载到寄存器中。如果我想将“0x12345678”加载到 r0 中,我如何编写一条不仅包含 0x12345678,而且还描述“加载文字到 r0”的指令?没有剩余位可用于编码实际操作。ARM 加载文字指令是一个有趣的小野兽,而且 ARM 汇编器也必须比普通的汇编器聪明一点,因为它必须“捕捉”
无论如何,为了回答这些抱怨,ARM 提出了 Thumb 模式。现在几乎所有指令的指令长度为 16 位,分支为 32 位,而不是每条指令 32 位。Thumb 模式有一些牺牲,但总的来说,这些牺牲很容易做出,因为 Thumb 仅通过减少指令长度就可以将代码密度提高 40%。
指令集的“代码密度”是衡量您可以在给定数量的程序内存中获取多少内容,或者需要多少字节的程序内存来存储给定数量的功能的量度。
正如 Andrew Kohlsmith 所指出的,即使在同一个 MCU 上,不同的编译器也可以获得不同的代码密度。
您可能会喜欢阅读 Miro Samek的“计算机世界的昆虫”,它比较了各种 MCU。