使用 Eigen 分解稀疏矩阵时避免动态内存分配
Avoiding dynamic memory allocation on factorizing sparse matrix with Eigen
在我的应用程序中,除了 class 构造函数之外,我需要避免动态内存分配(类似 malloc)。
我有一个稀疏半定矩阵 M,其元素在程序执行期间发生变化,但它保持固定的稀疏模式。
为了尽可能快地求解许多线性系统 M * x = b,我的想法是在我的 class 构造函数中使用就地分解,如 Inplace matrix decompositions 中所述,然后调用 factorize 每当 M 改变时的方法:
struct MyClass {
private:
SparseMatrix<double> As_;
SimplicialLDLT<Ref<SparseMatrix<double>>> solver_;
public:
/** Constructor */
MyClass( const SparseMatrix<double> &As )
: As_( As )
, solver_( As_ ) // Inplace decomposition
{}
void assign( const SparseMatrix<double> &As_new ) {
// Here As_new has the same sparsity pattern of As_
solver_.factorize( As_new );
}
void solve( const VectorXd &b, VectorXd &x )
{
x = solver_.solve( b );
}
}
但是 factorize 方法仍然会创建一个与 As_ 大小相同的临时文件,因此使用动态内存分配。
是否可以通过某种方式避免它?如果 Eigen API 不允许此功能,一个想法是创建 SimplicialLDLT 的派生 class,以便仅在 analyzePattern 方法中执行动态内存分配将在 class 构造函数中调用。欢迎提出建议...
最后我找到了使用 CSparse 库获取 H = P * A * P' 的解决方法:
class SparseLDLTLinearSolver {
private:
/** Ordering algorithm */
AMDOrdering<int> ordering_;
/** Ordering P matrix */
PermutationMatrix<Dynamic, Dynamic, int> P_;
/** Inverse of P matrix */
PermutationMatrix<Dynamic, Dynamic, int> P_inv_;
/** Permuted matrix H = P * A * P' */
SparseMatrix<double> H_;
/** H matrix CSparse structure */
cs H_cs_;
/** Support vector for solve */
VectorXd y_;
/** Support permutation vector */
VectorXi w_;
/** LDLT sparse linear solver without ordering */
SimplicialLDLT<SparseMatrix<double>, Upper, NaturalOrdering<int>> solver_;
public:
int SparseLDLTLinearSolver( const SparseMatrix<double> &A )
: P_( A.rows() )
, P_inv_( A.rows() )
, H_( A.rows(), A.rows() )
, y_( A.rows() )
, w_( A.rows() )
{
assert( ( A.rows() == A.cols() ) && "Invalid matrix" );
ordering_( A.selfadjointView<Upper>(), P_inv_ );
P_ = P_inv_.inverse();
H_ = A.triangularView<Upper>();
H_.makeCompressed();
// Fill CSparse structure
H_cs_.nzmax = H_.nonZeros();
H_cs_.m = H_.rows();
H_cs_.n = H_.cols();
H_cs_.p = H_.outerIndexPtr();
H_cs_.i = H_.innerIndexPtr();
H_cs_.x = H_.valuePtr();
H_cs_.nz = -1;
const cs_sparse A_cs{
A.nonZeros(), A.rows(), A.cols(),
const_cast<int*>( A.outerIndexPtr() ),
const_cast<int*>( A.innerIndexPtr() ),
const_cast<double*>( A.valuePtr() ),
-1 };
cs_symperm_noalloc( &A_cs, P_.indices().data(), &H_cs_, w_.data() );
solver_.analyzePattern( H_ );
// Factorize in order to allocate internal data and avoid it on next factorization
solver_.factorize( H_ );
/*.*/
return -solver_.info();
}
int factorize( const Eigen::SparseMatrix<double> &A )
{
assert( ( A.rows() == P_.size() ) && ( A.cols() == P_.size() ) &&
"Invalid matrix size" );
// Fill CSparse structure
const cs_sparse A_cs{
A.nonZeros(), A.rows(), A.cols(),
const_cast<int*>( A.outerIndexPtr() ),
const_cast<int*>( A.innerIndexPtr() ),
const_cast<double*>( A.valuePtr() ),
-1 };
cs_symperm_noalloc( &A_cs, P_.indices().data(), &H_cs_, w_.data() );
solver_.factorize( H_ );
/*.*/
return -solver_.info();
}
void solve( const VectorXd &rhs, VectorXd &x )
{
assert( ( rhs.size() == P_.size() ) && ( x.size() == P_.size() ) &&
"Invalid vector size" );
// Solve (P * A * P') * y = P * b, then return x = P' * y
y_ = solver_.solve( P_ * rhs );
x.noalias() = P_inv_ * y_;
}
};
cs_symperm_noalloc 是对 CSparse 库的 cs_symperm 函数的微小重构。
它似乎有效,至少对于我的特殊问题。如果 Eigen 避免为某些稀疏矩阵操作创建临时对象(进入堆),将来会非常有用。
在我的应用程序中,除了 class 构造函数之外,我需要避免动态内存分配(类似 malloc)。 我有一个稀疏半定矩阵 M,其元素在程序执行期间发生变化,但它保持固定的稀疏模式。
为了尽可能快地求解许多线性系统 M * x = b,我的想法是在我的 class 构造函数中使用就地分解,如 Inplace matrix decompositions 中所述,然后调用 factorize 每当 M 改变时的方法:
struct MyClass {
private:
SparseMatrix<double> As_;
SimplicialLDLT<Ref<SparseMatrix<double>>> solver_;
public:
/** Constructor */
MyClass( const SparseMatrix<double> &As )
: As_( As )
, solver_( As_ ) // Inplace decomposition
{}
void assign( const SparseMatrix<double> &As_new ) {
// Here As_new has the same sparsity pattern of As_
solver_.factorize( As_new );
}
void solve( const VectorXd &b, VectorXd &x )
{
x = solver_.solve( b );
}
}
但是 factorize 方法仍然会创建一个与 As_ 大小相同的临时文件,因此使用动态内存分配。
是否可以通过某种方式避免它?如果 Eigen API 不允许此功能,一个想法是创建 SimplicialLDLT 的派生 class,以便仅在 analyzePattern 方法中执行动态内存分配将在 class 构造函数中调用。欢迎提出建议...
最后我找到了使用 CSparse 库获取 H = P * A * P' 的解决方法:
class SparseLDLTLinearSolver {
private:
/** Ordering algorithm */
AMDOrdering<int> ordering_;
/** Ordering P matrix */
PermutationMatrix<Dynamic, Dynamic, int> P_;
/** Inverse of P matrix */
PermutationMatrix<Dynamic, Dynamic, int> P_inv_;
/** Permuted matrix H = P * A * P' */
SparseMatrix<double> H_;
/** H matrix CSparse structure */
cs H_cs_;
/** Support vector for solve */
VectorXd y_;
/** Support permutation vector */
VectorXi w_;
/** LDLT sparse linear solver without ordering */
SimplicialLDLT<SparseMatrix<double>, Upper, NaturalOrdering<int>> solver_;
public:
int SparseLDLTLinearSolver( const SparseMatrix<double> &A )
: P_( A.rows() )
, P_inv_( A.rows() )
, H_( A.rows(), A.rows() )
, y_( A.rows() )
, w_( A.rows() )
{
assert( ( A.rows() == A.cols() ) && "Invalid matrix" );
ordering_( A.selfadjointView<Upper>(), P_inv_ );
P_ = P_inv_.inverse();
H_ = A.triangularView<Upper>();
H_.makeCompressed();
// Fill CSparse structure
H_cs_.nzmax = H_.nonZeros();
H_cs_.m = H_.rows();
H_cs_.n = H_.cols();
H_cs_.p = H_.outerIndexPtr();
H_cs_.i = H_.innerIndexPtr();
H_cs_.x = H_.valuePtr();
H_cs_.nz = -1;
const cs_sparse A_cs{
A.nonZeros(), A.rows(), A.cols(),
const_cast<int*>( A.outerIndexPtr() ),
const_cast<int*>( A.innerIndexPtr() ),
const_cast<double*>( A.valuePtr() ),
-1 };
cs_symperm_noalloc( &A_cs, P_.indices().data(), &H_cs_, w_.data() );
solver_.analyzePattern( H_ );
// Factorize in order to allocate internal data and avoid it on next factorization
solver_.factorize( H_ );
/*.*/
return -solver_.info();
}
int factorize( const Eigen::SparseMatrix<double> &A )
{
assert( ( A.rows() == P_.size() ) && ( A.cols() == P_.size() ) &&
"Invalid matrix size" );
// Fill CSparse structure
const cs_sparse A_cs{
A.nonZeros(), A.rows(), A.cols(),
const_cast<int*>( A.outerIndexPtr() ),
const_cast<int*>( A.innerIndexPtr() ),
const_cast<double*>( A.valuePtr() ),
-1 };
cs_symperm_noalloc( &A_cs, P_.indices().data(), &H_cs_, w_.data() );
solver_.factorize( H_ );
/*.*/
return -solver_.info();
}
void solve( const VectorXd &rhs, VectorXd &x )
{
assert( ( rhs.size() == P_.size() ) && ( x.size() == P_.size() ) &&
"Invalid vector size" );
// Solve (P * A * P') * y = P * b, then return x = P' * y
y_ = solver_.solve( P_ * rhs );
x.noalias() = P_inv_ * y_;
}
};
cs_symperm_noalloc 是对 CSparse 库的 cs_symperm 函数的微小重构。
它似乎有效,至少对于我的特殊问题。如果 Eigen 避免为某些稀疏矩阵操作创建临时对象(进入堆),将来会非常有用。