看来,带有优化的G ++不能从翻译单元静态变量内联一个简单的函数调用。下面的代码和编译输出示例。请注意,函数 can_inline_local 通过使用 DerivedType 的本地实例完美地内联调用,但 cant_inline_static 是一个相当长的调用。
在你打电话给警察进行预成熟优化之前,我想保护自己说多态继承会非常清楚地描述我的内核级串行驱动程序中断服务程序。如果G ++只能为我内联虚拟调用(使用我认为在编译时应该知道的内容),那么我就会有清晰的+可测试代码来编译C性能。
我使用arm-none-eabi-g ++ -v gcc版本4.9.3 20150529(预发布)(15:4.9.3 + svn227297-1)
arm-none-eabi-g ++ -std = gnu ++ 11 -O3 -c -o inline.o inline.cpp&& arm-none-eabi-objdump inline.o -S> inline.dump
inline.cpp:
extern "C"{
int * const MEMORY_MAPPED_IO_A = (int*)0x40001000;
int * const MEMORY_MAPPED_IO_B = (int*)0x40002000;
}
namespace{
/** Anon namespace should make these
typedefs static to this translation unit */
struct BaseType{
void* data;
virtual void VirtualMethod(int parameter){
*MEMORY_MAPPED_IO_A = parameter;
}
void VirtualCaller(int parameter){
this->VirtualMethod(parameter);
}
};
struct DerivedType : BaseType{
void VirtualMethod(int parameter) final {
*MEMORY_MAPPED_IO_B = parameter;
}
};
/** static keyword here may be superfluous */
static BaseType basetype;
static DerivedType derivedtype;
extern "C"{
void cant_inline_static(int parameter){
derivedtype.VirtualCaller(1);
}
void can_inline_local(int parameter){
DerivedType localobj;
localobj.VirtualCaller(1);
}
}
}
inline.dump
inline.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <_ZN12_GLOBAL__N_18BaseType13VirtualMethodEi>:
0: e59f3004 ldr r3, [pc, #4] ; c <_ZN12_GLOBAL__N_18BaseType13VirtualMethodEi+0xc>
4: e5831000 str r1, [r3]
8: e12fff1e bx lr
c: 40001000 .word 0x40001000
00000010 <_ZN12_GLOBAL__N_111DerivedType13VirtualMethodEi>:
10: e59f3004 ldr r3, [pc, #4] ; 1c <_ZN12_GLOBAL__N_111DerivedType13VirtualMethodEi+0xc>
14: e5831000 str r1, [r3]
18: e12fff1e bx lr
1c: 40002000 .word 0x40002000
00000020 <cant_inline_static>:
20: e59f0028 ldr r0, [pc, #40] ; 50 <cant_inline_static+0x30>
24: e5903000 ldr r3, [r0]
28: e59f2024 ldr r2, [pc, #36] ; 54 <cant_inline_static+0x34>
2c: e5933000 ldr r3, [r3]
30: e1530002 cmp r3, r2
34: 1a000003 bne 48 <cant_inline_static+0x28>
38: e3a02001 mov r2, #1
3c: e59f3014 ldr r3, [pc, #20] ; 58 <cant_inline_static+0x38>
40: e5832000 str r2, [r3]
44: e12fff1e bx lr
48: e3a01001 mov r1, #1
4c: e12fff13 bx r3
...
58: 40002000 .word 0x40002000
0000005c <can_inline_local>:
5c: e3a02001 mov r2, #1
60: e59f3004 ldr r3, [pc, #4] ; 6c <can_inline_local+0x10>
64: e5832000 str r2, [r3]
68: e12fff1e bx lr
6c: 40002000 .word 0x40002000
Disassembly of section .text.startup:
00000000 <_GLOBAL__sub_I_cant_inline_static>:
0: e59f3014 ldr r3, [pc, #20] ; 1c <_GLOBAL__sub_I_cant_inline_static+0x1c>
4: e59f2014 ldr r2, [pc, #20] ; 20 <_GLOBAL__sub_I_cant_inline_static+0x20>
8: e2831008 add r1, r3, #8
c: e2833018 add r3, r3, #24
10: e5821008 str r1, [r2, #8]
14: e5823000 str r3, [r2]
18: e12fff1e bx lr
...
更新
只需在BaseType中注释掉 void * data; 字段,就可以积极优化简单的虚拟调用。下面是objdump。如果类具有可能未初始化的数据成员,则看起来G ++可能不信任使用静态实例方法。有没有什么方法可以指定一个类是它看起来是什么,不需要构造或初始化?如果编译器假设这样的事情会因为一些过度设计/深奥的功能而导致C ++全部无效,我还不知道?我觉得我正在抓稻草,但值得再多问一下。
inline.o: file format elf32-littlearm
Disassembly of section .text.cant_inline_static:
00000000 <cant_inline_static>:
0: 2201 movs r2, #1
2: 4b01 ldr r3, [pc, #4] ; (8 <cant_inline_static+0x8>)
4: 601a str r2, [r3, #0]
6: 4770 bx lr
8: 40002000 .word 0x40002000
Disassembly of section .text.can_inline_local:
00000000 <can_inline_local>:
0: 2201 movs r2, #1
2: 4b01 ldr r3, [pc, #4] ; (8 <cant_inline_static+0x8>)
4: 601a str r2, [r3, #0]
6: 4770 bx lr
8: 40002000 .word 0x40002000
最终更新
我已经制定了在 cant_inline_static 开头发生的簿记代码。它只是取静态实例 derivedtype ,取消引用其vtable,查找 VirtualMethod 条目,然后将其与DerivedType :: VirtualMethod的.text地址进行比较。如果匹配:运行内联过程。如果它们不同:调用实例的vtable方法。
看来G ++期望虚拟调用最终是DerivedType :: VirtualMethod,但是它担心静态DerivedType derivedtype 变量的vtable可能指向不同的方法。如果初始化DerivedType的所有成员(和继承成员)变量然后G ++,那么它将获得完全内联&#39; VirtualMethod&#39;所需的信心。正如@rici解释的那样,它很可能与“衍生类型”有关。实例被接受.data(显式初始化)而不是.bss。
有趣的一点:如果 derivedtype AND basetype 实例同时调用 VirtualCaller ,那么无论成员初始化如何,G ++都会添加簿记代码。
此时我通过发现一些家伙如何编写G ++优化器的这一部分来扮演考古学家。这是一个有趣的旅程。我在这里得到了一些非常好的帮助。我在这个过程中学到了很多关于虚拟方法性能的知识。
答案 0 :(得分:3)
我对ARM汇编编程几乎一无所知,所以我有可能彻底搞砸自己:)但它看起来确实是内联的。在这两个功能中,您都可以找到:
e3a02001 mov r2, #1 ; put 1 to register r2
e59f3014 ldr r3, [pc, #20] ; put address 0x40002000 to r3
e5832000 str r2, [r3] ; store value of r1 to adress in r3
在这两种情况下都没有调用方法(我希望bl
指令)。
在静态变量的情况下,显然有一些我不理解的簿记代码,但它似乎与内联无关。如果我不得不猜测,我会说它是从某个表加载静态对象的地址来检查它是否被实例化,而在另一种情况下,本地对象似乎被完全优化掉了,从而导致代码更短。