gpt4 book ai didi

c++ - 为什么内联函数比函数指针慢?

转载 作者:IT老高 更新时间:2023-10-28 23:01:55 30 4
gpt4 key购买 nike

考虑以下代码:

typedef void (*Fn)();

volatile long sum = 0;

inline void accu() {
sum+=4;
}

static const Fn map[4] = {&accu, &accu, &accu, &accu};

int main(int argc, char** argv) {
static const long N = 10000000L;
if (argc == 1)
{
for (long i = 0; i < N; i++)
{
accu();
accu();
accu();
accu();
}
}
else
{
for (long i = 0; i < N; i++)
{
for (int j = 0; j < 4; j++)
(*map[j])();
}
}
}

当我编译它时:

g++ -O3 test.cpp

我希望第一个分支运行得更快,因为编译器可以将函数调用内联到 accu。第二个分支不能内联,因为 accu 是通过存储在数组中的函数指针调用的。

但结果让我吃惊:

time ./a.out 

real 0m0.108s
user 0m0.104s
sys 0m0.000s

time ./a.out 1

real 0m0.095s
user 0m0.088s
sys 0m0.004s

我不明白为什么,所以我做了一个objdump:

objdump -DStTrR a.out > a.s

而且反汇编似乎并不能解释我得到的性能结果:

8048300 <main>:
8048300: 55 push %ebp
8048301: 89 e5 mov %esp,%ebp
8048303: 53 push %ebx
8048304: bb 80 96 98 00 mov $0x989680,%ebx
8048309: 83 e4 f0 and $0xfffffff0,%esp
804830c: 83 7d 08 01 cmpl $0x1,0x8(%ebp)
8048310: 74 27 je 8048339 <main+0x39>
8048312: 8d b6 00 00 00 00 lea 0x0(%esi),%esi
8048318: e8 23 01 00 00 call 8048440 <_Z4accuv>
804831d: e8 1e 01 00 00 call 8048440 <_Z4accuv>
8048322: e8 19 01 00 00 call 8048440 <_Z4accuv>
8048327: e8 14 01 00 00 call 8048440 <_Z4accuv>
804832c: 83 eb 01 sub $0x1,%ebx
804832f: 90 nop
8048330: 75 e6 jne 8048318 <main+0x18>
8048332: 31 c0 xor %eax,%eax
8048334: 8b 5d fc mov -0x4(%ebp),%ebx
8048337: c9 leave
8048338: c3 ret
8048339: b8 80 96 98 00 mov $0x989680,%eax
804833e: 66 90 xchg %ax,%ax
8048340: 8b 15 18 a0 04 08 mov 0x804a018,%edx
8048346: 83 c2 04 add $0x4,%edx
8048349: 89 15 18 a0 04 08 mov %edx,0x804a018
804834f: 8b 15 18 a0 04 08 mov 0x804a018,%edx
8048355: 83 c2 04 add $0x4,%edx
8048358: 89 15 18 a0 04 08 mov %edx,0x804a018
804835e: 8b 15 18 a0 04 08 mov 0x804a018,%edx
8048364: 83 c2 04 add $0x4,%edx
8048367: 89 15 18 a0 04 08 mov %edx,0x804a018
804836d: 8b 15 18 a0 04 08 mov 0x804a018,%edx
8048373: 83 c2 04 add $0x4,%edx
8048376: 83 e8 01 sub $0x1,%eax
8048379: 89 15 18 a0 04 08 mov %edx,0x804a018
804837f: 75 bf jne 8048340 <main+0x40>
8048381: eb af jmp 8048332 <main+0x32>
8048383: 90 nop
...
8048440 <_Z4accuv>:
8048440: a1 18 a0 04 08 mov 0x804a018,%eax
8048445: 83 c0 04 add $0x4,%eax
8048448: a3 18 a0 04 08 mov %eax,0x804a018
804844d: c3 ret
804844e: 90 nop
804844f: 90 nop

看来直接调用分支肯定比函数指针分支做得少。但是为什么函数指针分支比直接调用跑得快呢?

请注意,我只使用“时间”来测量时间。我使用clock_gettime 进行了测量,得到了类似的结果。

最佳答案

第二个分支不能内联并不完全正确。实际上,存储在数组中的所有函数指针在编译时都可以看到。所以编译器可以用直接调用代替间接函数调用(它确实这样做了)。从理论上讲,它可以更进一步并内联它们(在这种情况下,我们有两个相同的分支)。但是这个特殊的编译器不够聪明,不能这样做。

因此,第一个分支被优化得“更好”。但有一个异常(exception)。不允许编译器优化 volatile 变量 sum。从反汇编代码中可以看出,这会生成存储指令,然后是加载指令(取决于这些存储指令):

mov    %edx,0x804a018
mov 0x804a018,%edx

英特尔的软件优化手册(第 3.6.5.2 节)不建议这样安排指令:

... if a load is scheduled too soon after the store it depends on or if the generation of the data to be stored is delayed, there can be a significant penalty.

第二个分支避免了这个问题,因为存储和加载之间有额外的调用/返回指令。所以性能更好。

如果我们在中间添加一些(不是非常昂贵的)计算,则可以对第一个分支进行类似的改进:

long x1 = 0;
for (long i = 0; i < N; i++)
{
x1 ^= i<<8;
accu();
x1 ^= i<<1;
accu();
x1 ^= i<<2;
accu();
x1 ^= i<<4;
accu();
}
sum += x1;

关于c++ - 为什么内联函数比函数指针慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16493290/

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