diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index e2fda8406a0..5e3bf1fe461 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -1470,6 +1470,10 @@ E76046C128D4829000C36204 /* calcledgerlinesfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E76046BF28D4829000C36204 /* calcledgerlinesfunctor.cpp */; }; E76046C228D496B300C36204 /* calcledgerlinesfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E76046BF28D4829000C36204 /* calcledgerlinesfunctor.cpp */; }; E76046C328D496B400C36204 /* calcledgerlinesfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E76046BF28D4829000C36204 /* calcledgerlinesfunctor.cpp */; }; + E7605A2E2B6EF47E00903A6A /* functor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E7605A2D2B6EF47E00903A6A /* functor.cpp */; }; + E7605A2F2B6EF47E00903A6A /* functor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E7605A2D2B6EF47E00903A6A /* functor.cpp */; }; + E7605A302B6EF47E00903A6A /* functor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E7605A2D2B6EF47E00903A6A /* functor.cpp */; }; + E7605A312B6EF47E00903A6A /* functor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E7605A2D2B6EF47E00903A6A /* functor.cpp */; }; E763EF3F29E939C00029E56D /* convertfunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = E763EF3E29E939C00029E56D /* convertfunctor.h */; }; E763EF4029E939C00029E56D /* convertfunctor.h in Headers */ = {isa = PBXBuildFile; fileRef = E763EF3E29E939C00029E56D /* convertfunctor.h */; settings = {ATTRIBUTES = (Public, ); }; }; E763EF4229E939FB0029E56D /* convertfunctor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E763EF4129E939FB0029E56D /* convertfunctor.cpp */; }; @@ -2206,6 +2210,7 @@ E75EA9FC29CC3A88003A97A7 /* calcarticfunctor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = calcarticfunctor.cpp; path = src/calcarticfunctor.cpp; sourceTree = ""; }; E76046BC28D4828200C36204 /* calcledgerlinesfunctor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = calcledgerlinesfunctor.h; path = include/vrv/calcledgerlinesfunctor.h; sourceTree = ""; }; E76046BF28D4829000C36204 /* calcledgerlinesfunctor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = calcledgerlinesfunctor.cpp; path = src/calcledgerlinesfunctor.cpp; sourceTree = ""; }; + E7605A2D2B6EF47E00903A6A /* functor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = functor.cpp; path = src/functor.cpp; sourceTree = ""; }; E763EF3E29E939C00029E56D /* convertfunctor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = convertfunctor.h; path = include/vrv/convertfunctor.h; sourceTree = ""; }; E763EF4129E939FB0029E56D /* convertfunctor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = convertfunctor.cpp; path = src/convertfunctor.cpp; sourceTree = ""; }; E765675828BBFBA400BC6490 /* functorinterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = functorinterface.h; path = include/vrv/functorinterface.h; sourceTree = ""; }; @@ -3036,6 +3041,7 @@ E74A806528BC97D5005274E7 /* findfunctor.h */, E722106528F856C4002CD6E9 /* findlayerelementsfunctor.cpp */, E722106228F8569F002CD6E9 /* findlayerelementsfunctor.h */, + E7605A2D2B6EF47E00903A6A /* functor.cpp */, E765675B28BC019F00BC6490 /* functor.h */, E74A806828BC9842005274E7 /* functorinterface.cpp */, E765675828BBFBA400BC6490 /* functorinterface.h */, @@ -4153,6 +4159,7 @@ 4DEC4D9721C81E3B00D1D273 /* expan.cpp in Sources */, 4D766EFF20ACAD6D006875D8 /* neume.cpp in Sources */, 4D1694581E3A44F300569BF4 /* accid.cpp in Sources */, + E7605A2F2B6EF47E00903A6A /* functor.cpp in Sources */, 4DACC9912990F29A00B55913 /* atts_critapp.cpp in Sources */, 4D1694591E3A44F300569BF4 /* custos.cpp in Sources */, 4D16945A1E3A44F300569BF4 /* dot.cpp in Sources */, @@ -4430,6 +4437,7 @@ 4DC34BA819BC4A83006175CD /* accid.cpp in Sources */, 4DC34BAA19BC4A83006175CD /* custos.cpp in Sources */, 4D7927D020ECCC6D0002A45D /* view_slur.cpp in Sources */, + E7605A2E2B6EF47E00903A6A /* functor.cpp in Sources */, 4D81351E2322C41800F59C01 /* keyaccid.cpp in Sources */, BD2E4D95287587FD00B04350 /* stem.cpp in Sources */, E77C198328CD31AD00F5BADA /* calcdotsfunctor.cpp in Sources */, @@ -4720,6 +4728,7 @@ 4DC34BAD19BC4A83006175CD /* dot.cpp in Sources */, 4D2073FD22A3BCE000E0765F /* tabgrp.cpp in Sources */, 4D7927D220ECCC6D0002A45D /* view_slur.cpp in Sources */, + E7605A302B6EF47E00903A6A /* functor.cpp in Sources */, 4DACC9922990F29A00B55913 /* atts_critapp.cpp in Sources */, 4DB3D8B91F83D0C600B5FC2B /* systemmilestone.cpp in Sources */, 4D2073FA22A3BCE000E0765F /* tabdursym.cpp in Sources */, @@ -5004,6 +5013,7 @@ BB4C4B7F22A932DF001F6AF0 /* fb.cpp in Sources */, BB4C4BA322A932E5001F6AF0 /* textdirinterface.cpp in Sources */, BB4C4B3922A932CF001F6AF0 /* turn.cpp in Sources */, + E7605A312B6EF47E00903A6A /* functor.cpp in Sources */, 4DACC9932990F29A00B55913 /* atts_critapp.cpp in Sources */, BB4C4BA122A932E5001F6AF0 /* scoredefinterface.cpp in Sources */, BB4C4B4322A932D7001F6AF0 /* beatrpt.cpp in Sources */, diff --git a/include/vrv/adjustslursfunctor.h b/include/vrv/adjustslursfunctor.h index cc4665938fc..047f173d6e5 100644 --- a/include/vrv/adjustslursfunctor.h +++ b/include/vrv/adjustslursfunctor.h @@ -61,6 +61,15 @@ class AdjustSlursFunctor : public DocFunctor { */ bool ImplementsEndInterface() const override { return false; } + /* + * Enable parallelization + */ + ///@{ + ProcessingStrategy GetProcessingStrategy() const override { return ProcessingStrategy::SystemParallel; } + AdjustSlursFunctor *CloneFunctor() const override { return new AdjustSlursFunctor(*this); } + void MergeFunctor(const Functor *functor) override; + ///@} + /* * Check existence of cross-staff slurs */ diff --git a/include/vrv/calcbboxoverflowsfunctor.h b/include/vrv/calcbboxoverflowsfunctor.h index e5580cab0a7..2fbe5e94ea9 100644 --- a/include/vrv/calcbboxoverflowsfunctor.h +++ b/include/vrv/calcbboxoverflowsfunctor.h @@ -35,6 +35,14 @@ class CalcBBoxOverflowsFunctor : public DocFunctor { */ bool ImplementsEndInterface() const override { return true; } + /* + * Enable parallelization + */ + ///@{ + ProcessingStrategy GetProcessingStrategy() const override { return ProcessingStrategy::SystemParallel; } + CalcBBoxOverflowsFunctor *CloneFunctor() const override { return new CalcBBoxOverflowsFunctor(*this); } + ///@} + /* * Functor interface */ diff --git a/include/vrv/calcdotsfunctor.h b/include/vrv/calcdotsfunctor.h index 7c490bac9f5..04413c55f54 100644 --- a/include/vrv/calcdotsfunctor.h +++ b/include/vrv/calcdotsfunctor.h @@ -34,6 +34,14 @@ class CalcDotsFunctor : public DocFunctor { */ bool ImplementsEndInterface() const override { return false; } + /* + * Enable parallelization + */ + ///@{ + ProcessingStrategy GetProcessingStrategy() const override { return ProcessingStrategy::MeasureParallel; } + CalcDotsFunctor *CloneFunctor() const override { return new CalcDotsFunctor(*this); } + ///@} + /* * Functor interface */ diff --git a/include/vrv/calcstemfunctor.h b/include/vrv/calcstemfunctor.h index ee414083668..f49d0002d4a 100644 --- a/include/vrv/calcstemfunctor.h +++ b/include/vrv/calcstemfunctor.h @@ -34,6 +34,14 @@ class CalcStemFunctor : public DocFunctor { */ bool ImplementsEndInterface() const override { return false; } + /* + * Enable parallelization + */ + ///@{ + ProcessingStrategy GetProcessingStrategy() const override { return ProcessingStrategy::MeasureParallel; } + CalcStemFunctor *CloneFunctor() const override { return new CalcStemFunctor(*this); } + ///@} + /* * Functor interface */ diff --git a/include/vrv/functor.h b/include/vrv/functor.h index 6a08fac2bea..06fab581253 100644 --- a/include/vrv/functor.h +++ b/include/vrv/functor.h @@ -8,6 +8,10 @@ #ifndef __VRV_FUNCTOR_H__ #define __VRV_FUNCTOR_H__ +#include + +//---------------------------------------------------------------------------- + #include "comparison.h" #include "functorinterface.h" #include "vrvdef.h" @@ -16,6 +20,9 @@ namespace vrv { class Doc; +// Helper enum classes +enum class ProcessingStrategy { Sequential, MeasureParallel, SystemParallel }; + //---------------------------------------------------------------------------- // FunctorBase //---------------------------------------------------------------------------- @@ -76,6 +83,20 @@ class FunctorBase { */ virtual bool ImplementsEndInterface() const = 0; + /** + * Override to enable parallel functor processing + */ + ///@{ + virtual ProcessingStrategy GetProcessingStrategy() const { return ProcessingStrategy::Sequential; } + virtual int GetMaxNumberOfThreads() const { return 1; } + ///@} + + /** + * Returns the object class on which parallelization is applied + * Additionally checks if we have more than one thread + */ + std::optional GetConcurrentClass() const; + private: // public: @@ -108,6 +129,18 @@ class Functor : public FunctorBase, public FunctorInterface { virtual ~Functor() = default; ///@} + /** + * Copy child classes + * Must be overridden in order to use it (e.g. during parallelization) + */ + virtual Functor *CloneFunctor() const; + + /** + * Merge child classes, i.e. combine the state of the functor passed in with the current one + * The default implementation only considers the functor code + */ + virtual void MergeFunctor(const Functor *functor); + private: // public: @@ -133,6 +166,18 @@ class ConstFunctor : public FunctorBase, public ConstFunctorInterface { virtual ~ConstFunctor() = default; ///@} + /** + * Copy child classes + * Must be overridden in order to use it (e.g. during parallelization) + */ + virtual ConstFunctor *CloneFunctor() const; + + /** + * Merge child classes, i.e. combine the state of the functor passed in with the current one + * The default implementation only considers the functor code + */ + virtual void MergeFunctor(const ConstFunctor *functor); + private: // public: @@ -163,6 +208,11 @@ class DocFunctor : public Functor { */ Doc *GetDoc() { return m_doc; } + /* + * Get the maximal number of threads + */ + int GetMaxNumberOfThreads() const final; + private: // public: @@ -197,6 +247,11 @@ class DocConstFunctor : public ConstFunctor { */ const Doc *GetDoc() const { return m_doc; } + /* + * Get the maximal number of threads + */ + int GetMaxNumberOfThreads() const final; + private: // public: diff --git a/include/vrv/object.h b/include/vrv/object.h index 31bbf863f95..4e779c49373 100644 --- a/include/vrv/object.h +++ b/include/vrv/object.h @@ -646,7 +646,11 @@ class Object : public BoundingBox { */ ///@{ void Process(Functor &functor, int deepness = UNLIMITED_DEPTH, bool skipFirst = false); + void ProcessChildren(Functor &functor, int deepness); + void ProcessInParallel(Functor &functor, int deepness, const ArrayOfObjects &objects); void Process(ConstFunctor &functor, int deepness = UNLIMITED_DEPTH, bool skipFirst = false) const; + void ProcessChildren(ConstFunctor &functor, int deepness) const; + void ProcessInParallel(ConstFunctor &functor, int deepness, const ArrayOfConstObjects &objects) const; ///@} /** diff --git a/include/vrv/options.h b/include/vrv/options.h index 98a66d74f36..9e7e80b1f11 100644 --- a/include/vrv/options.h +++ b/include/vrv/options.h @@ -632,6 +632,7 @@ class Options { OptionBool m_incip; OptionBool m_justifyVertically; OptionBool m_landscape; + OptionInt m_maxThreads; OptionDbl m_minLastJustification; OptionBool m_mmOutput; OptionBool m_moveScoreDefinitionToStaff; diff --git a/include/vrv/resetfunctor.h b/include/vrv/resetfunctor.h index f8f4d5c1032..876040c79d0 100644 --- a/include/vrv/resetfunctor.h +++ b/include/vrv/resetfunctor.h @@ -109,6 +109,17 @@ class ResetHorizontalAlignmentFunctor : public Functor { */ bool ImplementsEndInterface() const override { return false; } + /* + * Enable parallelization + */ + ///@{ + ProcessingStrategy GetProcessingStrategy() const override { return ProcessingStrategy::MeasureParallel; } + ResetHorizontalAlignmentFunctor *CloneFunctor() const override + { + return new ResetHorizontalAlignmentFunctor(*this); + } + ///@} + /* * Functor interface */ @@ -167,6 +178,14 @@ class ResetVerticalAlignmentFunctor : public Functor { */ bool ImplementsEndInterface() const override { return false; } + /* + * Enable parallelization + */ + ///@{ + ProcessingStrategy GetProcessingStrategy() const override { return ProcessingStrategy::MeasureParallel; } + ResetVerticalAlignmentFunctor *CloneFunctor() const override { return new ResetVerticalAlignmentFunctor(*this); } + ///@} + /* * Functor interface */ diff --git a/src/adjustslursfunctor.cpp b/src/adjustslursfunctor.cpp index 34d5bb61c6b..2d2d709abb2 100644 --- a/src/adjustslursfunctor.cpp +++ b/src/adjustslursfunctor.cpp @@ -30,6 +30,16 @@ AdjustSlursFunctor::AdjustSlursFunctor(Doc *doc) : DocFunctor(doc) this->ResetCurrent(); } +void AdjustSlursFunctor::MergeFunctor(const Functor *functor) +{ + Functor::MergeFunctor(functor); + + const AdjustSlursFunctor *adjustSlursFunctor = dynamic_cast(functor); + if (adjustSlursFunctor && adjustSlursFunctor->HasCrossStaffSlurs()) { + m_crossStaffSlurs = true; + } +} + void AdjustSlursFunctor::ResetCurrent() { m_currentCurve = NULL; diff --git a/src/functor.cpp b/src/functor.cpp new file mode 100644 index 00000000000..7056adf9fd6 --- /dev/null +++ b/src/functor.cpp @@ -0,0 +1,84 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: functor.cpp +// Author: David Bauer +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#include "functor.h" + +//---------------------------------------------------------------------------- + +#include "doc.h" + +namespace vrv { + +//---------------------------------------------------------------------------- +// FunctorBase +//---------------------------------------------------------------------------- + +std::optional FunctorBase::GetConcurrentClass() const +{ + if (this->GetMaxNumberOfThreads() > 1) { + switch (this->GetProcessingStrategy()) { + case ProcessingStrategy::MeasureParallel: return MEASURE; + case ProcessingStrategy::SystemParallel: return SYSTEM; + default: break; + } + } + return std::nullopt; +} + +//---------------------------------------------------------------------------- +// Functor +//---------------------------------------------------------------------------- + +Functor *Functor::CloneFunctor() const +{ + assert(false); + return NULL; +} + +void Functor::MergeFunctor(const Functor *functor) +{ + if (functor->GetCode() == FUNCTOR_STOP) { + this->SetCode(FUNCTOR_STOP); + } +} + +//---------------------------------------------------------------------------- +// ConstFunctor +//---------------------------------------------------------------------------- + +ConstFunctor *ConstFunctor::CloneFunctor() const +{ + assert(false); + return NULL; +} + +void ConstFunctor::MergeFunctor(const ConstFunctor *functor) +{ + if (functor->GetCode() == FUNCTOR_STOP) { + this->SetCode(FUNCTOR_STOP); + } +} + +//---------------------------------------------------------------------------- +// DocFunctor +//---------------------------------------------------------------------------- + +int DocFunctor::GetMaxNumberOfThreads() const +{ + return m_doc->GetOptions()->m_maxThreads.GetValue(); +} + +//---------------------------------------------------------------------------- +// DocConstFunctor +//---------------------------------------------------------------------------- + +int DocConstFunctor::GetMaxNumberOfThreads() const +{ + return m_doc->GetOptions()->m_maxThreads.GetValue(); +} + +} // namespace vrv diff --git a/src/object.cpp b/src/object.cpp index 086e4589db2..092e800890b 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -11,10 +11,12 @@ #include #include +#include #include #include #include #include +#include //---------------------------------------------------------------------------- @@ -1039,30 +1041,90 @@ void Object::Process(Functor &functor, int deepness, bool skipFirst) --deepness; if (!this->SkipChildren(functor.VisibleOnly())) { - // We need a pointer to the array for the option to work on a reversed copy - ArrayOfObjects *children = &m_children; - Filters *filters = functor.GetFilters(); - if (functor.GetDirection() == BACKWARD) { - for (ArrayOfObjects::reverse_iterator iter = children->rbegin(); iter != children->rend(); ++iter) { - // we will end here if there is no filter at all or for the current child type - if (this->FiltersApply(filters, *iter)) { + this->ProcessChildren(functor, deepness); + } + + if (functor.ImplementsEndInterface() && !skipFirst) { + FunctorCode code = this->AcceptEnd(functor); + functor.SetCode(code); + } +} + +void Object::ProcessChildren(Functor &functor, int deepness) +{ + // Objects which will be processed in parallel are collected here + ArrayOfObjects parallelProcessed; + const std::optional concurrentClass = functor.GetConcurrentClass(); + // We need a pointer to the array for the option to work on a reversed copy + ArrayOfObjects *children = &m_children; + Filters *filters = functor.GetFilters(); + if (functor.GetDirection() == BACKWARD) { + for (ArrayOfObjects::reverse_iterator iter = children->rbegin(); iter != children->rend(); ++iter) { + // we will end here if there is no filter at all or for the current child type + if (this->FiltersApply(filters, *iter)) { + if (concurrentClass && (*iter)->Is(*concurrentClass)) { + parallelProcessed.push_back(*iter); + } + else { (*iter)->Process(functor, deepness); } } } - else { - for (ArrayOfObjects::iterator iter = children->begin(); iter != children->end(); ++iter) { - // we will end here if there is no filter at all or for the current child type - if (this->FiltersApply(filters, *iter)) { + } + else { + for (ArrayOfObjects::iterator iter = children->begin(); iter != children->end(); ++iter) { + // we will end here if there is no filter at all or for the current child type + if (this->FiltersApply(filters, *iter)) { + if (concurrentClass && (*iter)->Is(*concurrentClass)) { + parallelProcessed.push_back(*iter); + } + else { (*iter)->Process(functor, deepness); } } } } + // Perform parallel processing + if (!parallelProcessed.empty()) { + this->ProcessInParallel(functor, deepness, parallelProcessed); + } +} - if (functor.ImplementsEndInterface() && !skipFirst) { - FunctorCode code = this->AcceptEnd(functor); - functor.SetCode(code); +void Object::ProcessInParallel(Functor &functor, int deepness, const ArrayOfObjects &objects) +{ + const int hardwareLimit = static_cast(std::thread::hardware_concurrency()); + const int concurrency = std::min(functor.GetMaxNumberOfThreads(), hardwareLimit); + assert(concurrency >= 1); + + // Assign the objects to tasks + std::vector objectsPerTask(concurrency); + for (int index = 0; index < objects.size(); ++index) { + objectsPerTask[index % concurrency].push_back(objects[index]); + } + + // Clone the functor for each task + std::vector functorClones; + for (int taskIndex = 0; taskIndex < concurrency; ++taskIndex) { + functorClones.push_back(functor.CloneFunctor()); + } + + // Launch parallel tasks + std::vector> futures; + for (int taskIndex = 0; taskIndex < concurrency; ++taskIndex) { + futures.push_back(std::async(std::launch::async, [&objectsPerTask, &functorClones, taskIndex, deepness] { + for (Object *object : objectsPerTask[taskIndex]) { + object->Process(*functorClones[taskIndex], deepness); + } + })); + } + + // Synchronize and merge + for (std::future &future : futures) { + future.get(); + } + for (Functor *clone : functorClones) { + functor.MergeFunctor(clone); + delete clone; } } @@ -1093,30 +1155,90 @@ void Object::Process(ConstFunctor &functor, int deepness, bool skipFirst) const --deepness; if (!this->SkipChildren(functor.VisibleOnly())) { - // We need a pointer to the array for the option to work on a reversed copy - const ArrayOfObjects *children = &m_children; - Filters *filters = functor.GetFilters(); - if (functor.GetDirection() == BACKWARD) { - for (ArrayOfObjects::const_reverse_iterator iter = children->rbegin(); iter != children->rend(); ++iter) { - // we will end here if there is no filter at all or for the current child type - if (this->FiltersApply(filters, *iter)) { + this->ProcessChildren(functor, deepness); + } + + if (functor.ImplementsEndInterface() && !skipFirst) { + FunctorCode code = this->AcceptEnd(functor); + functor.SetCode(code); + } +} + +void Object::ProcessChildren(ConstFunctor &functor, int deepness) const +{ + // Objects which will be processed in parallel are collected here + ArrayOfConstObjects parallelProcessed; + const std::optional concurrentClass = functor.GetConcurrentClass(); + // We need a pointer to the array for the option to work on a reversed copy + const ArrayOfObjects *children = &m_children; + Filters *filters = functor.GetFilters(); + if (functor.GetDirection() == BACKWARD) { + for (ArrayOfObjects::const_reverse_iterator iter = children->rbegin(); iter != children->rend(); ++iter) { + // we will end here if there is no filter at all or for the current child type + if (this->FiltersApply(filters, *iter)) { + if (concurrentClass && (*iter)->Is(*concurrentClass)) { + parallelProcessed.push_back(*iter); + } + else { (*iter)->Process(functor, deepness); } } } - else { - for (ArrayOfObjects::const_iterator iter = children->begin(); iter != children->end(); ++iter) { - // we will end here if there is no filter at all or for the current child type - if (this->FiltersApply(filters, *iter)) { + } + else { + for (ArrayOfObjects::const_iterator iter = children->begin(); iter != children->end(); ++iter) { + // we will end here if there is no filter at all or for the current child type + if (this->FiltersApply(filters, *iter)) { + if (concurrentClass && (*iter)->Is(*concurrentClass)) { + parallelProcessed.push_back(*iter); + } + else { (*iter)->Process(functor, deepness); } } } } + // Perform parallel processing + if (!parallelProcessed.empty()) { + this->ProcessInParallel(functor, deepness, parallelProcessed); + } +} - if (functor.ImplementsEndInterface() && !skipFirst) { - FunctorCode code = this->AcceptEnd(functor); - functor.SetCode(code); +void Object::ProcessInParallel(ConstFunctor &functor, int deepness, const ArrayOfConstObjects &objects) const +{ + const int hardwareLimit = static_cast(std::thread::hardware_concurrency()); + const int concurrency = std::min(functor.GetMaxNumberOfThreads(), hardwareLimit); + assert(concurrency >= 1); + + // Assign the objects to tasks + std::vector objectsPerTask(concurrency); + for (int index = 0; index < objects.size(); ++index) { + objectsPerTask[index % concurrency].push_back(objects[index]); + } + + // Clone the functor for each task + std::vector functorClones; + for (int taskIndex = 0; taskIndex < concurrency; ++taskIndex) { + functorClones.push_back(functor.CloneFunctor()); + } + + // Launch parallel tasks + std::vector> futures; + for (int taskIndex = 0; taskIndex < concurrency; ++taskIndex) { + futures.push_back(std::async(std::launch::async, [&objectsPerTask, &functorClones, taskIndex, deepness] { + for (const Object *object : objectsPerTask[taskIndex]) { + object->Process(*functorClones[taskIndex], deepness); + } + })); + } + + // Synchronize and merge + for (std::future &future : futures) { + future.get(); + } + for (ConstFunctor *clone : functorClones) { + functor.MergeFunctor(clone); + delete clone; } } diff --git a/src/options.cpp b/src/options.cpp index 0a24f1c16bd..af9dfa6d89d 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -1050,6 +1050,11 @@ Options::Options() m_landscape.Init(false); this->Register(&m_landscape, "landscape", &m_general); + m_maxThreads.SetInfo( + "Maximal number of threads", "The maximal number of threads (values above 1 activate parallelization)"); + m_maxThreads.Init(1, 1, 256); + this->Register(&m_maxThreads, "maxThreads", &m_general); + m_minLastJustification.SetInfo("Minimum last-system-justification width", "The last system is only justified if the unjustified width is greater than this percent"); m_minLastJustification.Init(0.8, 0.0, 1.0);