【发布时间】:2020-10-09 21:33:50
【问题描述】:
问题描述
我正在尝试使用 GCC 矢量扩展编写 NEON 优化代码。 因此我定义了一个联合结构,如
#include <arm_neon.h>
typedef int32_t v4si __attribute__ ((vector_size (16)));
typedef float32_t v4sf __attribute__ ((vector_size (16)));
union v128
{
int32x4_t m128i;
float32x4_t m128f;
v4si si;
v4sf sf;
};
v128 x,y;
编写像x.sf *= y.sf 这样的代码经常会由于总线错误而导致崩溃。
使用 gdb 进行检查总是会发现,在所有这些崩溃情况下,至少有一个变量仅与 8 个字节对齐,而不是与 16 个字节对齐。
但是,当我使用优化选项“-O2”进行编译时,这些崩溃情况的发生率要低得多。
是否有任何 gcc/g++ 编译器选项始终保证 GCC 向量的 16 位对齐? 由于“-O2”支持一整套优化,有谁知道哪个特定优化导致总线错误频率低得多?
我正在树莓派 3 上编译和测试我的代码。在那里我还使用了 g++ 参数:
-march=armv8-a+crc -mtune=cortex-a53 -mfloat-abi=hard -mfpu=neon-fp-armv8 -funsafe-math-optimizations
最小代码示例
simd_numeric_test.cpp:
#include <random>
#include <limits>
#include <cfloat>
#include <type_traits>
#include <cassert>
#include <arm_neon.h>
typedef int32_t v4si __attribute__ ((vector_size (16), aligned(16)));
typedef float32_t v4sf __attribute__ ((vector_size (16), aligned(16)));
typedef int32x4_t m128i_t; // __attribute__ ((aligned(16)));
typedef float32x4_t m128f_t; // __attribute__ ((aligned(16)));
union v128
{
m128i_t m128i;
m128f_t m128f;
v4si si;
v4sf sf;
};
static_assert( sizeof(v128) == 16 );
struct vf32_t
{
v128 val;
static constexpr size_t num_items() { return (sizeof(val) / sizeof(float32_t)); }
inline
const vf32_t& operator+=( const vf32_t& other ) { val.sf += other.val.sf; return *this; }
inline
const float32_t* cbegin() const { return &(val.sf[0]); }
inline
const float32_t* cend() const { return &(val.sf[num_items()]); }
};
static_assert( sizeof(vf32_t) == 16 );
class CSimdNumericTest
{
protected:
const size_t m_numElemInSimd = vf32_t::num_items();
const int m_randomSeed_u = 69;
const int m_repeats_u = 10000;
const float32_t m_maxFloatVal_f32;// = 43.f;
std::default_random_engine m_rand;
std::uniform_real_distribution<float32_t> m_floatSampler;
void test_binary_assign_vv_operation( const vf32_t a_v32, const vf32_t b_v32 ) const;
public:
void float32_base_op_test();
CSimdNumericTest()
: m_maxFloatVal_f32( std::ceil( std::pow( std::numeric_limits<float32_t>::max(),
1.f / static_cast<float32_t>( m_numElemInSimd ) ) ) )
, m_rand( m_randomSeed_u )
, m_floatSampler( -m_maxFloatVal_f32, m_maxFloatVal_f32 )
{}
};
void CSimdNumericTest::test_binary_assign_vv_operation( const vf32_t a_v32, const vf32_t b_v32 ) const
{
vf32_t x = a_v32;
x += b_v32;
auto aIter = a_v32.cbegin();
auto bIter = b_v32.cbegin();
for ( auto xIter = x.cbegin(); xIter != x.cend();
++xIter, ++aIter, ++bIter ) {
float32_t rx = *aIter;
rx += *bIter;
assert( rx == *xIter );
}
}
void CSimdNumericTest::float32_base_op_test()
{
vf32_t a_v32, b_v32;
const float32_t l_minFloat_f32 = 1. / m_maxFloatVal_f32;
for ( int n = 0; n < m_repeats_u; ++n )
{
for ( size_t i = 0; i < vf32_t::num_items(); ++i )
{
a_v32.val.sf[i] = m_floatSampler( m_rand );
b_v32.val.sf[i] = m_floatSampler( m_rand );
}
test_binary_assign_vv_operation( a_v32, b_v32 );
}
}
int main(int argc, char **argv) {
CSimdNumericTest test;
test.float32_base_op_test();
return 0;
}
我编译了所有东西
arm-linux-gnueabihf-g++ -c -o simd_numeric_test_neon.o simd_numeric_test.cpp -pipe -fsigned-char -pthread -ftree-vectorize -Wall -Wextra -Wdate-time -Wformat -Werror=format-security -ggdb3 -O0 -march=armv8-a+crc -mtune=cortex-a53 -mfloat-abi=hard -mfpu=neon-fp-armv8 -funsafe-math-optimizations -Wno-psabi
arm-linux-gnueabihf-g++ -pthread -lpthread -lstdc++ -o simd_test_neon simd_numeric_test_neon.o
编译结果:
- simd_numeric_test_neon.o目标文件
- simd_test_neon 可执行文件
在赋值语句处出现崩溃:
x += b_v32;
进一步调查结果
现在我注意到所有的崩溃都是在使用传值函数参数时发生的。虽然原始向量变量仍然正确对齐,但复制的函数参数不再存在。因此,当我将 pass-by-value 替换为 pass-by-reference 时,可执行文件可以正常工作:
void test_binary_assign_vv_operation( const vf32_t a_v32, const vf32_t b_v32 )
到
void test_binary_assign_vv_operation( const vf32_t& a_v32, const vf32_t& b_v32 )
我在所有的总线错误崩溃案例中都观察到了这种模式。
但是,这种观察并没有真正带来解决方案。有很多函数(例如在 C++STL 中)使用 pass-by-value。
是否有任何 g++ 参数也可以为矢量化函数参数实现正确的内存对齐? 这可能是一个 g++ 错误吗?
在此先感谢
【问题讨论】:
-
评论不用于扩展讨论;这个对话是moved to chat。
标签: c++ gcc simd memory-alignment neon