最近在学习指令集相关知识,刚好来练练手。在正式应用前首先看一段代码吧
Eigen::MatrixXd mB;
mB.resize(num, 1);
for (size_t i = 0; i < num; i++)
{
mB(i, 0) = std::pow(input_->points[i].x, 2) + std::pow(input_->points[i].y, 2) + std::pow(input_->points[i].z, 2);
}
这段代码很容易理解,依次将各点进行 x 2 + y 2 + z 2 x^2+y^2+z^2 x2+y2+z2计算,然后赋值给矩阵mB。那么变成AVX指令集加速版本应该是这样的。
#if defined (__AVX__) && defined (__AVX2__)
size_t i = 0;
__m256 avx_vector_x, avx_vector_y, avx_vector_z, sum_square;
__m128 low128, high128;
//每次处理8个
for (; (i + 8) <= num; i += 8)
{
avx_vector_x = _mm256_setr_ps(input_->points[i].x, input_->points[i + 1].x, input_->points[i+2].x, input_->points[i+3].x, input_->points[i+4].x, input_->points[i+5].x, input_->points[i+6].x, input_->points[i+7].x);
avx_vector_y = _mm256_setr_ps(input_->points[i].y, input_->points[i + 1].y, input_->points[i + 2].y, input_->points[i + 3].y, input_->points[i + 4].y, input_->points[i + 5].y, input_->points[i + 6].y, input_->points[i + 7].y);
avx_vector_z = _mm256_setr_ps(input_->points[i].z, input_->points[i + 1].z, input_->points[i + 2].z, input_->points[i + 3].z, input_->points[i + 4].z, input_->points[i + 5].z, input_->points[i + 6].z, input_->points[i + 7].z);
sum_square = _mm256_fmadd_ps(avx_vector_x, avx_vector_x, _mm256_fmadd_ps(avx_vector_y, avx_vector_y, _mm256_mul_ps(avx_vector_z, avx_vector_z)));
low128 =_mm256_extractf128_ps(sum_square, 0);
high128 = _mm256_extractf128_ps(sum_square, 1);
// 提取并存储结果到 Eigen 矩阵 mB
mB(i, 0) = _mm_cvtss_f32(low128);
mB(i + 1, 0) = _mm_cvtss_f32(_mm_shuffle_ps(low128, low128, _MM_SHUFFLE(3, 2, 0, 1)));
mB(i + 2, 0) = _mm_cvtss_f32(_mm_shuffle_ps(low128, low128, _MM_SHUFFLE(3, 1, 0, 2)));
mB(i + 3, 0) = _mm_cvtss_f32(_mm_shuffle_ps(low128, low128, _MM_SHUFFLE(2, 1, 0, 3)));
mB(i + 4, 0) = _mm_cvtss_f32(high128);
mB(i + 5, 0) = _mm_cvtss_f32(_mm_shuffle_ps(high128, high128, _MM_SHUFFLE(3, 2, 0, 1)));
mB(i + 6, 0) = _mm_cvtss_f32(_mm_shuffle_ps(high128, high128, _MM_SHUFFLE(3, 1, 0, 2)));
mB(i + 7, 0) = _mm_cvtss_f32(_mm_shuffle_ps(high128, high128, _MM_SHUFFLE(2, 1, 0, 3)));
}
//剩下的不足8个按照常规处理
for (; i < num; i++)
{
mB(i, 0) = std::pow(input_->points[i].x, 2) + std::pow(input_->points[i].y, 2) + std::pow(input_->points[i].z, 2);
}
#endif
这段代码中出现了好几个关键的指令集方法,第一次看这个我也是懵逼状态。不过查阅了手册后,很快便能理解AVX指令的一些规则。
AVX指令集规则
数据类型
学这个首先得学习常见的数据类型,下面是一些基本数据类型的介绍。
数据类型 | 描述 | 大小 |
---|---|---|
__m128 | 包含4个float类型的128bit向量(16字节) | 4*32bit |
__m128d | 包含2个double类型的128bit向量(16字节) | 2*64bit |
__m128i | 包含数个int类型的128bit向量(16字节) | 128bit |
__m256 | 包含8个float的256bit向量(32字节) | 8*32bit |
__m256d | 包含4个double类型的256bit向量(32字节) | 4*64bit |
__m256i | 包含数个int类型的256bit向量(32字节) | 256bit |
这里主要解释下__m128i和__m256i两个类型:这里的整型包括char,short,int,long,以及unsigned以上类型,所以,例如__m256i就可以由32个char(8bits),或者16个short(16bits),或者8个int(32bits),又或者4个long(64bits)构成。
函数解释
AVX指令集函数命名规则基本上是这样的,
_mm<bit_width>_<name>_<data_type>
以上面的程序中_mm256_setr_ps为例: 256指输出bit_width,setr指按反方向设置,ps指的是输入类型为float。
这里一般来说:
ps:float类型
pd:double类型
epi8/epi16/epi32/epi64:向量里每个数都是整型,一个整型8bit/16bit/32bit/64bit
epu8/epu16/epu32/epu64:向量里每个数都是无符号整型(unsigned),一个整型8bit/16bit/32bit/64bit
m128/m128i/m128d/m256/m256i/m256d:输入值与返回类型不同时会出现 ,例如__m256i_mm256_setr_m128i(__m128ilo,__m128ihi),输入两个__m128i向量 ,把他们拼在一起,变成一个__m256i返回 。另外这种结尾只见于load
si128/si256:不需要知道啥类型,反正128bit/256bit
另外解释下这里面的另外几个函数:
_mm256_mul_ps:两向量相乘,注意不是向量的dot方式乘法,而是各位置相乘
_mm256_fmadd_ps(a,b,c): a*b+c
_mm_cvtss_f32:将低字节的32位转换为float类型输出
_mm_shuffle_ps:将字节重新排列
这里解释一下,比如这句mB(i + 1, 0) = _mm_cvtss_f32(_mm_shuffle_ps(low128, low128, _MM_SHUFFLE(3, 2, 0, 1)));原本low128是按照高位到低位依次是(1.1,2.2,3.3,4.4)。经过重排列后,原本应该是(3,2,1,0),现在把1和0的位置上的值做了交换。
当然这里还可以改成更简单的版本。无需写重新排列的代码。
在程序i的for循环下加入:
for (int j = 0; j < 8; j++)
{
mB(i+j, 0) = sum_square.m256_f32[j];
}
这样写的话代码会简洁很多。
以上就是我的初学体会,如果有错,感谢各位指正。
参考链接