C++ 加载和存储优化和堆对象
C++ load and store optimizations and heap objects
我正在努力解决对已加载或未加载到寄存器的内部类型的内存访问。
假设一些 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
每次访问都必须 load/store 数据吗?或者这些 类 是否应该声明一个私有的 operator new
,这样它们就不会存储在堆中?
如果编译器将函数与调用分开编译,则无法优化存储和加载。当函数在一个 .cpp 文件中,调用在另一个 .cpp 文件中,并且未启用 link 时间优化时,情况确实如此。
但是,如果编译器
同时(或在link时间优化期间)看到函数定义及其调用,
决定内联函数调用并且
决定熔断循环,
然后它可能会删除不必要的存储和负载。
但是请注意,这三个点中的 none 是微不足道的。程序员只控制第一点,其他两点由编译器自行决定 100%。因此,您通常必须假设此类优化不会发生。如果您的函数实际上是模板(这也保证满足第 1 点),内联的可能性会增加一点,但是编译器是否真的融合循环是您无法控制的。
关于包含 SIMD 类型的结构:SIMD 类型驻留在堆上是完全合法的。和分配到栈上完全没有区别。
但是,您不能只使用 __m128
作为 std::array<float, 4>
的别名,那样会违反严格的别名规则。将 std::array<float, 4>
重新解释为 __m128
只能通过副本安全地发生(重新解释为 char*
,复制,重新解释为 __m128
),否则允许您的编译器混淆访问到数组和 SIMD 类型。
我正在努力解决对已加载或未加载到寄存器的内部类型的内存访问。
假设一些 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
每次访问都必须 load/store 数据吗?或者这些 类 是否应该声明一个私有的 operator new
,这样它们就不会存储在堆中?
如果编译器将函数与调用分开编译,则无法优化存储和加载。当函数在一个 .cpp 文件中,调用在另一个 .cpp 文件中,并且未启用 link 时间优化时,情况确实如此。
但是,如果编译器
同时(或在link时间优化期间)看到函数定义及其调用,
决定内联函数调用并且
决定熔断循环,
然后它可能会删除不必要的存储和负载。
但是请注意,这三个点中的 none 是微不足道的。程序员只控制第一点,其他两点由编译器自行决定 100%。因此,您通常必须假设此类优化不会发生。如果您的函数实际上是模板(这也保证满足第 1 点),内联的可能性会增加一点,但是编译器是否真的融合循环是您无法控制的。
关于包含 SIMD 类型的结构:SIMD 类型驻留在堆上是完全合法的。和分配到栈上完全没有区别。
但是,您不能只使用 __m128
作为 std::array<float, 4>
的别名,那样会违反严格的别名规则。将 std::array<float, 4>
重新解释为 __m128
只能通过副本安全地发生(重新解释为 char*
,复制,重新解释为 __m128
),否则允许您的编译器混淆访问到数组和 SIMD 类型。