-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DEV] Refactoring parallelization computation and remove duplication from solver #1017
Conversation
src/tests/src/libs/antares/study/parallel-years/test-parallel-years-calculator.cpp
Outdated
Show resolved
Hide resolved
src/tests/src/libs/antares/study/parallel-years/test-parallel-years-calculator.cpp
Outdated
Show resolved
Hide resolved
a9422c6
to
8b7b5a5
Compare
Kudos, SonarCloud Quality Gate passed! |
src/tests/src/libs/antares/study/parallel-years/test-parallel-years-calculator.cpp
Outdated
Show resolved
Hide resolved
src/tests/src/libs/antares/study/parallel-years/test-parallel-years-calculator.cpp
Outdated
Show resolved
Hide resolved
src/tests/src/libs/antares/study/parallel-years/test-parallel-years-calculator.cpp
Outdated
Show resolved
Hide resolved
@@ -930,7 +930,7 @@ void AdvancedParameters::onSelectNumberOfCoresLevel(Data::NumberOfCoresMode ncMo | |||
{ | |||
study->parameters.nbCores.ncMode = ncMode; | |||
// Force refresh for study->nbYearsParallelRaw | |||
study->getNumberOfCores(false, 1 /* ignored */); | |||
study->getNumberOfCores(false, false, 1 /* ignored */); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question : why do we suppose that the getNumberOfCores's second argument (which is new here) is necessarily false ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know. Honestly it feels like the lesser issue of "getNumberOfCores"
- It is in the study
- It's called "get" but doesn't return anything
- Last parameter is ignored if the first one is false
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do agree.
See review of src/libs/antares/study/study.cpp (#1017 (comment)) for my opinion on this
@@ -320,7 +320,7 @@ class JobSaveStudy final : public Toolbox::Jobs::Job | |||
|
|||
// Updating the number of logical cores to use when saving the study | |||
// so that the run window is up to date. | |||
study->getNumberOfCores(false, 0); | |||
study->getNumberOfCores(false, false, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question as in advanced.cpp's review.
8b7b5a5
to
6e9ae1c
Compare
2bf23a7
to
813e2c0
Compare
Kudos, SonarCloud Quality Gate passed! |
src/libs/antares/study/study.cpp
Outdated
|
||
SetsOfParallelYearCalculator setsBuilder(forceParallel, | ||
enableParallel, | ||
nbYearsParallelForced, Yuni::System::CPU::Count(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move Yuni::System::CPU::Count()
to next line, for more readability
uint forcedNbOfParallelYears_; | ||
uint number_of_cores_; | ||
bool thermalTSRefresh_; | ||
Parameters& p; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p should be renamed parameters_
// Un lot d'année à exécuter en parallèle. | ||
// En fonction d'une éventuelle play-list, certaines seront jouées et d'autres non. | ||
|
||
unsigned int setsSizes = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need setsSizes, we already have nbPerformedYears as a data member in this struct.
It contains exactly the same information and has a better name.
Besides, the use of this data member in class SetsOfParallelYearCalculator is a regression compared to the previous version of the parallel years orchectrator : the previous counterpart of setsSizes was the size of a set, containing also years not performed (disabled by the playlist).
A question is : what should we use instead of setsSizes ?
Should we use the set's size or the set->nbPerformedYears ? (Especially if we deal with the hydro hot start, see function allSetsParallelYearsHaveSameSize)
uint number_of_cores_; | ||
bool thermalTSRefresh_; | ||
Parameters& p; | ||
uint nbYearsReallyPerformed{0}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nbYearsReallyPerformed :
To be strict, this data should not belong to class SetsOfParallelYearCalculator.
The job of this class is to define the years that will be run at the same time (each in a different thread).
This class does not mean counting the performed years over the whole simulation.
But this class currently takes advantages of its own treatments to compute this number.
By the way, this number is already computed in other contexts :
- src/libs/antares/benchmarking/info_collectors.cpp : see function performedYearsCountToFileContent(...)
- parameters.cpp : see function prepareForSimulation(...), especially look for local variable effectiveNbYears
We should remove nbYearsReallyPerformed from the class and compute it somewhere else.
For now, I can't see another location to hold and compute it than the parameters.cpp, because it already contains the data member yearsFilter (see this data).
else | ||
buildNewSet = false; | ||
|
||
// End of loop over years |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment :
// End of loop over years
should be moved to the same line we close the bracket of the loop
|
||
} | ||
|
||
void SetsOfParallelYearCalculator::buildSetsOfParallelYears() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could make this algorithm drier, and reject some data member assignement in a further method.
For instance, set->nbYears
can be computed later, as well as set->yearFailed
or
set->isFirstPerformedYearOfASet
(not exhaustive list).
It would clarify this algorithm.
|
||
SetsOfParallelYearCalculator builder(forceParallel, | ||
enableParallel, | ||
maxNbYearsInParallel, 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The number of available cores on the machine is quite hidden : here it's 1.
I would recommend to declare a new variable uint machine_nb_cores = 1;
and pass it to the constructor below.
params.resetYearsWeigth(); | ||
params.setYearWeight(0,4); | ||
params.setYearWeight(1,10); | ||
params.setYearWeight(2,3); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to set some years' weight to test the parallel years orchestrator (or builder) ?
It seems to me that year weights and parallel years orchestrator are not related.
** \warning TODO: REMOVE THIS METHOD ONCE THE TSGEN HAS BEEN DECOUPLED FROM SOLVER | ||
*/ | ||
|
||
bool TempAreaListHolder::checkThermalTSGeneration(YString folder_) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thermal clusters are special regarding TS refresh.
As for any other production mean, clusters have their own global parameter to enable time series refreshment. But user can ask the refresh to any particular cluster, that is not to obey to the global parameter.
Here we check if any cluster has to refresh its TS, and we return true in that case, false otherwise.
Question : why get this information form input files, and not from memory ?
Probably because when we build the parallel years orchestrator, this information on clusters is not loaded yet.
In that case, why not build the parallel years orchestrator after the information on clusters is loaded ?
About the file run.cpp, which has not been touched in the PR, but should be commented as well. // Needed For RAM estimation
uint& maxNbCores = Data::Study::Current::Get()->maxNbYearsInParallel;
uint maxNbCoresParallelMode = Data::Study::Current::Get()->maxNbYearsInParallel_save;
// Needed for run window's simulation cores field
uint& minNbCores = Data::Study::Current::Get()->minNbYearsInParallel;
uint minNbCoresParallelMode = Data::Study::Current::Get()->minNbYearsInParallel_save;
if (featuresAlias[pFeatureIndex] == Solver::parallel)
{
maxNbCores = maxNbCoresParallelMode; // For RAM estimation
minNbCores = minNbCoresParallelMode; // For run window's simulation cores field
...
}
else
{
maxNbCores = 1; // For RAM estimation
minNbCores = 1; // Not needed (because simulation cores field is hidden here)
....
} This snippet sets the variables minNbCores and maxNbCores, which are later used in the window to print a line of information like : Simulation cores : 4 (smallest batch size : 1) So back to the snippet : minNbCores is set to 1 and maxNbCores is set to 4. uint minNbCores = 0;
uint maxNbCores = 0;
if (featuresAlias[pFeatureIndex] == Solver::parallel)
{
maxNbCores = Data::Study::Current::Get()->maxNbYearsInParallel; // For RAM estimation
minNbCores = Data::Study::Current::Get()->minNbYearsInParallel; // For run window's simulation cores field
...
}
else
{
// No need to set minNbCores or maxNbCores here
....
} |
maxNbYearsInParallel = setsBuilder.getForcedNbOfParallelYears(); | ||
parameters.allSetsHaveSameSize = setsBuilder.allSetsParallelYearsHaveSameSize(); | ||
setsOfParallelYears = setsBuilder.getSetsOfParallelYears(); | ||
pNbYearsReallyPerformed = setsBuilder.getNbYearsReallyPerformed(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About :
pNbYearsReallyPerformed = setsBuilder.getNbYearsReallyPerformed();
If we agree that computing the number of performed years in the study is not part of the responsibility of the parallel years orchestrator, this line should be removed.
parameters); | ||
// For GUI | ||
minNbYearsInParallel_save = setsBuilder.getMinNbParallelYearsForGUI(); | ||
maxNbYearsInParallel_save = setsBuilder.getForcedNbOfParallelYears(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About :
maxNbYearsInParallel_save = setsBuilder.getForcedNbOfParallelYears();
The data member maxNbYearsInParallel_save is useless, as it is the same as maxNbYearsInParallel, except that it is used by the GUI to print some information in the run simulation window.
We should remove this line.
thermalTSRefresh, | ||
parameters); | ||
// For GUI | ||
minNbYearsInParallel_save = setsBuilder.getMinNbParallelYearsForGUI(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to name minNbYearsInParallel_save as it is.
minNbYearsInParallel is enough.
Note this value is only read by GUI to print some information about the minimum size there is over all set (of parallel years)
} | ||
} | ||
} // End if hot start | ||
void Study::getNumberOfCores(const bool forceParallel, const bool enableParallel, const uint nbYearsParallelForced) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function getNumberOfCores(...) updates some Study data members that are used elsewhere either in the GUI or in the solver to run the simulation properly.
Ideally, we should have an instance of SetsOfParallelYearCalculator that would contain and would be able to give all information needed about parallel sets to any part of the code that requests them.
I can't see anywhere else in the code to put this instance than as a Study's data member.
Indeed, as a singleton, the study can be reached from anywhere in the code, and deliver information.
But if some part of code needs information about parallel years, it should retrieve them through the instance study->parallelYearCalculator
, and not directly from the study itself.
Furthermore, instead of having getNumberOfCores(...), study->parallelYearCalculator
should have a different interface :
- We could have at least 2 updates(...) methods :
- one for a default update() : calculating the sets of parallel years with the characteristics of the study
- one update(....) in case we have external information to compute the sets of parallel years (I mean information from the solver command line option to force the number of cores used)
- some get functions to give information to any part of code requiring it.
- Any other methods should be private, especially those that are used to compute the sets of parallel years
With this, we would encapsulate the information about parallel years, and furthermore we would avoid some unclean lines that can be found in the code like :
study->getNumberOfCores(false, false, 0);
Instead, we would have :
study->parallelYearCalculator->update()
which is cleaner I guess.
} | ||
|
||
|
||
bool SetsOfParallelYearCalculator::allSetsParallelYearsHaveSameSize() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About allSetsParallelYearsHaveSameSize() :
This function answer the question : does all sets contains the same number of performed years ?
For now It is only called in case the study enables the hydro hot start.
And that is reflected in the body of this function.
Well it should not : the function should stick to its name and answer the simple question without knowing if it's called in the context of hydro hot start or not.
So the body of this function should be :
bool SetsOfParallelYearCalculator::allSetsParallelYearsHaveSameSize()
{
uint currentSetSize = (uint)setsOfParallelYears[0].setsSizes;
return all_of(setsOfParallelYears.begin(), setsOfParallelYears.end(),
[currentSetSize](const setOfParallelYears& v){ return v.setsSizes == currentSetSize; } );
}
and that's it.
Another benefit of this simplification is that p.initialReservoirLevels.iniLevels
is not required anymore in class SetsOfParallelYearCalculator, so we loose a dependency on a study parameter.
|
||
bool allSetsParallelYearsHaveSameSize(); | ||
|
||
[[nodiscard]] uint getForcedNbOfParallelYears() const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As already explained in review of parallel-years.cpp, we return actualMaxNbOfPerformedYearsInASet_, and rename this getter accordingly
} | ||
|
||
|
||
void SetsOfParallelYearCalculator::limitNbOfParallelYearsbyMinRefreshSpan() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About functions limitNbOfParallelYearsbyMinRefreshSpan and isRefreshNeededForCurrentYear :
- there are some duplication code in both functions
- they imply a dependency of class SetsOfParallelYearCalculator to study->parameter, making this class more difficult to test in isolation that it should.
Just a first think (to be digged)
So we could have a parent class refreshSpanHandler :
class refreshSpanHandler
{
getSpan() = 0;
doWeRefresh(int year) = 0;
}
Then derive it for all kinds of production or load.
Maybe build them (create instance of them) outside the class SetsOfParallelYearCalculator.
Add them to the parallel years calculator, that is put them in a vector of parent class refreshSpanHandler, let's say refreshSpanHandlerList)
Then we could traverse this vector with an std::algorithm.
example :
refreshing = std::any_of(refreshSpanHandlerList.begin(), refreshSpanHandlerList.end(), [](const refreshSpanHandler h) { return h->doWeRefresh(y)});
Note we would avoid class SetsOfParallelYearCalculator a dependency to study.parameter in that way.
smallest batch size is incorrect as well (always 1) |
This PR still has some interest, its comments / remarks should be read, and the subject should be taken care of. |
src/solver/simulation/solver.hxx's method buildSetsOfParallelYears and Study's getNumberOfCores had code duplication.
Parallelization computation is now managed by a separate class: SetsOfParallelYearCalculator
Although, due to dependencies on loading and calculations performed later at the study class, SetsOfParallelYearCalculator's results are still managed by the study and passed to the solver through study's member.