C 负载并存储优化和堆对象

C++ load and store optimizations and heap objects

本文关键字:对象 优化 存储 负载      更新时间:2023-10-16

我试图将头缠绕在内存访问固有类型的内存中,这些类型已加载到寄存器中。

假设一些simd函数接受引用对浮动数组的引用。例如,

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,因此它们不存储在堆中?

如果编译器与呼叫分开编译功能,则无法优化商店和加载。当函数在一个.cpp文件中,在另一个.cpp文件中调用和链接时间优化时,绝对是这种情况。

但是,如果编译器

  1. 同时看到功能定义及其呼叫(或在链接时间优化期间(,

  2. 决定内联函数调用和

  3. 决定融合循环,

然后它可能会删除不必要的商店和负载。

注意,这三个点都不是微不足道的。程序员仅控制第一点,另外两个是由编译器酌情决定的100%。因此,您通常必须假设这种优化不会发生。如果您的功能实际上是模板(也可以确保满足点1(,那么将上升升起的机会有些机会,但是编译器是否真的将循环融合在您的控制范围内。


关于包含SIMD类型的结构:SIMD类型驻留在堆上是完全合法的。与在堆栈上分配的绝对没有区别。

但是,您不仅可以用__m128别名std::array<float, 4>,这会违反严格的别名规则。将std::array<float, 4>重新解释为__m128只能使用副本安全地进行(重新解释为char*,副本,重新解释为__m128(,否则允许您的编译器混合使用该数组和SIMD类型的访问。