是我的 MIPS 编译器疯了,还是我选择了 MIPS 疯了?
我在嵌入式项目中使用 MIPS CPU (PIC32),但我开始质疑我的选择。 我知道像 MIPS 这样的 RISC CPU 会生成比预期更多的指令,但我没想到会是这样。这是反汇编列表中的一个片段:
225: LATDSET = 0x0040;
sw s1,24808(s2)
sw s4,24808(s2)
sw s4,24808(s2)
sw s1,24808(s2)
sw s4,24808(s3)
sw s4,24808(s3)
sw s1,24808(s3)
226: {
227: porte = PORTE;
lw t1,24848(s4)
andi v0,t1,0xffff
lw v1,24848(s6)
andi ra,v1,0xffff
lw v1,24848(s6)
andi ra,v1,0xffff
lw v0,24848(s6)
andi t2,v0,0xffff
lw a2,24848(s5)
andi v1,a2,0xffff
lw t2,24848(s5)
andi v1,t2,0xffff
lw v0,24848(s5)
andi t2,v0,0xffff
228: if (porte & 0x0004)
andi t2,v0,0x4
andi s8,ra,0x4
andi s8,ra,0x4
andi ra,t2,0x4
andi a1,v1,0x4
andi a2,v1,0x4
andi a2,t2,0x4
229: pst_bytes_somi[0] |= sliding_bit;
or t3,t4,s0
xori a3,t2,0x0
movz t3,s0,a3
addu s0,t3,zero
or t3,t4,s1
xori a3,s8,0x0
movz t3,s1,a3
addu s1,t3,zero
or t3,t4,s1
xori a3,s8,0x0
movz t3,s1,a3
addu s1,t3,zero
or v1,t4,s0
xori a3,ra,0x0
movz v1,s0,a3
addu s0,v1,zero
or a0,t4,s2
xori a3,a1,0x0
movz a0,s2,a3
addu s2,a0,zero
or t3,t4,s2
xori a3,a2,0x0
movz t3,s2,a3
addu s2,t3,zero
or v1,t4,s0
xori a3,a2,0x0
movz v1,s0,a3
这似乎是用于在固定地址进行简单读/写和测试变量的大量指令。在不同的 CPU 上,我可能可以将每个 C 语句减少到大约 1..3 条指令,而无需求助于手写汇编。显然,时钟速率相当高,但它并不比我在不同CPU(例如dsPIC)中的时钟速率高10倍。
我已将优化设置为最大。我的 C 编译器很糟糕吗(gcc 3.4.4)?或者这是 MIPS 的典型特征?
I am using a MIPS CPU (PIC32) in an embedded project, but I am starting to question my choice.
I understand that a RISC CPU like MIPS will generate more instructions than one might expect, but I didn't think it would be like this. Here is a snippet from the disassembly listing:
225: LATDSET = 0x0040;
sw s1,24808(s2)
sw s4,24808(s2)
sw s4,24808(s2)
sw s1,24808(s2)
sw s4,24808(s3)
sw s4,24808(s3)
sw s1,24808(s3)
226: {
227: porte = PORTE;
lw t1,24848(s4)
andi v0,t1,0xffff
lw v1,24848(s6)
andi ra,v1,0xffff
lw v1,24848(s6)
andi ra,v1,0xffff
lw v0,24848(s6)
andi t2,v0,0xffff
lw a2,24848(s5)
andi v1,a2,0xffff
lw t2,24848(s5)
andi v1,t2,0xffff
lw v0,24848(s5)
andi t2,v0,0xffff
228: if (porte & 0x0004)
andi t2,v0,0x4
andi s8,ra,0x4
andi s8,ra,0x4
andi ra,t2,0x4
andi a1,v1,0x4
andi a2,v1,0x4
andi a2,t2,0x4
229: pst_bytes_somi[0] |= sliding_bit;
or t3,t4,s0
xori a3,t2,0x0
movz t3,s0,a3
addu s0,t3,zero
or t3,t4,s1
xori a3,s8,0x0
movz t3,s1,a3
addu s1,t3,zero
or t3,t4,s1
xori a3,s8,0x0
movz t3,s1,a3
addu s1,t3,zero
or v1,t4,s0
xori a3,ra,0x0
movz v1,s0,a3
addu s0,v1,zero
or a0,t4,s2
xori a3,a1,0x0
movz a0,s2,a3
addu s2,a0,zero
or t3,t4,s2
xori a3,a2,0x0
movz t3,s2,a3
addu s2,t3,zero
or v1,t4,s0
xori a3,a2,0x0
movz v1,s0,a3
This seems like a crazy number of instructions for simple reading / writing and testing variables at fixed addresses. On a different CPU, I could probably get each C statement down to about 1..3 instructions, without resorting to hand-written asm. Obviously the clock rate is fairly high, but it's not 10x higher than what I would have in a different CPU (e.g. dsPIC).
I have optimisation set to maximum. Is my C compiler terrible (It's gcc 3.4.4)? Or is this typical of MIPS?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
终于找到答案了。反汇编列表完全具有误导性。编译器正在进行循环展开,我们在每个 C 语句下看到的实际上是 8 倍的指令数,因为它展开了 8 倍的循环。这些指令不是位于连续的地址!在编译器选项中关闭循环展开会产生以下结果:
每个人都感到恐慌。
Finally figured out the answer. The disassembly listing is totally misleading. The compiler is doing loop unrolling, and what we're seeing under each C statement is actually 8x the number of instructions, because it's unrolling the loop 8x. The instructions are not at consecutive addresses! Turning off loop unrolling in the compiler options produces this:
Panic over everyone.
我认为你的编译器行为不正常......
例如检查以下语句:
很明显,有些指令基本上什么也不做。指令(3)没有做任何新的事情,而是将指令(2)计算出的相同结果存储在 s8 中。
指令 (6) 也无效,因为它被下一条指令 (7) 覆盖,
我相信任何进行静态分析阶段的编译器至少会删除指令(3)和(6)。
类似的分析也适用于代码的其他部分。例如,在第一个语句中,您可以看到一些寄存器(v0 和 v0)两次加载相同的值。
我认为你的编译器在优化编译代码方面做得不好。
I think your compiler is misbehaving...
Check for example this statement:
It is obvious that there are instructions that basically do nothing. Instruction (3) does nothing as new as stores in s8 the same result computed by instruction (2).
Instruction (6) also has no effect, as it is overriden by the next instruction (7),
I believe any compiler which does some static analysis phase would at least remove instructions (3) and (6).
Similar analysis would apply to other portions of your code. For example in the first statement you can see some registers (v0 and v0) is loaded with the same value twice.
I think your compiler is not doing a good job at optimizing the compiled code.
MIPS 基本上是 RISC 设计中所有愚蠢之处的体现。如今,x86(和 x86_64)已经吸收了 RISC 中几乎所有有价值的想法,并且 ARM 已经发展得比传统 RISC 更高效,同时仍然坚持保留小型系统指令集的 RISC 概念。
为了回答这个问题,我想说,你选择 MIPS 是疯狂的,或者更重要的是,在没有先了解一点 MIPS ISA 以及为什么它如此糟糕以及你需要忍受多少低效率的情况下就选择它你想使用它。在大多数情况下,我会选择 ARM 来实现低功耗/嵌入式系统,或者如果您能承受更多的功耗,那么选择 Intel Atom 会更好。
编辑:实际上,您可能会疯狂的第二个原因...从评论来看,您似乎正在使用 16 位整数。你不应该在 C 中使用小于
int
类型,除非在数组或将被大量分配的结构中(无论是在数组中还是在其他方式中,例如链接列表/树/ ETC。)。使用小类型永远不会带来任何好处,除了节省空间(除非你有大量这种类型的值,否则这是无关紧要的),并且几乎肯定比使用“普通”类型效率低。就 MIPS 而言,差异是极端的。切换到int
并查看您的问题是否消失。MIPS is basically the embodiment of everything that was stupid about RISC design. These days x86 (and x86_64) have absorbed pretty much all the worthwhile ideas out of RISC, and ARM has evolved to be much more efficient than traditional RISC while still staying true to the RISC concept of keeping a small, systematic instruction set.
To answer the question, I'd say you're crazy for choosing MIPS, or perhaps more importantly, for choosing it without first learning a bit about the MIPS ISA and why it's so bad and how much inefficiency you need to put up with if you want to use it. I'd choose ARM for low-power/embedded systems in most situations, or better yet Intel Atom if you can afford a bit more power consumption.
Edit: Actually, a second reason you may be crazy... From the comments, it seems you're using 16-bit integers. You should never use smaller-than-
int
types in C except in arrays or in a structure that will be allocated in large numbers (either in an array or some other way such as a linked list/tree/etc.). Using small types will never give any benefit except for saving space (which is irrelevant until you have a large number of values of such type) and is almost surely less efficient than using "normal" types. In the case of MIPS, the difference is extreme. Switch toint
and see if your problem goes away.我唯一能想到的是,编译器可能会注入额外的无意义指令,以使 CPU 的速度与慢得多的数据总线速度相匹配。即使这样的解释也还不够充分,因为存储/加载指令同样具有冗余。
由于编译器是可疑的,所以不要忘记,将精力集中在编译器上可能会让您看不到某种隧道视野。也许工具链的其他部分也潜藏着错误。
你从哪里得到编译器?我发现一些“简单”的来源经常会提供一些非常糟糕的工具。我的嵌入式开发朋友通常会编译自己的工具链,有时会得到更好的结果。
The only thing that I can think of is perhaps, perhaps, the compiler might be injecting extra nonsense instructions to mate up the speed of the CPU with a much slower data bus speed. Even that explanation isn't quite sufficient, as the store / load instructions similarly have redundancy.
As the compiler is suspect, don't forget that focusing efforts into the compiler can blind you to a type of tunnel vision. Perhaps errors are latent in other parts of the tool chain too.
Where did you get the compiler? I find that some of the "easy" sources often ship some pretty horrible tools. Embedded development friends of mine typically compile their own tool chain with sometimes much better results.
我尝试使用 CodeSourcery MIPS GCC 4.4-303 和 -O4 编译以下代码。我用 uint32_t 和 uint16_t 进行了尝试:
这是使用 uint32_t 整数的反汇编:
这是使用 uint16_t 整数的反汇编:
正如您所看到的,每个 C 语句都映射为两到三个指令。使用 16 位整数使函数仅长一条指令。
I tried compiling the following code with CodeSourcery MIPS GCC 4.4-303 with -O4. I tried it with uint32_t and uint16_t:
Here is the disassembly with uint32_t integers:
Here is the disassembly with uint16_t integers:
As you can see each C statement maps into two to three instructions. Using 16 bit integers makes the function only one instruction longer.
你开启编译器优化了吗?未优化的代码有很多冗余。
Have you turned on compiler optimizations? Unoptimzied code has much redundancy in it.