我试图把对已经或尚未加载到寄存器中的内在类型的内存访问的想法缠住。
假定一些SIMD函数接受对float数组的引用。例如,
void do_something(std::array<float, 4>& arr);
void do_something_else(std::array<float, 4>& arr);
每个函数首先将数据加载到寄存器中,执行其操作,然后将结果存储回数组中。假设以下代码段:
std::array<float, 4> my_arr{0.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
,以便不将其存储在堆中?
答案 0 :(得分:2)
如果编译器与调用分开编译函数,则无法优化存储和加载。当功能在一个.cpp文件中,在另一个.cpp文件中的调用以及链接时间优化未启用时,肯定是这种情况。
但是,如果是编译器
同时(或在链接时间优化期间)查看函数定义及其调用,
决定内联函数调用,并且
决定融合循环,
那么它可能会删除不必要的存储和负载。
但是请注意,这三点都不是无关紧要的。程序员仅控制第一个点,其他两个点则由编译器决定是否为100%。因此,您通常必须假设不会发生此类优化。如果您的函数实际上是模板(也保证满足第1点),则内联的可能性会有所提高,但是编译器是否真正融合了循环是您无法控制的。
关于包含SIMD类型的结构:将SIMD类型驻留在堆上是完全合法的。将它分配到堆栈上绝对没有区别。
但是,您不能仅将std::array<float, 4>
用__m128
别名,这会违反严格的别名规则。从std::array<float, 4>
到__m128
的重新解释只能通过复制安全地进行(重新解释为char*
,复制,重新解释为__m128
),否则允许编译器混淆访问到数组和SIMD类型。