C++ 如何使用SSE优化矩阵3乘3与点的乘法?

C++ 如何使用SSE优化矩阵3乘3与点的乘法?,c++,optimization,sse,matrix-multiplication,C++,Optimization,Sse,Matrix Multiplication,我必须在图像的每个点上应用一个变换矩阵来获得新的点坐标 为此,我创建了一个自定义的Matrix3by3类,其中包含一个大小为9的浮点数组 为了将矩阵应用于每个点,首先我创建了以下函数: constexpr auto apply_matrix(const Matrix3by3 & m, const Vec2i & p) -> Vec2f { const auto x = m.at(0, 0) * p.x + m.at(0, 1) * p.y + m.at(0, 2);

我必须在图像的每个点上应用一个变换矩阵来获得新的点坐标

为此,我创建了一个自定义的
Matrix3by3
类,其中包含一个大小为9的浮点数组

为了将矩阵应用于每个点,首先我创建了以下函数:

constexpr auto apply_matrix(const Matrix3by3 & m, const Vec2i & p) -> Vec2f
{
  const auto x = m.at(0, 0) * p.x + m.at(0, 1) * p.y + m.at(0, 2);
  const auto y = m.at(1, 0) * p.x + m.at(1, 1) * p.y + m.at(1, 2);
  const auto z = m.at(2, 0) * p.x + m.at(2, 1) * p.y + m.at(2, 2);

  return { x / z, y / z };
}
如您所见,此函数将执行简单的矩阵乘法,而不执行最后一次乘法,因为在我的2D图像中没有z值

这非常有效,但由于这部分代码是热门代码,我正在尝试对其进行优化,因此我创建了一个SSE版本:

constexpr auto apply_matrix(const Matrix3by3 & m, const Vec2i & p) -> Vec2f
{
  using SSEVec3 = union {
    struct
    {
      float z, y, x;

    };
    __m128 values_ = _mm_setzero_ps();
  };

  const auto mvec1 = _mm_set_ps(0, m.at(0, 0), m.at(0, 1), m.at(0, 2));
  const auto mvec2 = _mm_set_ps(0, m.at(1, 0), m.at(1, 1), m.at(1, 2));
  const auto mvec3 = _mm_set_ps(0, m.at(2, 0), m.at(2, 1), m.at(2, 2));

  const auto pvec1 = _mm_set1_ps(static_cast<float>(p.x));
  const auto pvec2 = _mm_set1_ps(static_cast<float>(p.y));

  auto result = SSEVec3{};
  result.values_ = _mm_add_ps(_mm_add_ps(_mm_mul_ps(mvec1, pvec1), _mm_mul_ps(mvec2, pvec2)), mvec3);

  return { result.x / result.z, result.y / result.z };
}
constexpr自动应用矩阵(constmatrix3by3&m,constvec2i&p)->Vec2f
{
使用SSEVec3=联合{
结构
{
浮动z,y,x;
};
__m128值=_mm_setzero_ps();
};
const auto mvec1=_mm_set_ps(0,m.at(0,0),m.at(0,1),m.at(0,2));
const auto mvec2=_mm_set_ps(0,m.at(1,0),m.at(1,1),m.at(1,2));
const auto mvec3=_mm_set_ps(0,m.at(2,0),m.at(2,1),m.at(2,2));
const auto pvec1=_mm_set1_ps(静态_cast(p.x));
const auto pvec2=_mm_set1_ps(静态投射(p.y));
自动结果=SSEVec3{};
结果.值u=mm_add_ps(mm_add_ps(mm_mul_ps(mvec1,pvec1),mm_mul_ps(mvec2,pvec2)),mvec3);
返回{result.x/result.z,result.y/result.z};
}
这也行得通,但它比第一个版本慢,而且因为我正在学习SSE,我不明白为什么会这样

我对第二个版本的想法是并行计算x、y和z值

所以,这就是我的问题,为什么SSE版本较慢,我如何优化它以尽可能快


谢谢

通常,只优化需要优化的,而不是您猜测需要优化的

可能(原始)代码中最糟糕的一点是重复分区,而您的“优化”根本没有帮助。除法浮点或双精度浮点远比这段代码中的其他方法糟糕,因此最好的优化方法是将1/z(除法一次)计算成一个辅助变量,然后将结果乘以两倍


但是,正如一开始所说的,您可能不需要任何优化,或者您可能需要其他优化。测试、分析并查找最慢的编码。猜测通常会导致浪费精力和不必要的代码复杂性。

通常,只优化需要优化的内容,而不是您猜测需要优化的内容

可能(原始)代码中最糟糕的一点是重复分区,而您的“优化”根本没有帮助。除法浮点或双精度浮点远比这段代码中的其他方法糟糕,因此最好的优化方法是将1/z(除法一次)计算成一个辅助变量,然后将结果乘以两倍


但是,正如一开始所说的,您可能不需要任何优化,或者您可能需要其他优化。测试、分析并查找最慢的编码。猜测通常会导致浪费精力和不必要的代码复杂性。

每当在这个级别进行优化时,您应该检查生成的汇编代码:
gcc-S
。你能让你的代码更完整吗:周围的代码是什么样子的?你跑了几次?另外,在比较函数的性能时,我会避免声明函数
constexpr
,我认为这可能是由最后的z除法决定的。在任何情况下,您都应该首先查看明显版本的反汇编输出。考虑到现代C++风格,你可能使用了一个非常新的编译器,其中自动矢量化可能会被打开。根据您拥有的芯片,使用mm_set_ps和m.at的手动存储也会杀死您。SSE没有“聚集”指令,因此请执行未对齐加载,然后屏蔽最低的单词或忽略结果。这样滥用
\u mm\u set\u ps
是一种主要的反模式,这应该是一种适当的宽加载(这会使一个车道的加载过多,如果需要,请进行pad).您是否尝试过比较启用SIMD和未启用SIMD编译器优化时的性能?这将告诉您编译器是否已经在使用SIMD,就像查看程序集一样。无论哪种方式,您都应该在矩阵中填充数组,并按照@harold的建议使用load。是否“我图像的每个点”的字面意思是
{(0,0),(0,width-1),(1,0),…,(height-1,width-1)}
?或者只是一组更大的“有趣”点?我假设所有点的矩阵都保持不变?无论何时在这个级别进行优化,都应该检查生成的汇编代码:
gcc-S
。你能让你的代码更完整吗:周围的代码是什么样子的?你跑了几次?另外,在比较函数的性能时,我会避免声明函数
constexpr
,我认为这可能是由最后的z除法决定的。在任何情况下,您都应该首先查看明显版本的反汇编输出。考虑到现代C++风格,你可能使用了一个非常新的编译器,其中自动矢量化可能会被打开。根据您拥有的芯片,使用mm_set_ps和m.at的手动存储也会杀死您。SSE没有“聚集”指令,因此请执行未对齐加载,然后屏蔽最低的单词或忽略结果。这样滥用
\u mm\u set\u ps
是一种主要的反模式,这应该是一种适当的宽加载(这会使一个车道的加载过多,如果需要,请进行pad).您是否尝试过比较启用SIMD和未启用SIMD编译器优化时的性能?这将告诉您编译器是否已经在使用SIMD,就像查看程序集一样。无论哪种方式,你都应该在矩阵中填充数组,并按照@harold的建议使用load。“我的图像的每个点”的字面意思是什么