From 02515a35425fe1ae85d78ee7a8ff15c15817bf6a Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Wed, 1 Oct 2025 08:33:48 +1000 Subject: [PATCH 1/4] OpenMP support of tensor processing. --- Makefile | 3 ++- RavelCAPI | 2 +- engine/minskyTensorOps.cc | 24 +++++++++++++++++------- engine/rungeKutta.cc | 12 ++++++++++-- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 6338d9203..5fdb05c4f 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ ifdef MXE MAKEOVERRIDES+=MXE_PREFIX=x86_64-w64-mingw32.shared endif -MAKEOVERRIDES+=DEBUG=$(DEBUG) +MAKEOVERRIDES+=DEBUG=$(DEBUG) OPENMP=$(OPENMP) # Build EcoLab with clang to avoid problems with old gcc compilers on # some Linux distros ifeq ($(HAVE_CLANG),1) @@ -213,6 +213,7 @@ FLAGS+=-std=c++20 -UTR1 -Ischema -Iengine -Imodel -Icertify/include -IRESTServic ifeq ($(CPLUSPLUS),clang++) # note some of these flags are disabling warnings that are invalid in some circumstances FLAGS+=-Wno-unused-command-line-argument -Wno-unknown-warning-option -Wno-defaulted-function-deleted -Wno-uninitialized +LIBS+=-fopenmp endif ifeq ($(DEBUG), 1) diff --git a/RavelCAPI b/RavelCAPI index fd6c74d37..909de651e 160000 --- a/RavelCAPI +++ b/RavelCAPI @@ -1 +1 @@ -Subproject commit fd6c74d37e133e1cc183b4f43f95cd00ce75a608 +Subproject commit 909de651ee9d7dc5d99f8926b42b5945b7619d42 diff --git a/engine/minskyTensorOps.cc b/engine/minskyTensorOps.cc index 014562e8a..95fd8f201 100644 --- a/engine/minskyTensorOps.cc +++ b/engine/minskyTensorOps.cc @@ -1520,13 +1520,18 @@ namespace minsky double v=0; if (!chain.empty()) { - v=(*chain.back())[i]; + auto chainBack=chain.back(); // stash a reference to the current chain, in case it gets updated below + v=(*chainBack)[i]; if (m_timestamptimestamp()) - { // update hypercube if argument has changed - const_cast(ravel).populateHypercube(arg->hypercube()); - chain=ravel::createRavelChain(ravel.getState(), arg); - m_timestamp=Timestamp::clock::now(); - } +#ifdef _OPENMP +#pragma omp critical(RavelTensor_updateChain) + if (m_timestamptimestamp()) // test again once lock has been obtained +#endif + { // update hypercube if argument has changed + const_cast(ravel).populateHypercube(arg->hypercube()); + chain=ravel::createRavelChain(ravel.getState(), arg); + m_timestamp=Timestamp::clock::now(); + } } return v; } @@ -1683,7 +1688,12 @@ namespace minsky throw FlowVarsResized(); result.ev->update(fv, n, sv); assert(result.size()==rhs->size()); - for (size_t i=0; isize(); ++i) + // the assumption here is that rhs is independent of rhs + auto rhsSize=rhs->size(); +#ifdef _OPENMP +#pragma omp parallel for if(rhsSize>20) +#endif + for (size_t i=0; i20) +#endif + for (size_t i=0; ieval(flow.data(), flow.size(), vars); // then create the result using the Godley table - for (size_t i=0; i20) +#endif + for (size_t i=0; i Date: Sun, 5 Oct 2025 20:55:02 +1100 Subject: [PATCH 2/4] Fix regression test failures. --- Makefile | 2 +- engine/evalGodley.cc | 13 ++++++++- engine/minskyTensorOps.cc | 60 ++++++++++++++++++++++++++++++--------- engine/rungeKutta.cc | 22 ++++++++++++-- test/Makefile | 6 ++-- 5 files changed, 83 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 0532f631d..92b1dc0cc 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ ifdef MXE MAKEOVERRIDES+=MXE_PREFIX=x86_64-w64-mingw32.shared endif -MAKEOVERRIDES+=DEBUG=$(DEBUG) OPENMP=$(OPENMP) +MAKEOVERRIDES+=DEBUG=$(DEBUG) # Build EcoLab with clang to avoid problems with old gcc compilers on # some Linux distros ifeq ($(HAVE_CLANG),1) diff --git a/engine/evalGodley.cc b/engine/evalGodley.cc index b89fd900b..3a17aa772 100644 --- a/engine/evalGodley.cc +++ b/engine/evalGodley.cc @@ -91,11 +91,22 @@ namespace minsky void EvalGodley::eval(double* sv, const double* fv) const { - for (size_t i=0; i20) +#endif + for (size_t i=0; i20) + for (size_t i=0; i struct GeneralTensorOp: public civita::ReductionOp { - GeneralTensorOp(): civita::ReductionOp([](double& x, double y,size_t){x*=(y>0.5);},1){} + GeneralTensorOp(): civita::ReductionOp([](double& x, double y,size_t){ +#ifdef _OPENMP +#pragma omp atomic +#endif + x*=(y>0.5); + },1){} }; template <> struct GeneralTensorOp: public civita::Scan { - GeneralTensorOp(): civita::Scan([](double& x,double y,size_t){x+=y;}) {} + GeneralTensorOp(): civita::Scan([](double& x,double y,size_t){ +#ifdef _OPENMP +#pragma omp atomic +#endif + x+=y; + }) {} }; template <> struct GeneralTensorOp: public civita::Scan { - GeneralTensorOp(): civita::Scan([](double& x,double y,size_t){x*=y;}) {} + GeneralTensorOp(): civita::Scan([](double& x,double y,size_t){ +#ifdef _OPENMP +#pragma omp atomic +#endif + x*=y; + }) {} }; template <> @@ -1504,6 +1519,7 @@ namespace minsky mutable vector chain; TensorPtr arg; mutable Timestamp m_timestamp; + bool redoCalc=false; CLASSDESC_ACCESS(Ravel); public: @@ -1518,21 +1534,28 @@ namespace minsky double operator[](size_t i) const override { double v=0; - if (!chain.empty()) + bool redoCalc=false; + do { + if (!chain.empty()) + v=(*chain.back())[i]; + auto chainBack=chain.back(); // stash a reference to the current chain, in case it gets updated below v=(*chainBack)[i]; - if (m_timestamptimestamp()) -#ifdef _OPENMP -#pragma omp critical(RavelTensor_updateChain) - if (m_timestamptimestamp()) // test again once lock has been obtained +#ifdef _OPENMP +#pragma omp single copyprivate(redoCalc) #endif + { + redoCalc=m_timestamptimestamp(); + if (redoCalc) { // update hypercube if argument has changed const_cast(ravel).populateHypercube(arg->hypercube()); chain=ravel::createRavelChain(ravel.getState(), arg); m_timestamp=Timestamp::clock::now(); } + } } + while (redoCalc); return v; } size_t size() const override {return chain.empty()? 1: chain.back()->size();} @@ -1690,15 +1713,26 @@ namespace minsky assert(result.size()==rhs->size()); // the assumption here is that rhs is independent of rhs auto rhsSize=rhs->size(); + string errMsg; #ifdef _OPENMP #pragma omp parallel for if(rhsSize>20) #endif for (size_t i=0; i20) + //#pragma omp parallel for if(eqSize>20) + // TODO - equations are ordered, so we can't naively parallelise this loop. #endif for (size_t i=0; ieval(flow.data(), flow.size(), vars); + try + { + equations[i]->eval(flow.data(), flow.size(), vars); + } + catch (std::exception& e) + { +#ifdef _OPENMP +#pragma omp critical +#endif + threadErrMsg=e.what(); + } + if (!threadErrMsg.empty()) + throw runtime_error(threadErrMsg); // then create the result using the Godley table auto stvSize=stockVars.size(); @@ -278,6 +292,10 @@ namespace minsky } // enable element-wise integration of tensor variables. for feature 147 assert(i->input().size()==i->stock->size()); + auto inputSize=i->input().size(); +#ifdef _OPENMP +#pragma omp parallel for if(inputSize>20) +#endif for (size_t j=0; jinput().size(); ++j) result[i->stock->idx()+j] = reverseFactor * (i->input().isFlowVar()? flow[i->input().idx()+j] : vars[i->input().idx()+j]); diff --git a/test/Makefile b/test/Makefile index cf78f0925..6b7cbc13e 100755 --- a/test/Makefile +++ b/test/Makefile @@ -26,6 +26,9 @@ else LIBS+=-lxcb endif +ifdef OPENMP +FLAGS+=-fopenmp +endif # RSVG dependencies calculated here FLAGS+=$(shell pkg-config --cflags librsvg-2.0) @@ -34,9 +37,6 @@ LIBS+=$(shell pkg-config --libs librsvg-2.0) EXES=cmpFp checkSchemasAreSame #testDatabase testGroup -ifdef AEGIS -#EXES+=tcl-cov -endif ifdef ASAN FLAGS+=-fsanitize=address -fno-omit-frame-pointer From db3757e6f55baac81546eb91870444464d8b9f8e Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Mon, 6 Oct 2025 09:19:05 +1100 Subject: [PATCH 3/4] Separate serial/parallel handling of exceptions Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- engine/rungeKutta.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/engine/rungeKutta.cc b/engine/rungeKutta.cc index 56ffb17c1..9e056cb0d 100644 --- a/engine/rungeKutta.cc +++ b/engine/rungeKutta.cc @@ -257,21 +257,22 @@ namespace minsky #ifdef _OPENMP //#pragma omp parallel for if(eqSize>20) // TODO - equations are ordered, so we can't naively parallelise this loop. -#endif for (size_t i=0; ieval(flow.data(), flow.size(), vars); } - catch (std::exception& e) + catch (const std::exception& e) { -#ifdef _OPENMP #pragma omp critical -#endif - threadErrMsg=e.what(); + threadErrMsg = e.what(); } if (!threadErrMsg.empty()) throw runtime_error(threadErrMsg); +#else + for (size_t i=0; ieval(flow.data(), flow.size(), vars); +#endif // then create the result using the Godley table auto stvSize=stockVars.size(); From 569489a2aae1cce62263d14f0a0c4550ef19ca72 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Mon, 6 Oct 2025 10:19:15 +1100 Subject: [PATCH 4/4] Address code review comments. --- Makefile | 4 ++++ engine/evalGodley.cc | 2 +- engine/minskyTensorOps.cc | 25 ++++++++++++------------ engine/rungeKutta.cc | 41 +++++++++++++++++++++------------------ test/Makefile | 1 + 5 files changed, 40 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 92b1dc0cc..2ba13afe8 100644 --- a/Makefile +++ b/Makefile @@ -213,6 +213,10 @@ FLAGS+=-std=c++20 -UTR1 -Ischema -Iengine -Imodel -Icertify/include -IRESTServic ifeq ($(CPLUSPLUS),clang++) # note some of these flags are disabling warnings that are invalid in some circumstances FLAGS+=-Wno-unused-command-line-argument -Wno-unknown-warning-option -Wno-defaulted-function-deleted -Wno-uninitialized +endif + +ifdef OPENMP +FLAGS+=-fopenmp LIBS+=-fopenmp endif diff --git a/engine/evalGodley.cc b/engine/evalGodley.cc index 3a17aa772..0d2fe04e0 100644 --- a/engine/evalGodley.cc +++ b/engine/evalGodley.cc @@ -100,7 +100,7 @@ namespace minsky #ifdef _OPENMP size_t sidxSize=sidx.size(); -#pragma omp parallel for if(initIdxSize>20) +#pragma omp parallel for if(sidxSize>20) for (size_t i=0; i chain; TensorPtr arg; mutable Timestamp m_timestamp; - bool redoCalc=false; CLASSDESC_ACCESS(Ravel); public: @@ -1538,22 +1537,22 @@ namespace minsky do { if (!chain.empty()) - v=(*chain.back())[i]; - - auto chainBack=chain.back(); // stash a reference to the current chain, in case it gets updated below - v=(*chainBack)[i]; + { + v=(*chain.back())[i]; #ifdef _OPENMP +#pragma omp barrier #pragma omp single copyprivate(redoCalc) #endif - { - redoCalc=m_timestamptimestamp(); - if (redoCalc) - { // update hypercube if argument has changed - const_cast(ravel).populateHypercube(arg->hypercube()); - chain=ravel::createRavelChain(ravel.getState(), arg); - m_timestamp=Timestamp::clock::now(); + { + redoCalc=m_timestamptimestamp(); + if (redoCalc) + { // update hypercube if argument has changed + const_cast(ravel).populateHypercube(arg->hypercube()); + chain=ravel::createRavelChain(ravel.getState(), arg); + m_timestamp=Timestamp::clock::now(); + } } - } + } } while (redoCalc); return v; diff --git a/engine/rungeKutta.cc b/engine/rungeKutta.cc index 56ffb17c1..acd3488d8 100644 --- a/engine/rungeKutta.cc +++ b/engine/rungeKutta.cc @@ -252,26 +252,29 @@ namespace minsky // firstly evaluate the flow variables. Initialise to flowVars so // that no input vars are correctly initialised auto flow(flowVars); - auto eqSize=equations.size(); - string threadErrMsg; -#ifdef _OPENMP - //#pragma omp parallel for if(eqSize>20) + + for (size_t i=0; ieval(flow.data(), flow.size(), vars); // TODO - equations are ordered, so we can't naively parallelise this loop. -#endif - for (size_t i=0; ieval(flow.data(), flow.size(), vars); - } - catch (std::exception& e) - { -#ifdef _OPENMP -#pragma omp critical -#endif - threadErrMsg=e.what(); - } - if (!threadErrMsg.empty()) - throw runtime_error(threadErrMsg); +// auto eqSize=equations.size(); +// string threadErrMsg; +//#ifdef _OPENMP +// //#pragma omp parallel for if(eqSize>20) +//#endif +// for (size_t i=0; ieval(flow.data(), flow.size(), vars); +// } +// catch (const std::exception& e) +// { +//#ifdef _OPENMP +//#pragma omp critical +//#endif +// threadErrMsg=e.what(); +// } +// if (!threadErrMsg.empty()) +// throw runtime_error(threadErrMsg); // then create the result using the Godley table auto stvSize=stockVars.size(); diff --git a/test/Makefile b/test/Makefile index 6b7cbc13e..89d57b475 100755 --- a/test/Makefile +++ b/test/Makefile @@ -28,6 +28,7 @@ endif ifdef OPENMP FLAGS+=-fopenmp +LIBS+=-fopenmp endif # RSVG dependencies calculated here