diff options
author | Dennis Francis <dennis.francis@collabora.com> | 2019-01-15 21:34:46 +0530 |
---|---|---|
committer | Dennis Francis <dennis.francis@collabora.com> | 2019-02-05 13:56:22 +0100 |
commit | 3346947b7e102384dfc6cd98dbf7da81936f8fd6 (patch) | |
tree | 01db16dcb48779866e2f611a57e6836c4e9111c7 | |
parent | ba1e745b3d022856080c25167226e8a9eeadc911 (diff) |
Allow computing spans of formula-groups
Includes unit tests for correctness of the new functionality.
Change-Id: I35f7449006d973de006a756664ae468b9a0dcb31
Reviewed-on: https://gerrit.libreoffice.org/66841
Tested-by: Jenkins
Reviewed-by: Dennis Francis <dennis.francis@collabora.com>
-rw-r--r-- | sc/inc/column.hxx | 2 | ||||
-rw-r--r-- | sc/inc/document.hxx | 14 | ||||
-rw-r--r-- | sc/inc/formulacell.hxx | 14 | ||||
-rw-r--r-- | sc/inc/table.hxx | 2 | ||||
-rw-r--r-- | sc/qa/unit/parallelism.cxx | 134 | ||||
-rw-r--r-- | sc/source/core/data/column2.cxx | 66 | ||||
-rw-r--r-- | sc/source/core/data/column4.cxx | 200 | ||||
-rw-r--r-- | sc/source/core/data/document10.cxx | 10 | ||||
-rw-r--r-- | sc/source/core/data/formulacell.cxx | 116 | ||||
-rw-r--r-- | sc/source/core/data/table7.cxx | 13 | ||||
-rw-r--r-- | sc/source/ui/view/output.cxx | 53 |
11 files changed, 472 insertions, 152 deletions
diff --git a/sc/inc/column.hxx b/sc/inc/column.hxx index d21918648678..a885d0240a2c 100644 --- a/sc/inc/column.hxx +++ b/sc/inc/column.hxx @@ -678,7 +678,7 @@ public: std::unique_ptr<sc::ColumnIterator> GetColumnIterator( SCROW nRow1, SCROW nRow2 ) const; - void EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2 ); + bool EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2, bool bSkipRunning = false ); void StoreToCache(SvStream& rStrm) const; void RestoreFromCache(SvStream& rStrm); diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx index bed8003de16c..ddf92b904a8a 100644 --- a/sc/inc/document.hxx +++ b/sc/inc/document.hxx @@ -2430,12 +2430,18 @@ public: /** * Make sure all of the formula cells in the specified range have been * fully calculated. This method only re-calculates those formula cells - * that have been flagged dirty. + * that have been flagged dirty. In case of formula-groups, this calculates + * only the dirty subspans along with the dependents in the same way + * recursively. * - * @param rRange range in which to potentially calculate the formula - * cells. + * @param rRange range in which to potentially calculate the formula + * cells. + * @param bSkipRunning flag to skip evaluation of formula-cells that are + * marked as already being evaluated. + * @return true if at least one formula-cell in the specified range was dirty + * else returns false. */ - void EnsureFormulaCellResults( const ScRange& rRange ); + SC_DLLPUBLIC bool EnsureFormulaCellResults( const ScRange& rRange, bool bSkipRunning = false ); SvtBroadcaster* GetBroadcaster( const ScAddress& rPos ); const SvtBroadcaster* GetBroadcaster( const ScAddress& rPos ) const; diff --git a/sc/inc/formulacell.hxx b/sc/inc/formulacell.hxx index b6959c74d6cd..ec7c61171ae7 100644 --- a/sc/inc/formulacell.hxx +++ b/sc/inc/formulacell.hxx @@ -143,10 +143,12 @@ private: ScFormulaCell( const ScFormulaCell& ) = delete; - bool CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow = false); + bool CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow, + SCROW nStartOffset, SCROW nEndOffset); bool InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope& aScope, bool& bDependencyComputed, - bool& bDependencyCheckFailed); + bool& bDependencyCheckFailed, + SCROW nStartOffset, SCROW nEndOffset); bool InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope& aScope, bool& bDependencyComputed, bool& bDependencyCheckFailed); @@ -248,7 +250,7 @@ public: void CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress ); // compile temporary string tokens void CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening ); bool MarkUsedExternalReferences(); - void Interpret(); + bool Interpret(SCROW nStartOffset = -1, SCROW nEndOffset = -1); bool IsIterCell() const { return bIsIterCell; } sal_uInt16 GetSeenInIteration() const { return nSeenInIteration; } @@ -433,13 +435,15 @@ public: return (pDocument->GetAutoCalc() || (cMatrixFlag != ScMatrixMode::NONE)); } - void MaybeInterpret() + bool MaybeInterpret() { if (NeedsInterpret()) { assert(!pDocument->IsThreadedGroupCalcInProgress()); Interpret(); + return true; } + return false; } /** @@ -451,7 +455,7 @@ public: CompareState CompareByTokenArray( const ScFormulaCell& rOther ) const; - bool InterpretFormulaGroup(); + bool InterpretFormulaGroup(SCROW nStartOffset = -1, SCROW nEndOffset = -1); // nOnlyNames may be one or more of SC_LISTENING_NAMES_* void StartListeningTo( ScDocument* pDoc ); diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx index 38ea573ba120..ee217ba50c82 100644 --- a/sc/inc/table.hxx +++ b/sc/inc/table.hxx @@ -1050,7 +1050,7 @@ public: std::unique_ptr<sc::ColumnIterator> GetColumnIterator( SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const; - void EnsureFormulaCellResults( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2 ); + bool EnsureFormulaCellResults( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2, bool bSkipRunning = false ); void ConvertFormulaToValue( sc::EndListeningContext& rCxt, diff --git a/sc/qa/unit/parallelism.cxx b/sc/qa/unit/parallelism.cxx index 670e7242645d..058cbfad9728 100644 --- a/sc/qa/unit/parallelism.cxx +++ b/sc/qa/unit/parallelism.cxx @@ -38,6 +38,8 @@ public: void testFGCycleWithPlainFormulaCell1(); void testFGCycleWithPlainFormulaCell2(); void testMultipleFGColumn(); + void testFormulaGroupSpanEval(); + void testFormulaGroupSpanEvalNonGroup(); CPPUNIT_TEST_SUITE(ScParallelismTest); CPPUNIT_TEST(testSUMIFS); @@ -49,6 +51,8 @@ public: CPPUNIT_TEST(testFGCycleWithPlainFormulaCell1); CPPUNIT_TEST(testFGCycleWithPlainFormulaCell2); CPPUNIT_TEST(testMultipleFGColumn); + CPPUNIT_TEST(testFormulaGroupSpanEval); + CPPUNIT_TEST(testFormulaGroupSpanEvalNonGroup); CPPUNIT_TEST_SUITE_END(); private: @@ -510,11 +514,11 @@ void ScParallelismTest::testMultipleFGColumn() sc::AutoCalcSwitch aACSwitch(*m_pDoc, false); m_pDoc->InsertTab(0, "1"); - size_t nNumRowsInBlock = 200; - size_t nNumFG = 50; - size_t nNumRowsInRef = nNumRowsInBlock*2; - size_t nColAFGLen = 2*nNumRowsInBlock*nNumFG - nNumRowsInRef + 1; - size_t nColAStartOffset = nNumRowsInBlock/2; + constexpr size_t nNumRowsInBlock = 200; + constexpr size_t nNumFG = 50; + constexpr size_t nNumRowsInRef = nNumRowsInBlock*2; + constexpr size_t nColAFGLen = 2*nNumRowsInBlock*nNumFG - nNumRowsInRef + 1; + constexpr size_t nColAStartOffset = nNumRowsInBlock/2; lcl_setupMultipleFGColumn(m_pDoc, nNumRowsInBlock, nNumFG, nColAStartOffset); m_xDocShell->DoHardRecalc(); @@ -535,6 +539,126 @@ void ScParallelismTest::testMultipleFGColumn() m_pDoc->DeleteTab(0); } +void ScParallelismTest::testFormulaGroupSpanEval() +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, false); + m_pDoc->InsertTab(0, "1"); + + constexpr size_t nFGLen = 2048; + OUString aFormula; + + for (size_t nRow = 0; nRow < nFGLen; ++nRow) + { + aFormula = "=$C" + OUString::number(nRow+1) + " + 0"; + m_pDoc->SetFormula(ScAddress(1, nRow, 0), aFormula, + formula::FormulaGrammar::GRAM_NATIVE_UI); + aFormula = "=SUM($B" + OUString::number(nRow+1) + ":$B" + OUString::number(nRow+2) + ")"; + m_pDoc->SetFormula(ScAddress(0, nRow, 0), aFormula, + formula::FormulaGrammar::GRAM_NATIVE_UI); + } + + m_xDocShell->DoHardRecalc(); + + for (size_t nRow = 0; nRow < nFGLen; ++nRow) + { + m_pDoc->SetValue(2, nRow, 0, 1.0); + ScFormulaCell* pFCell = m_pDoc->GetFormulaCell(ScAddress(1, nRow, 0)); + pFCell->SetDirtyVar(); + pFCell = m_pDoc->GetFormulaCell(ScAddress(0, nRow, 0)); + pFCell->SetDirtyVar(); + } + + constexpr size_t nSpanStart = 100; + constexpr size_t nSpanLen = 1024; + constexpr size_t nSpanEnd = nSpanStart + nSpanLen - 1; + + m_pDoc->SetAutoCalc(true); + + // EnsureFormulaCellResults should only calculate the sepecified range along with the dependent spans recursively and nothing more. + // The specified range is A99:A1124, and the dependent range is B99:B1125 (since A99 = SUM(B99:B100) and A1124 = SUM(B1124:B1125) ) + bool bAnyDirty = m_pDoc->EnsureFormulaCellResults(ScRange(0, nSpanStart, 0, 0, nSpanEnd, 0)); + CPPUNIT_ASSERT(bAnyDirty); + m_pDoc->SetAutoCalc(false); + + OString aMsg; + for (size_t nRow = 0; nRow < nFGLen; ++nRow) + { + size_t nExpectedA = 0, nExpectedB = 0; + // For nRow from 100(nSpanStart) to 1123(nSpanEnd) column A must have the value of 2 and + // column B should have value 1. + + // For nRow == 1124, column A should have value 0 and column B should have value 1. + + // For all other rows both column A and B must have value 0. + if (nRow >= nSpanStart) + { + if (nRow <= nSpanEnd) + { + nExpectedA = 2; + nExpectedB = 1; + } + else if (nRow == nSpanEnd + 1) + nExpectedB = 1; + } + + aMsg = "Value at Cell A" + OString::number(nRow+1); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aMsg.getStr(), nExpectedA, static_cast<size_t>(m_pDoc->GetValue(0, nRow, 0))); + aMsg = "Value at Cell B" + OString::number(nRow+1); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aMsg.getStr(), nExpectedB, static_cast<size_t>(m_pDoc->GetValue(1, nRow, 0))); + } + + m_pDoc->DeleteTab(0); +} + +void ScParallelismTest::testFormulaGroupSpanEvalNonGroup() +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, false); + m_pDoc->InsertTab(0, "1"); + + constexpr size_t nFGLen = 2048; + OUString aFormula; + + for (size_t nRow = 0; nRow < nFGLen; ++nRow) + { + aFormula = "=$B" + OUString::number(nRow+1) + " + 0"; + m_pDoc->SetFormula(ScAddress(0, nRow, 0), aFormula, + formula::FormulaGrammar::GRAM_NATIVE_UI); + } + + m_xDocShell->DoHardRecalc(); + + constexpr size_t nNumChanges = 12; + constexpr size_t nChangeRows[nNumChanges] = {10, 11, 12, 101, 102, 103, 251, 252, 253, 503, 671, 1029}; + for (size_t nIdx = 0; nIdx < nNumChanges; ++nIdx) + { + size_t nRow = nChangeRows[nIdx]; + m_pDoc->SetValue(1, nRow, 0, 1.0); + ScFormulaCell* pFCell = m_pDoc->GetFormulaCell(ScAddress(0, nRow, 0)); + pFCell->SetDirtyVar(); + } + + m_pDoc->SetAutoCalc(true); + bool bAnyDirty = m_pDoc->EnsureFormulaCellResults(ScRange(0, 9, 0, 0, 1030, 0)); + CPPUNIT_ASSERT(bAnyDirty); + m_pDoc->SetAutoCalc(false); + + OString aMsg; + for (size_t nRow = 0, nIdx = 0; nRow < nFGLen; ++nRow) + { + size_t nExpected = 0; + if (nIdx < nNumChanges && nRow == nChangeRows[nIdx]) + { + nExpected = 1; + ++nIdx; + } + + aMsg = "Value at Cell A" + OString::number(nRow+1); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aMsg.getStr(), nExpected, static_cast<size_t>(m_pDoc->GetValue(0, nRow, 0))); + } + + m_pDoc->DeleteTab(0); +} + CPPUNIT_TEST_SUITE_REGISTRATION(ScParallelismTest); CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sc/source/core/data/column2.cxx b/sc/source/core/data/column2.cxx index 5f093d4c660e..8d2702980fba 100644 --- a/sc/source/core/data/column2.cxx +++ b/sc/source/core/data/column2.cxx @@ -2906,70 +2906,6 @@ void ScColumn::AssertNoInterpretNeeded( SCROW nRow1, SCROW nRow2 ) } #endif - -bool ScColumn::HandleRefArrayForParallelism( SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup ) -{ - if (nRow1 > nRow2) - return false; - - std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow1); - sc::CellStoreType::const_iterator it = aPos.first; - size_t nOffset = aPos.second; - SCROW nRow = nRow1; - for (;it != maCells.end() && nRow <= nRow2; ++it, nOffset = 0) - { - switch( it->type ) - { - case sc::element_type_edittext: - // These require EditEngine (in ScEditUtils::GetString()), which is probably - // too complex for use in threads. - return false; - case sc::element_type_formula: - { - size_t nRowsToRead = nRow2 - nRow + 1; - size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1 - sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data); - std::advance(itCell, nOffset); - // Loop inside the formula block. - for (size_t i = nOffset; i < nEnd; ++itCell, ++i) - { - // Check if itCell is already in path. - // If yes use a cycle guard to mark all elements of the cycle - // and return false - const ScFormulaCellGroupRef& mxGroupChild = (*itCell)->GetCellGroup(); - ScFormulaCell* pChildTopCell = mxGroupChild ? mxGroupChild->mpTopCell : *itCell; - if (pChildTopCell->GetSeenInPath()) - { - ScRecursionHelper& rRecursionHelper = GetDoc()->GetRecursionHelper(); - ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pChildTopCell); - return false; - } - - (*itCell)->MaybeInterpret(); - - // child cell's Interpret could result in calling dependency calc - // and that could detect a cycle involving mxGroup - // and do early exit in that case. - if (mxGroup->mbPartOfCycle) - { - // Set itCell as dirty as itCell may be interpreted in InterpretTail() - (*itCell)->SetDirtyVar(); - return false; - } - } - nRow += nEnd - nOffset; - break; - } - default: - // Skip this block. - nRow += it->size - nOffset; - continue; - } - } - - return true; -} - void ScColumn::SetFormulaResults( SCROW nRow, const double* pResults, size_t nLen ) { sc::CellStoreType::position_type aPos = maCells.position(nRow); @@ -3030,6 +2966,8 @@ void ScColumn::CalculateInThread( ScInterpreterContext& rContext, SCROW nRow, si continue; ScFormulaCell& rCell = **itCell; + if (!rCell.NeedsInterpret()) + continue; // Here we don't call IncInterpretLevel() and DecInterpretLevel() as this call site is // always in a threaded calculation. rCell.InterpretTail(rContext, ScFormulaCell::SCITP_NORMAL); diff --git a/sc/source/core/data/column4.cxx b/sc/source/core/data/column4.cxx index 7448ef02dbab..4e04c3cca55b 100644 --- a/sc/source/core/data/column4.cxx +++ b/sc/source/core/data/column4.cxx @@ -28,6 +28,7 @@ #include <sharedformula.hxx> #include <drwlayer.hxx> #include <compiler.hxx> +#include <recursionhelper.hxx> #include <svl/sharedstringpool.hxx> #include <sal/log.hxx> @@ -1642,20 +1643,201 @@ std::unique_ptr<sc::ColumnIterator> ScColumn::GetColumnIterator( SCROW nRow1, SC return std::make_unique<sc::ColumnIterator>(maCells, nRow1, nRow2); } -void ScColumn::EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2 ) +static bool lcl_InterpretSpan(sc::formula_block::const_iterator& rSpanIter, SCROW nStartOffset, SCROW nEndOffset, + const ScFormulaCellGroupRef& mxParentGroup, bool& bAllowThreading) { - if (!ValidRow(nRow1) || !ValidRow(nRow2) || nRow1 > nRow2) - return; + bAllowThreading = true; + ScFormulaCell* pCellStart = nullptr; + SCROW nSpanStart = -1; + SCROW nSpanEnd = -1; + sc::formula_block::const_iterator itSpanStart; + bool bAnyDirty = false; + for (SCROW nFGOffset = nStartOffset; nFGOffset <= nEndOffset; ++rSpanIter, ++nFGOffset) + { + bool bThisDirty = (*rSpanIter)->NeedsInterpret(); + if (!pCellStart && bThisDirty) + { + pCellStart = *rSpanIter; + itSpanStart = rSpanIter; + nSpanStart = nFGOffset; + bAnyDirty = true; + } - if (!HasFormulaCell(nRow1, nRow2)) - return; + if (pCellStart && (!bThisDirty || nFGOffset == nEndOffset)) + { + nSpanEnd = bThisDirty ? nFGOffset : nFGOffset - 1; + assert(nSpanStart >= nStartOffset && nSpanStart <= nSpanEnd && nSpanEnd <= nEndOffset); + + // Found a completely dirty sub span [nSpanStart, nSpanEnd] inside the required span [nStartOffset, nEndOffset] + bool bGroupInterpreted = pCellStart->Interpret(nSpanStart, nSpanEnd); - sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, - []( size_t /*nRow*/, ScFormulaCell* pCell ) + // child cell's Interpret could result in calling dependency calc + // and that could detect a cycle involving mxGroup + // and do early exit in that case. + if (mxParentGroup && mxParentGroup->mbPartOfCycle) + { + // Set pCellStart as dirty as pCellStart may be interpreted in InterpretTail() + pCellStart->SetDirtyVar(); + bAllowThreading = false; + return bAnyDirty; + } + + if (!bGroupInterpreted) + { + // Evaluate from second cell in non-grouped style (no point in trying group-interpret again). + ++itSpanStart; + for (SCROW nIdx = nSpanStart+1; nIdx <= nSpanEnd; ++nIdx, ++itSpanStart) + (*itSpanStart)->Interpret(); // We know for sure that this cell is dirty so directly call Interpret(). + } + + pCellStart = nullptr; // For next sub span start detection. + } + } + + return bAnyDirty; +} + +static void lcl_EvalDirty(sc::CellStoreType& rCells, SCROW nRow1, SCROW nRow2, ScDocument& rDoc, + const ScFormulaCellGroupRef& mxGroup, bool bThreadingDepEval, bool bSkipRunning, + bool& bIsDirty, bool& bAllowThreading) +{ + std::pair<sc::CellStoreType::const_iterator,size_t> aPos = rCells.position(nRow1); + sc::CellStoreType::const_iterator it = aPos.first; + size_t nOffset = aPos.second; + SCROW nRow = nRow1; + + bIsDirty = false; + + for (;it != rCells.end() && nRow <= nRow2; ++it, nOffset = 0) + { + switch( it->type ) { - pCell->MaybeInterpret(); + case sc::element_type_edittext: + // These require EditEngine (in ScEditUtils::GetString()), which is probably + // too complex for use in threads. + if (bThreadingDepEval) + { + bAllowThreading = false; + return; + } + break; + case sc::element_type_formula: + { + size_t nRowsToRead = nRow2 - nRow + 1; + const size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1 + sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data); + std::advance(itCell, nOffset); + + // Loop inside the formula block. + size_t nCellIdx = nOffset; + while (nCellIdx < nEnd) + { + const ScFormulaCellGroupRef& mxGroupChild = (*itCell)->GetCellGroup(); + ScFormulaCell* pChildTopCell = mxGroupChild ? mxGroupChild->mpTopCell : *itCell; + + // Check if itCell is already in path. + // If yes use a cycle guard to mark all elements of the cycle + // and return false + if (bThreadingDepEval && pChildTopCell->GetSeenInPath()) + { + ScRecursionHelper& rRecursionHelper = rDoc.GetRecursionHelper(); + ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pChildTopCell); + bAllowThreading = false; + return; + } + + if (bSkipRunning && (*itCell)->IsRunning()) + { + ++itCell; + nCellIdx += 1; + nRow += 1; + nRowsToRead -= 1; + continue; + } + + if (mxGroupChild) + { + // It is a Formula-group, evaluate the necessary parts of it (spans). + const SCROW nFGStartOffset = (*itCell)->aPos.Row() - pChildTopCell->aPos.Row(); + const SCROW nFGEndOffset = std::min(nFGStartOffset + static_cast<SCROW>(nRowsToRead) - 1, mxGroupChild->mnLength - 1); + assert(nFGEndOffset >= nFGStartOffset); + const SCROW nSpanLen = nFGEndOffset - nFGStartOffset + 1; + // The (main) span required to be evaluated is [nFGStartOffset, nFGEndOffset], but this span may contain + // non-dirty cells, so split this into sets of completely-dirty spans and try evaluate each of them in grouped-style. + + bool bAnyDirtyInSpan = lcl_InterpretSpan(itCell, nFGStartOffset, nFGEndOffset, mxGroup, bAllowThreading); + if (!bAllowThreading) + return; + // itCell will now point to cell just after the end of span [nFGStartOffset, nFGEndOffset]. + bIsDirty = bIsDirty || bAnyDirtyInSpan; + + // update the counters by nSpanLen. + // itCell already got updated. + nCellIdx += nSpanLen; + nRow += nSpanLen; + nRowsToRead -= nSpanLen; + } + else + { + // No formula-group here. + bool bDirtyFlag = (*itCell)->MaybeInterpret(); + bIsDirty = bIsDirty || bDirtyFlag; + + // child cell's Interpret could result in calling dependency calc + // and that could detect a cycle involving mxGroup + // and do early exit in that case. + if (bThreadingDepEval && mxGroup && mxGroup->mbPartOfCycle) + { + // Set itCell as dirty as itCell may be interpreted in InterpretTail() + (*itCell)->SetDirtyVar(); + bAllowThreading = false; + return; + } + + // update the counters by 1. + nCellIdx += 1; + nRow += 1; + nRowsToRead -= 1; + ++itCell; + } + } + break; + } + default: + // Skip this block. + nRow += it->size - nOffset; + continue; } - ); + } + + if (bThreadingDepEval) + bAllowThreading = true; + +} + +// Returns true if at least one FC is dirty. +bool ScColumn::EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2, bool bSkipRunning ) +{ + if (!ValidRow(nRow1) || !ValidRow(nRow2) || nRow1 > nRow2) + return false; + + if (!HasFormulaCell(nRow1, nRow2)) + return false; + + bool bAnyDirty = false, bTmp = false; + lcl_EvalDirty(maCells, nRow1, nRow2, *GetDoc(), nullptr, false, bSkipRunning, bAnyDirty, bTmp); + return bAnyDirty; +} + +bool ScColumn::HandleRefArrayForParallelism( SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup ) +{ + if (nRow1 > nRow2) + return false; + + bool bAllowThreading = true, bTmp = false; + lcl_EvalDirty(maCells, nRow1, nRow2, *GetDoc(), mxGroup, true, false, bTmp, bAllowThreading); + + return bAllowThreading; } namespace { diff --git a/sc/source/core/data/document10.cxx b/sc/source/core/data/document10.cxx index c495ff133db6..938d096c071d 100644 --- a/sc/source/core/data/document10.cxx +++ b/sc/source/core/data/document10.cxx @@ -944,17 +944,21 @@ std::unique_ptr<sc::ColumnIterator> ScDocument::GetColumnIterator( SCTAB nTab, S return pTab->GetColumnIterator(nCol, nRow1, nRow2); } -void ScDocument::EnsureFormulaCellResults( const ScRange& rRange ) +bool ScDocument::EnsureFormulaCellResults( const ScRange& rRange, bool bSkipRunning ) { + bool bAnyDirty = false; for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) { ScTable* pTab = FetchTable(nTab); if (!pTab) continue; - pTab->EnsureFormulaCellResults( - rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()); + bool bRet = pTab->EnsureFormulaCellResults( + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), bSkipRunning); + bAnyDirty = bAnyDirty || bRet; } + + return bAnyDirty; } sc::ExternalDataMapper& ScDocument::GetExternalDataMapper() diff --git a/sc/source/core/data/formulacell.cxx b/sc/source/core/data/formulacell.cxx index eac866003765..94d6ad81da8b 100644 --- a/sc/source/core/data/formulacell.cxx +++ b/sc/source/core/data/formulacell.cxx @@ -1509,9 +1509,10 @@ struct TemporaryCellGroupMaker } // namespace -void ScFormulaCell::Interpret() +bool ScFormulaCell::Interpret(SCROW nStartOffset, SCROW nEndOffset) { ScRecursionHelper& rRecursionHelper = pDocument->GetRecursionHelper(); + bool bGroupInterpreted = false; static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType(); TemporaryCellGroupMaker cellGroupMaker( this, forceType != ForceCalculationNone && forceType != ForceCalculationCore ); @@ -1524,7 +1525,7 @@ void ScFormulaCell::Interpret() aResult.SetResultError( FormulaError::CircularReference ); // This will mark all elements in the cycle as parts-of-cycle. ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pTopCell); - return; + return bGroupInterpreted; } #if DEBUG_CALCULATION @@ -1539,20 +1540,20 @@ void ScFormulaCell::Interpret() #endif if (!IsDirtyOrInTableOpDirty() || rRecursionHelper.IsInReturn()) - return; // no double/triple processing + return bGroupInterpreted; // no double/triple processing //FIXME: // If the call originates from a Reschedule in DdeLink update, leave dirty // Better: Do a Dde Link Update without Reschedule or do it completely asynchronously! if ( pDocument->IsInDdeLinkUpdate() ) - return; + return bGroupInterpreted; if (bRunning) { if (!pDocument->GetDocOptions().IsIter()) { aResult.SetResultError( FormulaError::CircularReference ); - return; + return bGroupInterpreted; } if (aResult.GetResultError() == FormulaError::CircularReference) @@ -1563,14 +1564,14 @@ void ScFormulaCell::Interpret() !rRecursionHelper.GetRecursionInIterationStack().top()->bIsIterCell) rRecursionHelper.SetInIterationReturn( true); - return; + return bGroupInterpreted; } // no multiple interprets for GetErrCode, IsValue, GetValue and // different entry point recursions. Would also lead to premature // convergence in iterations. if (rRecursionHelper.GetIteration() && nSeenInIteration == rRecursionHelper.GetIteration()) - return ; + return bGroupInterpreted; bool bOldRunning = bRunning; if (rRecursionHelper.GetRecursionCount() > MAXRECURSION) @@ -1586,7 +1587,7 @@ void ScFormulaCell::Interpret() aDC.enterGroup(); #endif bool bPartOfCycleBefore = mxGroup && mxGroup->mbPartOfCycle; - bool bGroupInterpreted = InterpretFormulaGroup(); + bGroupInterpreted = InterpretFormulaGroup(nStartOffset, nEndOffset); bool bPartOfCycleAfter = mxGroup && mxGroup->mbPartOfCycle; #if DEBUG_CALCULATION @@ -1600,7 +1601,7 @@ void ScFormulaCell::Interpret() if (!bPartOfCycleBefore && bPartOfCycleAfter && rRecursionHelper.AnyParentFGInCycle()) { pDocument->DecInterpretLevel(); - return; + return bGroupInterpreted; } ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this); @@ -1846,6 +1847,8 @@ void ScFormulaCell::Interpret() else aDC.storeResult( aResult.GetString()); #endif + + return bGroupInterpreted; } void ScFormulaCell::InterpretTail( ScInterpreterContext& rContext, ScInterpretTailParameter eTailParam ) @@ -4232,9 +4235,12 @@ struct ScDependantsCalculator const SCROW mnLen; const ScAddress& mrPos; const bool mFromFirstRow; + const SCROW mnStartOffset; + const SCROW mnEndOffset; + const SCROW mnSpanLen; ScDependantsCalculator(ScDocument& rDoc, const ScTokenArray& rCode, const ScFormulaCell& rCell, - const ScAddress& rPos, bool fromFirstRow) : + const ScAddress& rPos, bool fromFirstRow, SCROW nStartOffset, SCROW nEndOffset) : mrDoc(rDoc), mrCode(rCode), mxGroup(rCell.GetCellGroup()), @@ -4243,7 +4249,10 @@ struct ScDependantsCalculator // ScColumn::FetchVectorRefArray() always fetches data from row 0, even if the data is used // only from further rows. This data fetching could also lead to Interpret() calls, so // in OpenCL mode the formula in practice depends on those cells too. - mFromFirstRow(fromFirstRow) + mFromFirstRow(fromFirstRow), + mnStartOffset(nStartOffset), + mnEndOffset(nEndOffset), + mnSpanLen(nEndOffset - nStartOffset + 1) { } @@ -4369,7 +4378,10 @@ struct ScDependantsCalculator // Partially from ScGroupTokenConverter::convert in sc/source/core/data/grouptokenconverter.cxx ScRangeList aRangeList; + + // Self references should be checked by considering the entire formula-group not just the provided span. bool bHasSelfReferences = false; + for (auto p: mrCode.RPNTokens()) { switch (p->GetType()) @@ -4393,10 +4405,10 @@ struct ScDependantsCalculator } // Trim data array length to actual data range. - SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row(), mnLen); + SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row() + mnStartOffset, mnSpanLen); - aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row(), aRefPos.Tab(), - aRefPos.Col(), aRefPos.Row() + nTrimLen - 1, aRefPos.Tab())); + aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row() + mnStartOffset, aRefPos.Tab(), + aRefPos.Col(), aRefPos.Row() + mnStartOffset + nTrimLen - 1, aRefPos.Tab())); } else { @@ -4458,26 +4470,19 @@ struct ScDependantsCalculator continue; } - // Row reference is relative. - bool bAbsLast = !aRef.Ref2.IsRowRel(); - ScAddress aRefPos = aAbs.aStart; - SCROW nRefRowSize = aAbs.aEnd.Row() - aAbs.aStart.Row() + 1; - SCROW nArrayLength = nRefRowSize; - if (!bAbsLast) - { - // range end position is relative. Extend the array length. - SCROW nLastRefRowOffset = aAbs.aEnd.Row() - mrPos.Row(); - SCROW nLastRefRow = mrPos.Row() + mnLen - 1 + nLastRefRowOffset; - SCROW nNewLength = nLastRefRow - aAbs.aStart.Row() + 1; - if (nNewLength > nArrayLength) - nArrayLength = nNewLength; - } + // The first row that will be referenced through the doubleref. + SCROW nFirstRefRow = bIsRef1RowRel ? aAbs.aStart.Row() + mnStartOffset : aAbs.aStart.Row(); + // The last row that will be referenced through the doubleref. + SCROW nLastRefRow = bIsRef2RowRel ? aAbs.aEnd.Row() + mnEndOffset : aAbs.aEnd.Row(); + // Number of rows to be evaluated from nFirstRefRow. + SCROW nArrayLength = nLastRefRow - nFirstRefRow + 1; + assert(nArrayLength > 0); // Trim trailing empty rows. - nArrayLength = trimLength(aRefPos.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), aRefPos.Row(), nArrayLength); + nArrayLength = trimLength(aAbs.aStart.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), nFirstRefRow, nArrayLength); - aRangeList.Join(ScRange(aAbs.aStart.Col(), aRefPos.Row(), aRefPos.Tab(), - aAbs.aEnd.Col(), aRefPos.Row() + nArrayLength - 1, aRefPos.Tab())); + aRangeList.Join(ScRange(aAbs.aStart.Col(), nFirstRefRow, aAbs.aStart.Tab(), + aAbs.aEnd.Col(), nFirstRefRow + nArrayLength - 1, aAbs.aEnd.Tab())); } break; default: @@ -4510,7 +4515,7 @@ struct ScDependantsCalculator } }; -bool ScFormulaCell::InterpretFormulaGroup() +bool ScFormulaCell::InterpretFormulaGroup(SCROW nStartOffset, SCROW nEndOffset) { if (!mxGroup || !pCode) return false; @@ -4573,17 +4578,29 @@ bool ScFormulaCell::InterpretFormulaGroup() bool bDependencyComputed = false; bool bDependencyCheckFailed = false; + // Get rid of -1's in offsets (defaults) or any invalid offsets. + SCROW nMaxOffset = mxGroup->mnLength - 1; + nStartOffset = nStartOffset < 0 ? 0 : std::min(nStartOffset, nMaxOffset); + nEndOffset = nEndOffset < 0 ? nMaxOffset : std::min(nEndOffset, nMaxOffset); + + if (nEndOffset < nStartOffset) + { + nStartOffset = 0; + nEndOffset = nMaxOffset; + } + // Preference order: First try OpenCL, then threading. + // TODO: Do formula-group span computation for OCL too if nStartOffset/nEndOffset are non default. if( InterpretFormulaGroupOpenCL(aScope, bDependencyComputed, bDependencyCheckFailed)) return true; - if( InterpretFormulaGroupThreading(aScope, bDependencyComputed, bDependencyCheckFailed)) + if( InterpretFormulaGroupThreading(aScope, bDependencyComputed, bDependencyCheckFailed, nStartOffset, nEndOffset)) return true; return false; } -bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow) +bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow, SCROW nStartOffset, SCROW nEndOffset) { ScRecursionHelper& rRecursionHelper = pDocument->GetRecursionHelper(); // iterate over code in the formula ... @@ -4601,7 +4618,7 @@ bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rSco } ScFormulaGroupDependencyComputeGuard aDepComputeGuard(rRecursionHelper); - ScDependantsCalculator aCalculator(*pDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow); + ScDependantsCalculator aCalculator(*pDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset); bOKToParallelize = aCalculator.DoIt(); } @@ -4632,14 +4649,16 @@ bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rSco // To be called only from InterpretFormulaGroup(). bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope& aScope, bool& bDependencyComputed, - bool& bDependencyCheckFailed) + bool& bDependencyCheckFailed, + SCROW nStartOffset, + SCROW nEndOffset) { static const bool bThreadingProhibited = std::getenv("SC_NO_THREADED_CALCULATION"); if (!bDependencyCheckFailed && !bThreadingProhibited && pCode->IsEnabledForThreading() && ScCalcConfig::isThreadingEnabled()) { - if(!bDependencyComputed && !CheckComputeDependencies(aScope)) + if(!bDependencyComputed && !CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset)) { bDependencyComputed = true; bDependencyCheckFailed = true; @@ -4660,7 +4679,8 @@ bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope ScDocument* mpDocument; ScInterpreterContext* mpContext; const ScAddress& mrTopPos; - SCROW const mnLength; + SCROW const mnStartOffset; + SCROW const mnEndOffset; public: Executor(const std::shared_ptr<comphelper::ThreadTaskTag>& rTag, @@ -4669,20 +4689,23 @@ bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope ScDocument* pDocument2, ScInterpreterContext* pContext, const ScAddress& rTopPos, - SCROW nLength) : + SCROW nStartOffset, + SCROW nEndOffset) : comphelper::ThreadTask(rTag), mnThisThread(nThisThread), mnThreadsTotal(nThreadsTotal), mpDocument(pDocument2), mpContext(pContext), mrTopPos(rTopPos), - mnLength(nLength) + mnStartOffset(nStartOffset), + mnEndOffset(nEndOffset) { } virtual void doWork() override { - mpDocument->CalculateInColumnInThread(*mpContext, mrTopPos, mnLength, mnThisThread, mnThreadsTotal); + ScAddress aStartPos(mrTopPos.Col(), mrTopPos.Row() + mnStartOffset, mrTopPos.Tab()); + mpDocument->CalculateInColumnInThread(*mpContext, aStartPos, mnEndOffset - mnStartOffset + 1, mnThisThread, mnThreadsTotal); ScDocumentThreadSpecific::MergeBackIntoNonThreadedData(mpDocument->maNonThreaded); } @@ -4707,11 +4730,13 @@ bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope std::shared_ptr<comphelper::ThreadTaskTag> aTag = comphelper::ThreadPool::createThreadTaskTag(); ScThreadedInterpreterContextGetterGuard aContextGetterGuard(nThreadCount, *pDocument, pNonThreadedFormatter); ScInterpreterContext* context = nullptr; + for (int i = 0; i < nThreadCount; ++i) { context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i); ScDocument::SetupFromNonThreadedContext(*context, i); - rThreadPool.pushTask(std::make_unique<Executor>(aTag, i, nThreadCount, pDocument, context, mxGroup->mpTopCell->aPos, mxGroup->mnLength)); + rThreadPool.pushTask(std::make_unique<Executor>(aTag, i, nThreadCount, pDocument, context, mxGroup->mpTopCell->aPos, + nStartOffset, nEndOffset)); } SAL_INFO("sc.threaded", "Joining threads"); @@ -4729,7 +4754,10 @@ bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope SAL_INFO("sc.threaded", "Done"); } - pDocument->HandleStuffAfterParallelCalculation(mxGroup->mpTopCell->aPos, mxGroup->mnLength); + ScAddress aStartPos(mxGroup->mpTopCell->aPos); + SCROW nSpanLen = nEndOffset - nStartOffset + 1; + aStartPos.SetRow(aStartPos.Row() + nStartOffset); + pDocument->HandleStuffAfterParallelCalculation(aStartPos, nSpanLen); return true; } @@ -4782,7 +4810,7 @@ bool ScFormulaCell::InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope& a if (bDependencyCheckFailed) return false; - if(!bDependencyComputed && !CheckComputeDependencies(aScope, true)) + if(!bDependencyComputed && !CheckComputeDependencies(aScope, true, 0, mxGroup->mnLength - 1)) { bDependencyComputed = true; bDependencyCheckFailed = true; diff --git a/sc/source/core/data/table7.cxx b/sc/source/core/data/table7.cxx index cbc3949d47b6..30f869a58733 100644 --- a/sc/source/core/data/table7.cxx +++ b/sc/source/core/data/table7.cxx @@ -429,15 +429,22 @@ std::unique_ptr<sc::ColumnIterator> ScTable::GetColumnIterator( SCCOL nCol, SCRO return aCol[nCol].GetColumnIterator(nRow1, nRow2); } -void ScTable::EnsureFormulaCellResults( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2 ) +bool ScTable::EnsureFormulaCellResults( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2, bool bSkipRunning ) { if (nCol2 < nCol1 || !IsColValid(nCol1) || !ValidCol(nCol2)) - return; + return false; const SCCOL nMaxCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 ); + bool bAnyDirty = false; + for (SCCOL nCol = nCol1; nCol <= nMaxCol2; ++nCol) - aCol[nCol].EnsureFormulaCellResults(nRow1, nRow2); + { + bool bRet = aCol[nCol].EnsureFormulaCellResults(nRow1, nRow2, bSkipRunning); + bAnyDirty = bAnyDirty || bRet; + } + + return bAnyDirty; } void ScTable::finalizeOutlineImport() diff --git a/sc/source/ui/view/output.cxx b/sc/source/ui/view/output.cxx index 1cf2a1620d06..616e6d2073d7 100644 --- a/sc/source/ui/view/output.cxx +++ b/sc/source/ui/view/output.cxx @@ -1804,6 +1804,8 @@ void ScOutputData::FindChanged() pRowInfo[nArrY].bChanged = false; bool bProgress = false; + SCCOL nCol1 = MAXCOL, nCol2 = 0; + SCROW nRow1 = MAXROW, nRow2 = 0; for (nArrY=0; nArrY<nArrCount; nArrY++) { RowInfo* pThisRowInfo = &pRowInfo[nArrY]; @@ -1824,26 +1826,51 @@ void ScOutputData::FindChanged() // still being interpreted. Skip it. continue; - (void)pFCell->GetValue(); - if (!pFCell->IsChanged()) - // the result hasn't changed. Skip it. - continue; + ScAddress& rPos(pFCell->aPos); + nCol1 = std::min(rPos.Col(), nCol1); + nCol2 = std::max(rPos.Col(), nCol2); + nRow1 = std::min(rPos.Row(), nRow1); + nRow2 = std::max(rPos.Row(), nRow2); + } + } + + if (bProgress) + { + mpDoc->EnsureFormulaCellResults(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab), true); - pThisRowInfo->bChanged = true; - if ( pThisRowInfo->pCellInfo[nX+1].bMerged ) + for (nArrY=0; nArrY<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + for (nX=nX1; nX<=nX2; nX++) { - SCSIZE nOverY = nArrY + 1; - while ( nOverY<nArrCount && - pRowInfo[nOverY].pCellInfo[nX+1].bVOverlapped ) + const ScRefCellValue& rCell = pThisRowInfo->pCellInfo[nX+1].maCell; + + if (rCell.meType != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pFCell = rCell.mpFormula; + + if (!pFCell->IsChanged()) + // the result hasn't changed. Skip it. + continue; + + pThisRowInfo->bChanged = true; + if ( pThisRowInfo->pCellInfo[nX+1].bMerged ) { - pRowInfo[nOverY].bChanged = true; - ++nOverY; + SCSIZE nOverY = nArrY + 1; + while ( nOverY<nArrCount && + pRowInfo[nOverY].pCellInfo[nX+1].bVOverlapped ) + { + pRowInfo[nOverY].bChanged = true; + ++nOverY; + } } } } - } - if ( bProgress ) + ScProgress::DeleteInterpretProgress(); + } + mpDoc->EnableIdle(bWasIdleEnabled); } |