C++ 加载和存储优化以及堆对象
Posted
技术标签:
【中文标题】C++ 加载和存储优化以及堆对象【英文标题】:C++ load and store optimizations and heap objects 【发布时间】:2019-06-23 20:48:52 【问题描述】:我正试图围绕对已加载或尚未加载到寄存器中的内在类型的内存访问。
假设一些 SIMD 函数接受对浮点数组的引用。例如,
void do_something(std::array<float, 4>& arr);
void do_something_else(std::array<float, 4>& arr);
每个函数首先将数据加载到寄存器中,执行其操作,然后将结果存储回数组中。假设以下 sn-p :
std::array<float, 4> my_arr0.f, 0.f, 0.f, 0.f;
do_something(my_arr);
do_something_else(my_arr);
do_something(my_arr);
c++ 编译器是否优化了函数调用之间不必要的加载和存储?这还重要吗?
我见过将__m128
类型包装在结构中并在构造函数中调用加载的库。当您将这些存储在堆上并尝试在它们上调用内部函数时会发生什么?例如,
struct vec4
vec4(std::array<float, 4>&)
// do load
__m128 data;
;
std::vector<vec4> my_vecs;
// do SIMD work
您是否必须在每次访问时加载/存储数据?或者这些类应该声明一个私有的operator new
,这样它们就不会存储在堆上?
【问题讨论】:
您可以在godbolt 上看到任何C++
代码的生成程序集。您可以选择一些编译器并使用优化标志。
C++ 中的引用是保证的非空指针。所以你真的只传递一个指针。除非它们是内联的,否则编译器不会优化函数的加载和存储。
std::vector
的 SIMD 类型是 a dangerous pattern
@harold 假设向量正确对齐(@987654331@ 或自定义分配器),您是否必须为每个操作加载/存储在寄存器中?例如:scc.ustc.edu.cn/zlsc/tc4600/intel/2017.0.098/compiler_c/common/… +=
是否总是加载到寄存器中?这不是超级慢吗?
@scx 也许,也许不是,但为什么要冒险呢?您可以在需要时加载/存储,并使用__m128
进行主要计算
【参考方案1】:
如果编译器将函数与调用分开编译,则无法优化存储和加载。当函数在一个 .cpp 文件中、调用在另一个 .cpp 文件中并且未启用链接时间优化时,肯定会出现这种情况。
但是,如果编译器
同时(或在链接时间优化期间)查看函数定义及其调用,
决定内联函数调用和
决定融合循环,
那么它可能会删除不必要的存储和加载。
但是请注意,这三点都不是微不足道的。程序员只控制第一点,其他两个100%由编译器自行决定。因此,您通常必须假设不会发生此类优化。如果您的函数实际上是模板(这也保证满足第 1 点),则内联的机会会增加一点,但编译器是否真正融合循环是您无法控制的。
关于包含 SIMD 类型的结构:SIMD 类型驻留在堆上是完全合法的。和在栈上分配完全没有区别。
但是,您不能只为 std::array<float, 4>
加上 __m128
的别名,这将违反严格的别名规则。将std::array<float, 4>
重新解释为__m128
只能通过副本安全地进行(重新解释为char*
,复制,重新解释为__m128
),否则允许编译器混淆对数组和SIMD 类型的访问。
【讨论】:
而且这个很重,还是可以忽略? 内存访问总是潜在的昂贵。成本多少取决于数据的大小和缓存的大小。如果您的数据适合 L1 缓存,则加载指令的开销是几个 CPU 周期。如果您的数据大于最后一级缓存,您将在每次加载和存储时通过内存总线传输它。在这种情况下,每次减少内存总线使用量都是一种胜利。以上是关于C++ 加载和存储优化以及堆对象的主要内容,如果未能解决你的问题,请参考以下文章