是否为空函数将参数加载到缓存中?
Are arguments loaded into the cache for empty functions?
我知道 C++ 编译器会优化空(静态)函数。
基于这些知识,我编写了一段代码,只要我定义了某个标识符(使用编译器的 -D
选项),它就会被优化掉。
考虑以下虚拟示例:
#include <iostream>
#ifdef NO_INC
struct T {
static inline void inc(int& v, int i) {}
};
#else
struct T {
static inline void inc(int& v, int i) {
v += i;
}
};
#endif
int main(int argc, char* argv[]) {
int a = 42;
for (int i = 0; i < argc; ++i)
T::inc(a, i);
std::cout << a;
}
所需的行为如下:
每当定义 NO_INC
标识符时(编译时使用 -DNO_INC
),所有对 T::inc(...)
的调用都应该被优化掉(由于空函数体)。否则,对 T::inc(...)
的调用应该触发某个给定值 i
.
的增量
关于这个我有两个问题:
- 当我指定
-DNO_INC
选项时,调用 T::inc(...)
不会对性能产生负面影响,因为对空函数的调用已优化,我的假设是否正确?
- 我想知道当调用
T::inc(a, i)
时变量(a
和i
)是否仍然加载到缓存中(假设它们还没有)虽然函数体是空.
感谢任何建议!
因为使用了 inline
关键字,您可以安全地假设 1。使用这些函数不会对性能产生负面影响。
运行 你的代码通过
g++ -c -Os -g
objdump -S
确认这一点;摘录:
int main(int argc, char* argv[]) {
T t;
int a = 42;
1020: b8 2a 00 00 00 mov [=10=]x2a,%eax
for (int i = 0; i < argc; ++i)
1025: 31 d2 xor %edx,%edx
1027: 39 fa cmp %edi,%edx
1029: 7d 06 jge 1031 <main+0x11>
v += i;
102b: 01 d0 add %edx,%eax
for (int i = 0; i < argc; ++i)
102d: ff c2 inc %edx
102f: eb f6 jmp 1027 <main+0x7>
t.inc(a, i);
return a;
}
1031: c3 retq
(我用 return 替换了 cout 以提高可读性)
Compiler Explorer is an very useful tool to look at the assembly of your generated program, because there is no other way to figure out if the compiler optimized something or not for sure. Demo.
随着实际递增,您的 main
看起来像:
main: # @main
push rax
test edi, edi
jle .LBB0_1
lea eax, [rdi - 1]
lea ecx, [rdi - 2]
imul rcx, rax
shr rcx
lea esi, [rcx + rdi]
add esi, 41
jmp .LBB0_3
.LBB0_1:
mov esi, 42
.LBB0_3:
mov edi, offset std::cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
xor eax, eax
pop rcx
ret
如您所见,编译器完全内联了对 T::inc
的调用并直接进行递增。
对于空 T::inc
你得到:
main: # @main
push rax
mov edi, offset std::cout
mov esi, 42
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
xor eax, eax
pop rcx
ret
编译器优化了整个循环!
Is my assumption correct that calls to t.inc(...)
do not affect the performance negatively when I specify the -DNO_INC
option because the call to the empty function is optimized?
是的。
If my assumption holds, does it also hold for more complex function bodies (in the #else
branch)?
不,对于 "complex" 的一些定义。编译器使用启发式方法来确定是否值得内联一个函数,并以此为基础做出决定。
I wonder if the variables (a
and i
) are still loaded into the cache when t.inc(a, i)
is called (assuming they are not there yet) although the function body is empty.
不,如上所述,循环甚至不存在。
Is my assumption correct that calls to t.inc(...) do not affect the performance negatively when I specify the -DNO_INC option because the call to the empty function is optimized? If my assumption holds, does it also hold for more complex function bodies (in the #else branch)?
你是对的。我在 compiler explorer 中修改了您的示例(即删除了使程序集混乱的 cout),以使发生的事情更加明显。
编译器优化了所有的东西
main: # @main
movl , %eax
retq
只有 42 在 eax 和 returned 中被引入。
然而,对于更复杂的情况,需要更多指令来计算 return 值。 See here
main: # @main
testl %edi, %edi
jle .LBB0_1
leal -1(%rdi), %eax
leal -2(%rdi), %ecx
imulq %rax, %rcx
shrq %rcx
leal (%rcx,%rdi), %eax
addl , %eax
retq
.LBB0_1:
movl , %eax
retq
I wonder if the variables (a and i) are still loaded into the cache when t.inc(a, i) is called (assuming they are not there yet) although the function body is empty.
它们仅在编译器无法推断出它们未被使用时才加载。请参阅编译器资源管理器的第二个示例。
顺便说一下:您不需要创建 T 的实例(即 T t;
)来调用 [=33= 中的静态函数].这是在破坏目的。称它为 T::inc(...)
而不是 t.inc(...)
.
我知道 C++ 编译器会优化空(静态)函数。
基于这些知识,我编写了一段代码,只要我定义了某个标识符(使用编译器的 -D
选项),它就会被优化掉。
考虑以下虚拟示例:
#include <iostream>
#ifdef NO_INC
struct T {
static inline void inc(int& v, int i) {}
};
#else
struct T {
static inline void inc(int& v, int i) {
v += i;
}
};
#endif
int main(int argc, char* argv[]) {
int a = 42;
for (int i = 0; i < argc; ++i)
T::inc(a, i);
std::cout << a;
}
所需的行为如下:
每当定义 NO_INC
标识符时(编译时使用 -DNO_INC
),所有对 T::inc(...)
的调用都应该被优化掉(由于空函数体)。否则,对 T::inc(...)
的调用应该触发某个给定值 i
.
关于这个我有两个问题:
- 当我指定
-DNO_INC
选项时,调用T::inc(...)
不会对性能产生负面影响,因为对空函数的调用已优化,我的假设是否正确? - 我想知道当调用
T::inc(a, i)
时变量(a
和i
)是否仍然加载到缓存中(假设它们还没有)虽然函数体是空.
感谢任何建议!
因为使用了 inline
关键字,您可以安全地假设 1。使用这些函数不会对性能产生负面影响。
运行 你的代码通过
g++ -c -Os -g
objdump -S
确认这一点;摘录:
int main(int argc, char* argv[]) {
T t;
int a = 42;
1020: b8 2a 00 00 00 mov [=10=]x2a,%eax
for (int i = 0; i < argc; ++i)
1025: 31 d2 xor %edx,%edx
1027: 39 fa cmp %edi,%edx
1029: 7d 06 jge 1031 <main+0x11>
v += i;
102b: 01 d0 add %edx,%eax
for (int i = 0; i < argc; ++i)
102d: ff c2 inc %edx
102f: eb f6 jmp 1027 <main+0x7>
t.inc(a, i);
return a;
}
1031: c3 retq
(我用 return 替换了 cout 以提高可读性)
Compiler Explorer is an very useful tool to look at the assembly of your generated program, because there is no other way to figure out if the compiler optimized something or not for sure. Demo.
随着实际递增,您的 main
看起来像:
main: # @main
push rax
test edi, edi
jle .LBB0_1
lea eax, [rdi - 1]
lea ecx, [rdi - 2]
imul rcx, rax
shr rcx
lea esi, [rcx + rdi]
add esi, 41
jmp .LBB0_3
.LBB0_1:
mov esi, 42
.LBB0_3:
mov edi, offset std::cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
xor eax, eax
pop rcx
ret
如您所见,编译器完全内联了对 T::inc
的调用并直接进行递增。
对于空 T::inc
你得到:
main: # @main
push rax
mov edi, offset std::cout
mov esi, 42
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
xor eax, eax
pop rcx
ret
编译器优化了整个循环!
Is my assumption correct that calls to
t.inc(...)
do not affect the performance negatively when I specify the-DNO_INC
option because the call to the empty function is optimized?
是的。
If my assumption holds, does it also hold for more complex function bodies (in the
#else
branch)?
不,对于 "complex" 的一些定义。编译器使用启发式方法来确定是否值得内联一个函数,并以此为基础做出决定。
I wonder if the variables (
a
andi
) are still loaded into the cache whent.inc(a, i)
is called (assuming they are not there yet) although the function body is empty.
不,如上所述,循环甚至不存在。
Is my assumption correct that calls to t.inc(...) do not affect the performance negatively when I specify the -DNO_INC option because the call to the empty function is optimized? If my assumption holds, does it also hold for more complex function bodies (in the #else branch)?
你是对的。我在 compiler explorer 中修改了您的示例(即删除了使程序集混乱的 cout),以使发生的事情更加明显。
编译器优化了所有的东西
main: # @main
movl , %eax
retq
只有 42 在 eax 和 returned 中被引入。
然而,对于更复杂的情况,需要更多指令来计算 return 值。 See here
main: # @main
testl %edi, %edi
jle .LBB0_1
leal -1(%rdi), %eax
leal -2(%rdi), %ecx
imulq %rax, %rcx
shrq %rcx
leal (%rcx,%rdi), %eax
addl , %eax
retq
.LBB0_1:
movl , %eax
retq
I wonder if the variables (a and i) are still loaded into the cache when t.inc(a, i) is called (assuming they are not there yet) although the function body is empty.
它们仅在编译器无法推断出它们未被使用时才加载。请参阅编译器资源管理器的第二个示例。
顺便说一下:您不需要创建 T 的实例(即 T t;
)来调用 [=33= 中的静态函数].这是在破坏目的。称它为 T::inc(...)
而不是 t.inc(...)
.