【发布时间】:2023-03-13 15:25:01
【问题描述】:
我正在编写一个库,我想在其中拥有一些没有任何依赖关系的基本 NxN 矩阵功能,这有点像一个学习项目。我正在将我的表现与 Eigen 进行比较。我已经能够与 SSE2 相当,甚至在几个方面都击败了它,而 AVX2 在很多方面都击败了它(它只使用 SSE2,所以并不令人惊讶)。
我的问题是我正在使用高斯消元法创建一个上对角矩阵,然后将对角线相乘以获得行列式。我在 N
可以进行更多优化,但时序看起来更像是算法时序复杂性问题,或者我没有看到主要的 SSE 优势。尝试时,简单地展开循环对我来说并没有多大作用。
有没有更好的计算行列式的算法?
标量代码
/*
Warning: Creates Temporaries!
*/
template<typename T, int ROW, int COLUMN> MML_INLINE T matrix<T, ROW, COLUMN>::determinant(void) const
{
/*
This method assumes square matrix
*/
assert(row() == col());
/*
We need to create a temporary
*/
matrix<T, ROW, COLUMN> temp(*this);
/*We convert the temporary to upper triangular form*/
uint N = row();
T det = T(1);
for (uint c = 0; c < N; ++c)
{
det = det*temp(c,c);
for (uint r = c + 1; r < N; ++r)
{
T ratio = temp(r, c) / temp(c, c);
for (uint k = c; k < N; k++)
{
temp(r, k) = temp(r, k) - ratio * temp(c, k);
}
}
}
return det;
}
AVX2
template<> float matrix<float>::determinant(void) const
{
/*
This method assumes square matrix
*/
assert(row() == col());
/*
We need to create a temporary
*/
matrix<float> temp(*this);
/*We convert the temporary to upper triangular form*/
float det = 1.0f;
const uint N = row();
const uint Nm8 = N - 8;
const uint Nm4 = N - 4;
uint c = 0;
for (; c < Nm8; ++c)
{
det *= temp(c, c);
float8 Diagonal = _mm256_set1_ps(temp(c, c));
for (uint r = c + 1; r < N;++r)
{
float8 ratio1 = _mm256_div_ps(_mm256_set1_ps(temp(r,c)), Diagonal);
uint k = c + 1;
for (; k < Nm8; k += 8)
{
float8 ref = _mm256_loadu_ps(temp._v + c*N + k);
float8 r0 = _mm256_loadu_ps(temp._v + r*N + k);
_mm256_storeu_ps(temp._v + r*N + k, _mm256_fmsub_ps(ratio1, ref, r0));
}
/*We go Scalar for the last few elements to handle non-multiples of 8*/
for (; k < N; ++k)
{
_mm_store_ss(temp._v + index(r, k), _mm_sub_ss(_mm_set_ss(temp(r, k)), _mm_mul_ss(_mm256_castps256_ps128(ratio1),_mm_set_ss(temp(c, k)))));
}
}
}
for (; c < Nm4; ++c)
{
det *= temp(c, c);
float4 Diagonal = _mm_set1_ps(temp(c, c));
for (uint r = c + 1; r < N; ++r)
{
float4 ratio = _mm_div_ps(_mm_set1_ps(temp[r*N + c]), Diagonal);
uint k = c + 1;
for (; k < Nm4; k += 4)
{
float4 ref = _mm_loadu_ps(temp._v + c*N + k);
float4 r0 = _mm_loadu_ps(temp._v + r*N + k);
_mm_storeu_ps(temp._v + r*N + k, _mm_sub_ps(r0, _mm_mul_ps(ref, ratio)));
}
float fratio = _mm_cvtss_f32(ratio);
for (; k < N; ++k)
{
temp(r, k) = temp(r, k) - fratio*temp(c, k);
}
}
}
for (; c < N; ++c)
{
det *= temp(c, c);
float Diagonal = temp(c, c);
for (uint r = c + 1; r < N; ++r)
{
float ratio = temp[r*N + c] / Diagonal;
for (uint k = c+1; k < N;++k)
{
temp(r, k) = temp(r, k) - ratio*temp(c, k);
}
}
}
return det;
}
【问题讨论】:
-
鉴于您显然已准备好深入研究细节,并且 Eigen 是开源的...why not look at what it does 或逐步完成...?
-
他们的方法对我来说没有多大意义,因此很难适应我的图书馆的运作方式。如果我理解它背后的数学推理,我就能很容易地适应它。我认为这与我开始研究的部分旋转有关。其他方法对我来说很有意义,但这是我第一次无法理解其背后的方法。一般来说,只是好奇另一个大脑是否有“最好的方法”的想法。即使在发布了这个问题之后,我仍然非常关注它,当我找到更好的方法时会发布我的代码。
-
this 你可能会感兴趣。
-
我认为您需要进行旋转以处理 (0 1; 1 0) 等具有行列式 -1 的矩阵,但我认为您的方法将在该矩阵上失败。
-
是的,它会的,我正在努力弄清楚我想在这样做之前使用什么算法。
标签: c++ algorithm matrix linear-algebra algebra