空函数的参数是否加载到缓存中?
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(...)
的调用不会对性能产生负面影响,因为对空函数的调用已优化,我的假设是否正确? - 我想知道变量(
a
和i
(在调用T::inc(a, i)
时是否仍然加载到缓存中(假设它们还没有(,尽管函数体是空的。
感谢您的任何建议!
编译器资源管理器是一个非常有用的工具,用于查看生成的程序的程序集,因为没有其他方法可以确定编译器是否优化了某些内容。演示。
通过实际递增,您的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
编译器优化了整个循环!
当我指定
-DNO_INC
选项时,对t.inc(...)
的调用不会对性能产生负面影响,因为对空函数的调用已优化,我的假设是否正确?
是的。
如果我的假设成立,它是否也适用于更复杂的函数体(在
#else
分支中(?
不,对于"复杂"的某些定义。编译器使用启发式方法来确定内联函数是否值得,并以此为基础做出决定,而不是基于其他任何内容。
我想知道当调用
t.inc(a, i)
时变量(a
和i
(是否仍然加载到缓存中(假设它们还没有(,尽管函数体是空的。
不,如上所述,循环甚至不存在。
当我指定 -DNO_INC 选项时,对 t.inc(...( 的调用不会对性能产生负面影响,因为对空函数的调用得到了优化,我的假设是否正确?如果我的假设成立,它是否也适用于更复杂的函数体(在 #else 分支中(?
你是对的。我已经在编译器资源管理器中修改了您的示例(即删除了使程序集混乱的 cout(,以使发生的事情更加明显。
编译器优化了所有内容
main: # @main
movl $42, %eax
retq
只有 42 个在 eax 中被引线并返回。
但是,对于更复杂的情况,需要更多的指令来计算返回值。看这里
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 $41, %eax
retq
.LBB0_1:
movl $42, %eax
retq
我想知道当调用 t.inc(a, i( 时变量(a 和 i(是否仍然加载到缓存中(假设它们还没有(,尽管函数体是空的。
仅当编译器无法推断它们未使用时,才会加载它们。请参阅编译器资源管理器的第二个示例。
顺便说一句:你不需要制作 T 的实例(即T t;
(,以便调用类中的静态函数。这是违背目的。称它为比t.inc(...)
T::inc(...)
拉赫特.
因为使用了inline
keword,所以你可以安全地假设 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 $0x2a,%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(
- std::原子加载和存储都需要吗
- 如何加载(或映射)文件部分的最大大小,但适合在Windows上的RAM
- C++ 雷神库 - 使用资源加载器类时出现问题(不命名类型)
- 为什么加载SDF会导致Mobilizer创建闭环错误
- C++atioglxx.pdb未加载错误glBufferData OpenGL
- 如何使用tinyxml2从XML加载父实体和子实体
- 如何在C++中使用pybind11加载一个pickle python列表
- 系统.将数组移交给c#中动态加载的c++DLL时发生AccessViolationException
- 当我尝试加载内核模块时,如何修复C++中的这个 malloc() 错误?
- 树莓上的 Libtorch 无法加载 pt 文件,但在 ubuntu 上工作
- 空函数的参数是否加载到缓存中?
- QWebEngine 5.7.1 离线导航加载缓存
- 点燃缓存加载时间的选择查询
- 一次加载整个缓存行以避免争用其中的多个元素
- 测量 l1/l2 缓存中加载的用于读取(包括预取)的行数
- CPU 缓存是否也从以前的内存位置加载信息
- 阻止或阻塞cpu数据缓存加载
- 在进程启动时加载依赖缓存
- 如何在QWebEngine中加载缓存
- 有多少对象(包含 std::vectors)加载到 L1/L2/L3 缓存中