SwarmSolver: cache cell lookups and individual fitness
Two changes that cut the cost of the search, with the same result. getCell resolved a cell from scratch on every read and write: fetch the sheets, pick the sheet, query interfaces, then ask for the cell. The search touches the same few cells many thousands of times. Keep the resolved cells in a small cache keyed by sheet, column and row, cleared at the start of each solve. Differential Evolution recomputed the fitness of the original candidate on every comparison, and each fitness writes every variable into the document and reads the objective back. Store the fitness on the individual when it is created, then reuse the stored value. This roughly halves the number of fitness evaluations per generation. On a four variable integer model the whole run dropped from about 16 to about 11 seconds, most of the remainder being fixed start up cost. Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk> Change-Id: I96ea8c324c194dc217199f0b5318782937d43b92 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4057 Tested-by: Jenkins CPCI <releng@collaboraoffice.com> Reviewed-by: Miklos Vajna <vmiklos@collabora.com> (cherry picked from commit 7c97a43dc1b0127da468dd92ac0e7823e8788086) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208442 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
committed by
Xisco Fauli
parent
f4c5255cd6
commit
acd586803b
@@ -17,6 +17,10 @@
|
||||
struct Individual
|
||||
{
|
||||
std::vector<double> mVariables;
|
||||
// Fitness of mVariables, kept so it does not have to be recomputed. Each
|
||||
// recompute writes every variable into the document and reads the
|
||||
// objective back, which is by far the most expensive part of the search.
|
||||
double mFitness = std::numeric_limits<double>::lowest();
|
||||
};
|
||||
|
||||
template <typename DataProvider> class DifferentialEvolutionAlgorithm
|
||||
@@ -79,6 +83,7 @@ public:
|
||||
maPopulation.emplace_back();
|
||||
Individual& rIndividual = maPopulation.back();
|
||||
mrDataProvider.initializeVariables(rIndividual.mVariables, maGenerator);
|
||||
rIndividual.mFitness = mrDataProvider.calculateFitness(rIndividual.mVariables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,9 +140,12 @@ public:
|
||||
|
||||
double fCandidateFitness = mrDataProvider.calculateFitness(aCandidate.mVariables);
|
||||
|
||||
// see if is better than original, if so replace
|
||||
if (fCandidateFitness > mrDataProvider.calculateFitness(rOriginal.mVariables))
|
||||
// Replace the original if the candidate is better. The original's
|
||||
// fitness was computed when it was created, so reuse the stored
|
||||
// value here.
|
||||
if (fCandidateFitness > rOriginal.mFitness)
|
||||
{
|
||||
aCandidate.mFitness = fCandidateFitness;
|
||||
maPopulation[x] = std::move(aCandidate);
|
||||
|
||||
if (fCandidateFitness > mfBestFitness)
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
#include <limits>
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <o3tl/hash_combine.hxx>
|
||||
|
||||
#include <unotools/resmgr.hxx>
|
||||
#include <comphelper/servicehelper.hxx>
|
||||
@@ -91,6 +94,31 @@ struct Bound
|
||||
}
|
||||
};
|
||||
|
||||
// A cell position used as a cache key.
|
||||
struct CellKey
|
||||
{
|
||||
sal_Int32 nSheet;
|
||||
sal_Int32 nColumn;
|
||||
sal_Int32 nRow;
|
||||
|
||||
bool operator==(const CellKey& rOther) const
|
||||
{
|
||||
return nSheet == rOther.nSheet && nColumn == rOther.nColumn && nRow == rOther.nRow;
|
||||
}
|
||||
};
|
||||
|
||||
struct CellKeyHash
|
||||
{
|
||||
size_t operator()(const CellKey& rKey) const
|
||||
{
|
||||
size_t nSeed = 0;
|
||||
o3tl::hash_combine(nSeed, rKey.nSheet);
|
||||
o3tl::hash_combine(nSeed, rKey.nColumn);
|
||||
o3tl::hash_combine(nSeed, rKey.nRow);
|
||||
return nSeed;
|
||||
}
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_NONNEGATIVE,
|
||||
@@ -133,6 +161,11 @@ private:
|
||||
std::vector<Bound> maBounds;
|
||||
std::vector<sheet::SolverConstraint> maNonBoundedConstraints;
|
||||
|
||||
// Resolved cells, keyed by sheet, column and row. The search reads and
|
||||
// writes the same handful of cells many thousands of times, so resolving
|
||||
// each one only once is a large saving.
|
||||
std::unordered_map<CellKey, uno::Reference<table::XCell>, CellKeyHash> maCellCache;
|
||||
|
||||
private:
|
||||
static OUString getResourceString(TranslateId aId);
|
||||
|
||||
@@ -294,10 +327,17 @@ OUString SwarmSolver::getResourceString(TranslateId aId)
|
||||
|
||||
uno::Reference<table::XCell> SwarmSolver::getCell(const table::CellAddress& rPosition)
|
||||
{
|
||||
CellKey aKey{ rPosition.Sheet, rPosition.Column, rPosition.Row };
|
||||
auto aFound = maCellCache.find(aKey);
|
||||
if (aFound != maCellCache.end())
|
||||
return aFound->second;
|
||||
|
||||
uno::Reference<container::XIndexAccess> xSheets(mxDocument->getSheets(), uno::UNO_QUERY);
|
||||
uno::Reference<sheet::XSpreadsheet> xSheet(xSheets->getByIndex(rPosition.Sheet),
|
||||
uno::UNO_QUERY);
|
||||
return xSheet->getCellByPosition(rPosition.Column, rPosition.Row);
|
||||
uno::Reference<table::XCell> xCell = xSheet->getCellByPosition(rPosition.Column, rPosition.Row);
|
||||
maCellCache.emplace(aKey, xCell);
|
||||
return xCell;
|
||||
}
|
||||
|
||||
// Build a document cell address from the API address, throwing if its sheet is
|
||||
@@ -584,6 +624,7 @@ void SAL_CALL SwarmSolver::solve()
|
||||
// are only ever appended.
|
||||
maBounds.assign(maVariables.getLength(), Bound());
|
||||
maNonBoundedConstraints.clear();
|
||||
maCellCache.clear();
|
||||
|
||||
xModel->lockControllers();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user