计算行列式的最快方法?

时间:2016-03-28 00:18:29

标签: c++ algorithm matrix linear algebra

我正在编写一个库,我想要一些基本的NxN矩阵功能,它没有任何依赖关系,这是一个学习项目。我将我的表现与Eigen进行了比较。我已经能够相当平等甚至在SSE2的几个方面击败它的性能,并且AVX2在很多方面击败了它(它只使用SSE2,所以不是非常令人惊讶)。

我的问题是我使用高斯消元法创建一个上对角线矩阵,然后乘以对角线得到行列式。我在N< N< 300但是之后Eigen把我吹走了,随着矩阵变大,它变得更糟。鉴于所有内存都是按顺序访问的,编译器反汇编看起来并不可怕,我不认为这是一个优化问题。可以进行更多优化,但时间看起来更像是算法时序复杂度问题,或者我没有看到主要的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;
}

2 个答案:

答案 0 :(得分:3)

通过高斯消元将n×n矩阵减少到上(或下)三角形的算法通常具有O(n ^ 3)的复杂度(其中^表示“对于幂”)。

有计算确定性的替代方法,例如评估特征值集合(方阵的行列式等于其特征值的乘积)。对于一般矩阵,完整的特征值集的计算也 - 实际上 - O(n ^ 3)。

然而,理论上,特征值集的计算具有n^w的复杂度,其中w在2和2.376之间 - 这意味着对于(更多)更大的矩阵,它将比使用高斯消元更快。看看James Demmel,Ioana Dumitriu和Olga Holtz撰写的文章“快速线性代数是稳定的”,Numerische Mathematik,第108卷,第1期,第59-91页,2007年11月。如果Eigen使用的方法复杂性较低比O(n ^ 3)更大的矩阵(我不知道,从来没有理由去调查这些事情)可以解释你的观察结果。

答案 1 :(得分:0)

答案大多数地方似乎都使用Block LU Factorization在同一个内存空间中创建一个Lower triangle和Upper triangle矩阵。它是~O(n ^ 2.5),具体取决于您使用的块的大小。

以下是莱斯大学的一个强项,解释了算法。

www.caam.rice.edu/~timwar/MA471F03/Lecture24.ppt

由矩阵除法意味着乘以其逆。

这个想法似乎是显着增加n ^ 2个操作的数量,但减少了数量m ^ 3,这实际上降低了算法的复杂性,因为m是固定的小尺寸。

要采取一些有效的方式来写这个,因为要有效地做到这一点需要“到位”。算法我还没写过。