151class ThreadedSparseProduct {
153 typedef SparseMatrixType_ SparseMatrixType;
154 typedef typename SparseMatrixType::Scalar Scalar;
155 typedef typename SparseMatrixType::RealScalar RealScalar;
156 typedef typename SparseMatrixType::StorageIndex StorageIndex;
158 enum { IsRowMajor =
static_cast<int>(SparseMatrixType::IsRowMajor) };
173 ThreadedSparseProduct() =
default;
175 explicit ThreadedSparseProduct(
const SparseMatrixType& mat, ThreadPool*
pool =
nullptr) : m_pool(
pool) {
179 ~ThreadedSparseProduct() {
delete m_mirror.load(std::memory_order_acquire); }
181 ThreadedSparseProduct(
const ThreadedSparseProduct&) =
delete;
182 ThreadedSparseProduct& operator=(
const ThreadedSparseProduct&) =
delete;
187 eigen_assert(mat.isCompressed() &&
"ThreadedSparseProduct requires a compressed SparseMatrix");
189 delete m_mirror.exchange(
nullptr, std::memory_order_acq_rel);
190 m_adj_partition.clear();
192 build_partition(mat.outerIndexPtr(), mat.outerSize(), mat.nonZeros(), m_native_partition);
203 delete m_mirror.exchange(
nullptr, std::memory_order_acq_rel);
207 Index rows()
const {
return m_mat ? m_mat->rows() : Index(0); }
208 Index cols()
const {
return m_mat ? m_mat->cols() : Index(0); }
211 void apply(
const ConstVectorRef& x, MutableVectorRef y)
const {
212 apply_impl<
false,
true>(x, y, Scalar(1));
216 apply_impl<
true,
true>(x, y, Scalar(1));
219 void applyAddTo(
const ConstVectorRef& x, MutableVectorRef y,
const Scalar& alpha)
const {
220 apply_impl<
false,
false>(x, y, alpha);
223 void applyAdjointAddTo(
const ConstVectorRef& x, MutableVectorRef y,
const Scalar& alpha)
const {
224 apply_impl<
true,
false>(x, y, alpha);
228 ThreadPool*
pool()
const {
return m_pool ? m_pool : &internal::default_threaded_sparse_pool(); }
231 bool hasMirror()
const {
return m_mirror.load(std::memory_order_acquire) !=
nullptr; }
234 template <
bool Adjo
int,
bool Overwrite>
235 void apply_impl(
const ConstVectorRef& x, MutableVectorRef& y,
const Scalar& alpha)
const {
236 eigen_assert(m_mat &&
"ThreadedSparseProduct: matrix not set; call analyzePattern() first");
238 eigen_assert(x.size() == m_mat->rows());
239 eigen_assert(y.size() == m_mat->cols());
241 eigen_assert(x.size() == m_mat->cols());
242 eigen_assert(y.size() == m_mat->rows());
249 eigen_assert((x.size() == 0 || y.size() == 0 || std::uintptr_t(x.data() + x.size()) <= std::uintptr_t(y.data()) ||
250 std::uintptr_t(y.data() + y.size()) <= std::uintptr_t(x.data())) &&
251 "ThreadedSparseProduct: x and y must not overlap");
256 constexpr bool NeedRowMajorView = !Adjoint;
257 constexpr bool NativeIsRowMajor = IsRowMajor;
258 constexpr bool UseMirror = NeedRowMajorView != NativeIsRowMajor;
260 EIGEN_IF_CONSTEXPR (UseMirror) {
261 const MirrorType& m = ensure_mirror();
262 run<Adjoint, Overwrite>(m.valuePtr(), m.innerIndexPtr(), m.outerIndexPtr(), m.innerNonZeroPtr(),
263 m.outerSize(), m_adj_partition, x, y, alpha);
265 run<Adjoint, Overwrite>(m_mat->valuePtr(), m_mat->innerIndexPtr(), m_mat->outerIndexPtr(),
266 m_mat->innerNonZeroPtr(),
267 m_mat->outerSize(), m_native_partition, x, y, alpha);
272 static constexpr Index kThreadingThreshold = 20000;
274 template <
bool Conjugate,
bool Overwrite>
275 void run(
const Scalar* vals,
const StorageIndex* inner,
const StorageIndex* outer,
const StorageIndex* innerNnz,
276 Index outerSize,
const std::vector<Index>& part,
const ConstVectorRef& x, MutableVectorRef& y,
277 const Scalar& alpha)
const {
283#ifdef EIGEN_HAS_OPENMP
284 const int T = target_thread_count();
286 const int T =
static_cast<int>(part.size()) - 1;
288 const Index total_nnz = m_mat->nonZeros();
290 const Scalar* xp = x.data();
291 Scalar* yp = y.data();
292 if (T <= 1 || total_nnz < kThreadingThreshold) {
293 internal::run_dot_chunk<Conjugate, Overwrite, Scalar, StorageIndex>(vals, inner, outer, innerNnz, xp, yp, 0,
298#ifdef EIGEN_HAS_OPENMP
303 const Index chunk = numext::maxi<Index>(Index(1), (outerSize + Index(T) * 4 - 1) / (Index(T) * 4));
304#pragma omp parallel for schedule(dynamic, chunk) num_threads(T)
305 for (Index i = 0; i < outerSize; ++i) {
306 internal::run_dot_row<Conjugate, Overwrite, Scalar, StorageIndex>(vals, inner, outer, innerNnz, xp, yp, i, alpha);
312 Barrier barrier(
static_cast<unsigned>(T));
313 ThreadPool* p =
pool();
314 for (
int t = 1; t < T; ++t) {
315 const Index lo = part[t], hi = part[t + 1];
320 p->Schedule([=, &barrier]() {
321 internal::run_dot_chunk<Conjugate, Overwrite, Scalar, StorageIndex>(vals, inner, outer, innerNnz, xp, yp, lo,
326 internal::run_dot_chunk<Conjugate, Overwrite, Scalar, StorageIndex>(vals, inner, outer, innerNnz, xp, yp, part[0],
335 const MirrorType& ensure_mirror()
const {
336 MirrorType* m = m_mirror.load(std::memory_order_acquire);
338 std::lock_guard<std::mutex> lock(m_mirror_init_mu);
339 m = m_mirror.load(std::memory_order_relaxed);
350 std::unique_ptr<MirrorType> built(
new MirrorType(m_mat->rows(), m_mat->cols()));
352 built->makeCompressed();
353 build_partition(built->outerIndexPtr(), built->outerSize(), built->nonZeros(), m_adj_partition);
355 m_mirror.store(built.release(), std::memory_order_release);
363 int target_thread_count()
const {
364#ifdef EIGEN_HAS_OPENMP
365 return numext::maxi(1, Eigen::nbThreads());
367 return pool()->NumThreads();
376 void build_partition(
const StorageIndex* outer, Index outerSize, Index nnz, std::vector<Index>& part)
const {
377 const int T = target_thread_count();
378 internal::compute_nnz_balanced_partition(outer, outerSize, nnz, T, part);
380 for (std::size_t t = 0; t + 1 < part.size(); ++t)
381 if (part[t + 1] > part[t]) ++non_empty;
382 if (non_empty * 2 < T) {
389 const SparseMatrixType* m_mat =
nullptr;
390 ThreadPool* m_pool =
nullptr;
394 std::vector<Index> m_native_partition;
398 mutable std::atomic<MirrorType*> m_mirror{
nullptr};
399 mutable std::mutex m_mirror_init_mu;
400 mutable std::vector<Index> m_adj_partition;