sw/inc/ndhints.hxx | 48 +++++++++++----- sw/source/core/txtnode/atrref.cxx | 10 ++- sw/source/core/txtnode/atrtox.cxx | 10 ++- sw/source/core/txtnode/ndhints.cxx | 103 +++++++++++++++++++++++++++++------- sw/source/core/txtnode/thints.cxx | 11 +-- sw/source/core/txtnode/txatbase.cxx | 10 ++- 6 files changed, 144 insertions(+), 48 deletions(-)
New commits: commit 365e356b66ed98426ba06347ef29e58a02885579 Author: Noel Grandin <noel.gran...@collabora.co.uk> AuthorDate: Fri Jun 14 11:09:51 2024 +0200 Commit: Noel Grandin <noel.gran...@collabora.co.uk> CommitDate: Fri Jun 14 22:08:15 2024 +0200 tdf#144208 speedup doc with lots of redline (11) reduce the amount of sorting work we need to do in the m_HintsBy* maps, by storing the range of elements we need to resort Change-Id: I93bd22c9bfcf41f7c02828e184ff1170700cdcc5 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/168857 Reviewed-by: Noel Grandin <noel.gran...@collabora.co.uk> Tested-by: Jenkins diff --git a/sw/inc/ndhints.hxx b/sw/inc/ndhints.hxx index ceead82cecd9..e5d43d5973f7 100644 --- a/sw/inc/ndhints.hxx +++ b/sw/inc/ndhints.hxx @@ -57,10 +57,14 @@ struct CompareSwpHtEnd bool operator()( sal_Int32 nEndPos, const SwTextAttr* rhs ) const; bool operator()( const SwTextAttr* lhs, const SwTextAttr* rhs ) const; }; +typedef std::pair<sal_Int32, sal_Int32> WhichStartPair; struct CompareSwpHtWhichStart { bool operator()( const SwTextAttr* lhs, const sal_uInt16 nWhich ) const; bool operator()( const SwTextAttr* lhs, const SwTextAttr* rhs ) const; + // used when we sort only a partial range of the map + bool operator()( const SwTextAttr* lhs, const WhichStartPair nWhich ) const; + bool operator()( const WhichStartPair nWhich, const SwTextAttr* lhs ) const; }; /// An SwTextAttr container, stores all directly formatted text portions for a text node. @@ -89,9 +93,11 @@ private: bool m_bFootnote : 1; ///< footnotes bool m_bDDEFields : 1; ///< the TextNode has DDE fields // Sort on demand to avoid O(n^2) behaviour - mutable bool m_bStartMapNeedsSorting : 1; - mutable bool m_bEndMapNeedsSorting : 1; - mutable bool m_bWhichMapNeedsSorting : 1; + // SAL_MAX_INT32 means nothing needs sorting, -1 means everything needs sorting + mutable std::pair<sal_Int32, sal_Int32> m_StartMapNeedsSortingRange { SAL_MAX_INT32, -1 }; + mutable std::pair<sal_Int32, sal_Int32> m_EndMapNeedsSortingRange { SAL_MAX_INT32, -1 }; + // we are storing pairs of { Which, Start } here + mutable std::pair<WhichStartPair, WhichStartPair> m_WhichMapNeedsSortingRange { { SAL_MAX_INT32, -1 }, { -1, -1 } }; /// records a new attribute in m_pHistory. void NoteInHistory( SwTextAttr *pAttr, const bool bNew = false ); @@ -143,8 +149,8 @@ public: bool Contains( const SwTextAttr *pHt ) const; SwTextAttr * Get( size_t nPos ) const { - assert( !(nPos != 0 && m_bStartMapNeedsSorting) && "going to trigger a resort in the middle of an iteration, that's bad" ); - if (m_bStartMapNeedsSorting) + assert( !(nPos != 0 && m_StartMapNeedsSortingRange.first != SAL_MAX_INT32) && "going to trigger a resort in the middle of an iteration, that's bad" ); + if (m_StartMapNeedsSortingRange.first != SAL_MAX_INT32) ResortStartMap(); return m_HintsByStart[nPos]; } @@ -157,8 +163,8 @@ public: int GetLastPosSortedByEnd(sal_Int32 nEndPos) const; SwTextAttr * GetSortedByEnd( size_t nPos ) const { - assert( !(nPos != 0 && m_bEndMapNeedsSorting) && "going to trigger a resort in the middle of an iteration, that's bad" ); - if (m_bEndMapNeedsSorting) + assert( !(nPos != 0 && m_EndMapNeedsSortingRange.first != SAL_MAX_INT32) && "going to trigger a resort in the middle of an iteration, that's bad" ); + if (m_EndMapNeedsSortingRange.first != SAL_MAX_INT32) ResortEndMap(); return m_HintsByEnd[nPos]; } @@ -166,8 +172,8 @@ public: size_t GetFirstPosSortedByWhichAndStart(sal_uInt16 nWhich) const; SwTextAttr * GetSortedByWhichAndStart( size_t nPos ) const { - assert( !(nPos != 0 && m_bWhichMapNeedsSorting) && "going to trigger a resort in the middle of an iteration, that's bad" ); - if (m_bWhichMapNeedsSorting) + assert( !(nPos != 0 && m_WhichMapNeedsSortingRange.first.first != SAL_MAX_INT32) && "going to trigger a resort in the middle of an iteration, that's bad" ); + if (m_WhichMapNeedsSortingRange.first.first != SAL_MAX_INT32) ResortWhichMap(); return m_HintsByWhichAndStart[nPos]; } @@ -175,11 +181,11 @@ public: /// Trigger the sorting if necessary void SortIfNeedBe() const { - if (m_bStartMapNeedsSorting) + if (m_StartMapNeedsSortingRange.first != SAL_MAX_INT32) ResortStartMap(); - if (m_bEndMapNeedsSorting) + if (m_EndMapNeedsSortingRange.first != SAL_MAX_INT32) ResortEndMap(); - if (m_bWhichMapNeedsSorting) + if (m_WhichMapNeedsSortingRange.first.first != SAL_MAX_INT32) ResortWhichMap(); } SwTextAttr * Cut( const size_t nPosInStart ) @@ -209,8 +215,22 @@ public: bool CalcHiddenParaField() const; // changes mutable state // Marks the hint-maps as needing sorting because the position of something has changed - void StartPosChanged() const { m_bStartMapNeedsSorting = true; m_bEndMapNeedsSorting = true; m_bWhichMapNeedsSorting = true; } - void EndPosChanged() const { m_bStartMapNeedsSorting = true; m_bEndMapNeedsSorting = true; m_bWhichMapNeedsSorting = true; } + void StartPosChanged() const + { + m_StartMapNeedsSortingRange = { -1, -1 }; + m_EndMapNeedsSortingRange = { -1, -1 }; + m_WhichMapNeedsSortingRange = { { -1, -1 }, { -1, -1 } }; + } + void EndPosChanged(sal_uInt16 nWhich, sal_Int32 nStartPos, sal_Int32 nOldEndPos, sal_Int32 nNewEndPos) const + { + m_StartMapNeedsSortingRange = { std::min(nStartPos, m_StartMapNeedsSortingRange.first), + std::max(nStartPos, m_StartMapNeedsSortingRange.second) }; + m_EndMapNeedsSortingRange = { std::min(std::min(nOldEndPos, nNewEndPos), m_EndMapNeedsSortingRange.first), + std::max(std::max(nOldEndPos, nNewEndPos), m_EndMapNeedsSortingRange.second) }; + WhichStartPair aPair { sal_Int32(nWhich), nStartPos }; + m_WhichMapNeedsSortingRange = { std::min(aPair, m_WhichMapNeedsSortingRange.first), + std::max(aPair, m_WhichMapNeedsSortingRange.second) }; + } }; #endif diff --git a/sw/source/core/txtnode/atrref.cxx b/sw/source/core/txtnode/atrref.cxx index 6800c40993eb..bfd932545496 100644 --- a/sw/source/core/txtnode/atrref.cxx +++ b/sw/source/core/txtnode/atrref.cxx @@ -162,9 +162,13 @@ const sal_Int32* SwTextRefMark::GetEnd() const void SwTextRefMark::SetEnd(sal_Int32 n) { - *m_pEnd = n; - if (m_pHints) - m_pHints->EndPosChanged(); + if (*m_pEnd != n) + { + sal_Int32 nOldEndPos = *m_pEnd; + *m_pEnd = n; + if (m_pHints) + m_pHints->EndPosChanged(Which(), GetStart(), nOldEndPos, *m_pEnd); + } } void SwTextRefMark::UpdateFieldContent(SwDoc* pDoc, SwWrtShell& rWrtSh, OUString aContent) diff --git a/sw/source/core/txtnode/atrtox.cxx b/sw/source/core/txtnode/atrtox.cxx index 17093db9be0b..b56e75c1a97f 100644 --- a/sw/source/core/txtnode/atrtox.cxx +++ b/sw/source/core/txtnode/atrtox.cxx @@ -56,9 +56,13 @@ const sal_Int32* SwTextTOXMark::GetEnd() const void SwTextTOXMark::SetEnd(sal_Int32 n) { - *m_pEnd = n; - if (m_pHints) - m_pHints->EndPosChanged(); + if (*m_pEnd != n) + { + sal_Int32 nOldEndPos = *m_pEnd; + *m_pEnd = n; + if (m_pHints) + m_pHints->EndPosChanged(Which(), GetStart(), nOldEndPos, *m_pEnd); + } } void SwTextTOXMark::CopyTOXMark( SwDoc& rDoc ) diff --git a/sw/source/core/txtnode/ndhints.cxx b/sw/source/core/txtnode/ndhints.cxx index f5f234d45e80..b7f82239a22e 100644 --- a/sw/source/core/txtnode/ndhints.cxx +++ b/sw/source/core/txtnode/ndhints.cxx @@ -64,6 +64,33 @@ static bool CompareSwpHtStart( const SwTextAttr* lhs, const SwTextAttr* rhs ) return ( rHt1.GetStart() < rHt2.GetStart() ); } +/// sort order: Start +/// (char style: sort number), at last the pointer +namespace { +struct CompareSwpHtStartOnly +{ + bool operator()( const SwTextAttr* lhs, sal_Int32 rhs ) const + { + return lhs->GetStart() < rhs; + } + bool operator()( sal_Int32 lhs, const SwTextAttr* rhs ) const + { + return lhs < rhs->GetStart(); + } +}; +struct CompareSwpHtEndOnly +{ + bool operator()( const SwTextAttr* lhs, sal_Int32 rhs ) const + { + return lhs->GetAnyEnd() < rhs; + } + bool operator()( sal_Int32 lhs, const SwTextAttr* rhs ) const + { + return lhs < rhs->GetAnyEnd(); + } +}; +} + /// sort order: Which, Start, End(reverse) at last the pointer bool CompareSwpHtWhichStart::operator()( const SwTextAttr* lhs, const sal_uInt16 nWhich ) const { @@ -100,6 +127,22 @@ bool CompareSwpHtWhichStart::operator()( const SwTextAttr* lhs, const SwTextAttr return false; return reinterpret_cast<sal_IntPtr>(&rHt1) < reinterpret_cast<sal_IntPtr>(&rHt2); } +bool CompareSwpHtWhichStart::operator()( const SwTextAttr* lhs, const WhichStartPair rhs ) const +{ + if ( lhs->Which() < rhs.first ) + return true; + if ( lhs->Which() > rhs.first ) + return false; + return lhs->GetStart() < rhs.second; +} +bool CompareSwpHtWhichStart::operator()( const WhichStartPair lhs, const SwTextAttr* rhs ) const +{ + if ( lhs.first < rhs->Which() ) + return true; + if ( lhs.first > rhs->Which() ) + return false; + return lhs.second < rhs->GetStart(); +} /// sort order: End, Start(reverse), Which /// (char style: sort number), at last the pointer(reverse) @@ -149,12 +192,9 @@ void SwpHints::Insert(SwTextAttr* pHt) assert( pHt->m_pHints == nullptr ); pHt->m_pHints = this; - if (m_bStartMapNeedsSorting) - ResortStartMap(); - if (m_bEndMapNeedsSorting) - ResortEndMap(); - if (m_bWhichMapNeedsSorting) - ResortWhichMap(); + ResortStartMap(); + ResortEndMap(); + ResortWhichMap(); auto it1 = std::lower_bound(m_HintsByStart.begin(), m_HintsByStart.end(), pHt, CompareSwpHtStart); m_HintsByStart.insert(it1, pHt); @@ -412,37 +452,64 @@ void SwpHints::Resort() const void SwpHints::ResortStartMap() const { - if (m_bStartMapNeedsSorting) + if (m_StartMapNeedsSortingRange.first != SAL_MAX_INT32) { auto & rStartMap = const_cast<SwpHints*>(this)->m_HintsByStart; - std::sort(rStartMap.begin(), rStartMap.end(), CompareSwpHtStart); - m_bStartMapNeedsSorting = false; + if (m_StartMapNeedsSortingRange.first == -1) + std::sort(rStartMap.begin(), rStartMap.end(), CompareSwpHtStart); + else + { + // only need to sort a partial range of the array + auto it1 = std::lower_bound(rStartMap.begin(), rStartMap.end(), m_StartMapNeedsSortingRange.first, CompareSwpHtStartOnly()); + auto it2 = std::upper_bound(rStartMap.begin(), rStartMap.end(), m_StartMapNeedsSortingRange.second, CompareSwpHtStartOnly()); + std::sort(rStartMap.begin() + std::distance(rStartMap.begin(), it1), + rStartMap.begin() + std::distance(rStartMap.begin(), it2), CompareSwpHtStart); + } + m_StartMapNeedsSortingRange = { SAL_MAX_INT32, -1 }; } } void SwpHints::ResortEndMap() const { - if (m_bEndMapNeedsSorting) + if (m_EndMapNeedsSortingRange.first != SAL_MAX_INT32) { auto & rEndMap = const_cast<SwpHints*>(this)->m_HintsByEnd; - std::sort(rEndMap.begin(), rEndMap.end(), CompareSwpHtEnd()); - m_bEndMapNeedsSorting = false; + if (m_EndMapNeedsSortingRange.first == -1) + std::sort(rEndMap.begin(), rEndMap.end(), CompareSwpHtEnd()); + else + { + // only need to sort a partial range of the array + auto it1 = std::lower_bound(rEndMap.begin(), rEndMap.end(), m_EndMapNeedsSortingRange.first, CompareSwpHtEndOnly()); + auto it2 = std::upper_bound(rEndMap.begin(), rEndMap.end(), m_EndMapNeedsSortingRange.second, CompareSwpHtEndOnly()); + std::sort(rEndMap.begin() + std::distance(rEndMap.begin(), it1), + rEndMap.begin() + std::distance(rEndMap.begin(), it2), CompareSwpHtEnd()); + } + m_EndMapNeedsSortingRange = { SAL_MAX_INT32, -1 }; } } void SwpHints::ResortWhichMap() const { - if (m_bWhichMapNeedsSorting) + if (m_WhichMapNeedsSortingRange.first.first != SAL_MAX_INT32) { auto & rWhichStartMap = const_cast<SwpHints*>(this)->m_HintsByWhichAndStart; - std::sort(rWhichStartMap.begin(), rWhichStartMap.end(), CompareSwpHtWhichStart()); - m_bWhichMapNeedsSorting = false; + if (m_WhichMapNeedsSortingRange.first.first == -1) + std::sort(rWhichStartMap.begin(), rWhichStartMap.end(), CompareSwpHtWhichStart()); + else + { + // only need to sort a partial range of the array + auto it1 = std::lower_bound(rWhichStartMap.begin(), rWhichStartMap.end(), m_WhichMapNeedsSortingRange.first, CompareSwpHtWhichStart()); + auto it2 = std::upper_bound(rWhichStartMap.begin(), rWhichStartMap.end(), m_WhichMapNeedsSortingRange.second, CompareSwpHtWhichStart()); + std::sort(rWhichStartMap.begin() + std::distance(rWhichStartMap.begin(), it1), + rWhichStartMap.begin() + std::distance(rWhichStartMap.begin(), it2), CompareSwpHtWhichStart()); + } + m_WhichMapNeedsSortingRange = { { SAL_MAX_INT32, -1 }, { -1, -1 } }; } } size_t SwpHints::GetFirstPosSortedByWhichAndStart( sal_uInt16 nWhich ) const { - if (m_bWhichMapNeedsSorting) + if (m_WhichMapNeedsSortingRange.first.first != SAL_MAX_INT32) ResortWhichMap(); auto it = std::lower_bound(m_HintsByWhichAndStart.begin(), m_HintsByWhichAndStart.end(), nWhich, CompareSwpHtWhichStart()); if ( it == m_HintsByWhichAndStart.end() ) @@ -452,7 +519,7 @@ size_t SwpHints::GetFirstPosSortedByWhichAndStart( sal_uInt16 nWhich ) const int SwpHints::GetLastPosSortedByEnd( sal_Int32 nEndPos ) const { - if (m_bEndMapNeedsSorting) + if (m_EndMapNeedsSortingRange.first != SAL_MAX_INT32) ResortEndMap(); auto it = std::upper_bound(m_HintsByEnd.begin(), m_HintsByEnd.end(), nEndPos, CompareSwpHtEnd()); return it - m_HintsByEnd.begin() - 1; @@ -460,7 +527,7 @@ int SwpHints::GetLastPosSortedByEnd( sal_Int32 nEndPos ) const size_t SwpHints::GetIndexOf( const SwTextAttr *pHt ) const { - if (m_bStartMapNeedsSorting) + if (m_StartMapNeedsSortingRange.first != SAL_MAX_INT32) ResortStartMap(); auto it = std::lower_bound(m_HintsByStart.begin(), m_HintsByStart.end(), const_cast<SwTextAttr*>(pHt), CompareSwpHtStart); if ( it == m_HintsByStart.end() || *it != pHt ) diff --git a/sw/source/core/txtnode/thints.cxx b/sw/source/core/txtnode/thints.cxx index 4aae2af56eb8..9ab24d03af75 100644 --- a/sw/source/core/txtnode/thints.cxx +++ b/sw/source/core/txtnode/thints.cxx @@ -98,9 +98,6 @@ SwpHints::SwpHints(const SwTextNode& rParent) , m_bHiddenByParaField(false) , m_bFootnote(false) , m_bDDEFields(false) - , m_bStartMapNeedsSorting(false) - , m_bEndMapNeedsSorting(false) - , m_bWhichMapNeedsSorting(false) { } @@ -3390,7 +3387,7 @@ bool SwpHints::TryInsertHint( void SwpHints::DeleteAtPos( const size_t nPos ) { - assert(!m_bStartMapNeedsSorting && "deleting at pos and the list needs sorting?"); + assert(m_StartMapNeedsSortingRange.first == SAL_MAX_INT32 && "deleting at pos and the list needs sorting?"); SwTextAttr *pHint = Get(nPos); assert( pHint->m_pHints == this ); @@ -3401,11 +3398,11 @@ void SwpHints::DeleteAtPos( const size_t nPos ) SwTextAttr *pHt = m_HintsByStart[ nPos ]; m_HintsByStart.erase( m_HintsByStart.begin() + nPos ); - if (m_bStartMapNeedsSorting) + if (m_StartMapNeedsSortingRange.first != SAL_MAX_INT32) ResortStartMap(); - if (m_bEndMapNeedsSorting) + if (m_EndMapNeedsSortingRange.first != SAL_MAX_INT32) ResortEndMap(); - if (m_bWhichMapNeedsSorting) + if (m_WhichMapNeedsSortingRange.first.first != SAL_MAX_INT32) ResortWhichMap(); auto findIt = std::lower_bound(m_HintsByEnd.begin(), m_HintsByEnd.end(), pHt, CompareSwpHtEnd()); diff --git a/sw/source/core/txtnode/txatbase.cxx b/sw/source/core/txtnode/txatbase.cxx index 968a7b2d2b77..7b10d29297d8 100644 --- a/sw/source/core/txtnode/txatbase.cxx +++ b/sw/source/core/txtnode/txatbase.cxx @@ -81,9 +81,13 @@ const sal_Int32* SwTextAttrEnd::GetEnd() const void SwTextAttrEnd::SetEnd(sal_Int32 n) { - m_nEnd = n; - if (m_pHints) - m_pHints->EndPosChanged(); + if (m_nEnd != n) + { + sal_Int32 nOldEndPos = m_nEnd; + m_nEnd = n; + if (m_pHints) + m_pHints->EndPosChanged(Which(), GetStart(), nOldEndPos, m_nEnd); + } } void SwTextAttr::dumpAsXml(xmlTextWriterPtr pWriter) const