include/svl/itemset.hxx                       |   24 
 include/svl/whichranges.hxx                   |   21 
 svl/source/items/itemiter.cxx                 |   21 
 svl/source/items/itemset.cxx                  |  673 +++++++++++++++-----------
 svl/source/items/whiter.cxx                   |   25 
 svx/source/dialog/srchdlg.cxx                 |    2 
 sw/source/core/crsr/findattr.cxx              |    8 
 sw/source/core/doc/DocumentRedlineManager.cxx |    2 
 sw/source/core/txtnode/ndtxt.cxx              |    2 
 sw/source/core/undo/undobj1.cxx               |    2 
 sw/source/filter/ww8/writerhelper.cxx         |    2 
 sw/source/uibase/app/docstyle.cxx             |    2 
 sw/source/uibase/uiview/srcview.cxx           |    2 
 13 files changed, 472 insertions(+), 314 deletions(-)

New commits:
commit bd6c6d46e82eb5cf5839a5b99e4838471250e959
Author:     Armin Le Grand (allotropia) <armin.le.grand.ext...@allotropia.de>
AuthorDate: Mon Jul 31 13:12:48 2023 +0200
Commit:     Armin Le Grand <armin.le.gr...@me.com>
CommitDate: Mon Aug 7 17:53:53 2023 +0200

    ITEM: speedup WhichRanges access by buffering
    
    I checked for was to speedup SfxItemSet stuff, so had
    (besides other things) a look at WhichRangesContainer
    and it's usage(s). Problem with the WhichRanges is that
    a WhichID which you try to find is usually inside that
    range, so binary search is no option. You have to detect
    in which range the WhichID is hosted and can the directly
    calculate the index into the array of Items at the
    SfxtemSet.
    Currently when needing to transform a WhichID to an index
    into the array of Items in SfxItemSet the array of the
    WhichRangesContainer is searched linearly from the start
    every time.
    This can be a little bit speed up by buffering the last
    successful 'hit' and trying to re-use it. Also the
    special case of a single WhichPair (e.g. UI stuff) is
    worth having a look.
    All acesses to that transformation are changed to use
    the tooling method getOffsetFromWhich() at the
    WhichRangesContainer which does the transformation.
    This also needed cleanup of ItemOffsetHint instances
    & stuff around it. It does not more than before but
    also profits from the single entry buffer.
    I added some DBG_UTIL-based stuff to watch the hit/miss
    ratio, which is heavily changing from app to app & usage,
    but varies around 1.5 to 3.5, also saw 6.5 and more at
    document import.
    NOTE: I already checked if sorting the WhichPair(s) in
    WhichRangesContainer by their 'width' (highest WhichID
    mnius lowest WhichID helps. The idea was when the Items
    would be used in a regular manner that when having the
    widest WhichPairs at the start, the buffer would even
    be better used - but doing tests in all apps shows
    nearly no gain, so I left that out.
    NOTE: Not too much speedup, but faster...
    
    Had to deep-debug due to CppunitTest_sw_odfexport failing,
    found a slight diff between GetItemState impls, corrected.
    Also added more changes, e.g. TotalCount is now a member
    to not always have to calculate it from the
    WhichRangesContainer.
    Extended GetWhichByPos to 1st try to find a set Item, else
    iterate over WhichRangesContainer.
    This is due to SfxItemIter's implementations of
    GetItemState and ClearItem which both up to now just
    accessed the SfxPoolItem array of the SfxItemSet, ignoring
    that no Item (nullptr) or state DONTCARE (-1) may have been
    set there (no item is prevented by ite Iterator, but better
    be careful).
    
    Added WhichRangesContainer::getWhichFromOffset and made
    SfxItemSet::GetWhichByOffset use it. Addedd optimizations
    there for single-entry WhichPair and using the buffer at
    WhichRangesContainer which is possible.
    Removed debug comparing stuff (had a test that used the
    former adapted GetItemStateImpl method in
    SfxItemSet::GetItemState and compared with the changed
    GetItemState_ForWhichID).
    Added some comments and assertions where useful.
    Made ClearSingleItem_ForOffset work without handing
    over WhichID, that makes calls using it simpler and
    avoids calculating the WhichID just for that call.
    
    Change-Id: I54de552368b654f00f115978715f8241eb603752
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155316
    Tested-by: Jenkins
    Reviewed-by: Armin Le Grand <armin.le.gr...@me.com>

diff --git a/include/svl/itemset.hxx b/include/svl/itemset.hxx
index 52966ecc96d6..468893557b5e 100644
--- a/include/svl/itemset.hxx
+++ b/include/svl/itemset.hxx
@@ -40,9 +40,10 @@ class SAL_WARN_UNUSED SVL_DLLPUBLIC SfxItemSet
 
     SfxItemPool*      m_pPool;         ///< pool that stores the items
     const SfxItemSet* m_pParent;       ///< derivation
+    sal_uInt16        m_nCount;        ///< number of items
+    sal_uInt16        m_nTotalCount;   ///< number of WhichIDs, also size of 
m_ppItems array
     SfxPoolItem const** m_ppItems;     ///< pointer to array of items, we 
allocate and free this unless m_bItemsFixed==true
     WhichRangesContainer m_pWhichRanges;  ///< array of Which Ranges
-    sal_uInt16        m_nCount;        ///< number of items
     bool              m_bItemsFixed; ///< true if this is a SfxItemSetFixed 
object
 
 friend class SfxItemPoolCache;
@@ -69,7 +70,7 @@ protected:
     enum class SfxAllItemSetFlag { Flag };
     SfxItemSet( SfxItemPool&, SfxAllItemSetFlag );
     /** special constructor for SfxItemSetFixed */
-    SfxItemSet( SfxItemPool&, WhichRangesContainer&& ranges, SfxPoolItem const 
** ppItems );
+    SfxItemSet( SfxItemPool&, WhichRangesContainer&& ranges, SfxPoolItem const 
** ppItems, sal_uInt16 nTotalCount );
 
 public:
     SfxItemSet( const SfxItemSet& );
@@ -93,7 +94,7 @@ public:
 
     // Get number of items
     sal_uInt16                  Count() const { return m_nCount; }
-    sal_uInt16                  TotalCount() const;
+    sal_uInt16                  TotalCount() const { return m_nTotalCount; }
 
     const SfxPoolItem&          Get( sal_uInt16 nWhich, bool bSrchInParent = 
true ) const;
     template<class T>
@@ -140,7 +141,7 @@ public:
         return GetItem<T>(pItemSet, static_cast<sal_uInt16>(nWhich), 
bSearchInParent);
     }
 
-    sal_uInt16                  GetWhichByPos(sal_uInt16 nPos) const;
+    sal_uInt16                  GetWhichByOffset(sal_uInt16 nOffset) const;
 
     SfxItemState                GetItemState(   sal_uInt16 nWhich,
                                                 bool bSrchInParent = true,
@@ -224,12 +225,15 @@ public:
     void dumpAsXml(xmlTextWriterPtr pWriter) const;
 
 private:
-    sal_uInt16 ClearSingleItemImpl( sal_uInt16 nWhich, 
std::optional<sal_uInt16> oItemOffsetHint );
+    // split version(s) of ClearSingleItemImpl for input types WhichID and 
Offset
+    sal_uInt16 ClearSingleItem_ForWhichID( sal_uInt16 nWhich );
+    sal_uInt16 ClearSingleItem_ForOffset( sal_uInt16 nOffset );
+
     sal_uInt16 ClearAllItemsImpl();
-    SfxItemState  GetItemStateImpl( sal_uInt16 nWhich,
-                                bool bSrchInParent,
-                                const SfxPoolItem **ppItem,
-                                std::optional<sal_uInt16> oItemsOffsetHint) 
const;
+
+    // split version(s) of GetItemStateImpl for input types WhichID and Offset
+    SfxItemState GetItemState_ForWhichID( SfxItemState eState, sal_uInt16 
nWhich, bool bSrchInParent, const SfxPoolItem **ppItem) const;
+    SfxItemState GetItemState_ForOffset( sal_uInt16 nOffset, const SfxPoolItem 
**ppItem) const;
 };
 
 inline void SfxItemSet::SetParent( const SfxItemSet* pNew )
@@ -275,7 +279,7 @@ class SfxItemSetFixed : public SfxItemSet
 {
 public:
     SfxItemSetFixed( SfxItemPool& rPool)
-        : SfxItemSet(rPool, WhichRangesContainer(svl::Items_t<WIDs...>{}), 
m_aItems) {}
+        : SfxItemSet(rPool, WhichRangesContainer(svl::Items_t<WIDs...>{}), 
m_aItems, NITEMS) {}
 private:
     static constexpr sal_uInt16 NITEMS = svl::detail::CountRanges1<WIDs...>();
     const SfxPoolItem* m_aItems[NITEMS] = {};
diff --git a/include/svl/whichranges.hxx b/include/svl/whichranges.hxx
index 744a0f2edaf5..37a3d34e5613 100644
--- a/include/svl/whichranges.hxx
+++ b/include/svl/whichranges.hxx
@@ -71,6 +71,8 @@ template <sal_uInt16... WIDs> struct Items_t
 template <sal_uInt16... WIDs> inline static constexpr auto Items = 
Items_t<WIDs...>{};
 }
 
+#define INVALID_WHICHPAIR_OFFSET (sal_uInt16(0xffff))
+
 /**
  * Most of the time, the which ranges we point at are a compile-time literal.
  * So we take advantage of that, and avoid the cost of allocating our own 
array and copying into it.
@@ -85,12 +87,21 @@ struct SVL_DLLPUBLIC WhichRangesContainer
       * at a global const literal */
     bool m_bOwnRanges = false;
 
+    // variables for buffering the last used WhichPair to allow fast answers
+    // in getOffsetFromWhich
+    mutable sal_uInt16 m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+    mutable sal_uInt16 m_aLastWhichPairFirst = 0;
+    mutable sal_uInt16 m_aLastWhichPairSecond = 0;
+
     WhichRangesContainer() = default;
 
     WhichRangesContainer(std::unique_ptr<WhichPair[]> wids, sal_Int32 nSize)
         : m_pairs(wids.release())
         , m_size(nSize)
         , m_bOwnRanges(true)
+        , m_aLastWhichPairOffset(INVALID_WHICHPAIR_OFFSET)
+        , m_aLastWhichPairFirst(0)
+        , m_aLastWhichPairSecond(0)
     {
     }
     template <sal_uInt16... WIDs>
@@ -98,6 +109,9 @@ struct SVL_DLLPUBLIC WhichRangesContainer
         : m_pairs(svl::Items_t<WIDs...>::value.data())
         , m_size(svl::Items_t<WIDs...>::value.size())
         , m_bOwnRanges(false)
+        , m_aLastWhichPairOffset(INVALID_WHICHPAIR_OFFSET)
+        , m_aLastWhichPairFirst(0)
+        , m_aLastWhichPairSecond(0)
     {
     }
     WhichRangesContainer(const WhichPair* wids, sal_Int32 nSize);
@@ -121,6 +135,13 @@ struct SVL_DLLPUBLIC WhichRangesContainer
     }
     void reset();
 
+    // calculate and return the offset inside the fixed SfxPoolItem
+    // array of SfxItemPool
+    sal_uInt16 getOffsetFromWhich(sal_uInt16 nWhich) const;
+
+    // extract the WhichID for given offset
+    sal_uInt16 getWhichFromOffset(sal_uInt16 nOffset) const;
+
     // Adds a range to which ranges, keeping the ranges in valid state 
(sorted, non-overlapping)
     SAL_WARN_UNUSED_RESULT WhichRangesContainer MergeRange(sal_uInt16 nFrom, 
sal_uInt16 nTo) const;
 };
diff --git a/svl/source/items/itemiter.cxx b/svl/source/items/itemiter.cxx
index fd0f6240e0c6..d8864c387edd 100644
--- a/svl/source/items/itemiter.cxx
+++ b/svl/source/items/itemiter.cxx
@@ -58,14 +58,27 @@ const SfxPoolItem* SfxItemIter::ImplNextItem()
 
 SfxItemState SfxItemIter::GetItemState(bool bSrchInParent, const SfxPoolItem** 
ppItem) const
 {
-    sal_uInt16 nWhich = (*(m_rSet.m_ppItems + m_nCurrent))->Which();
-    return m_rSet.GetItemStateImpl(nWhich, bSrchInParent, ppItem, m_nCurrent);
+    // we have the offset, so use it to profit. It is always valid, so no need
+    // to check if smaller than TotalCount()
+    SfxItemState eState(m_rSet.GetItemState_ForOffset(m_nCurrent, ppItem));
+
+    // search in parent?
+    if (bSrchInParent && nullptr != m_rSet.GetParent()
+        && (SfxItemState::UNKNOWN == eState || SfxItemState::DEFAULT == 
eState))
+    {
+        // nOffset was only valid for *local* SfxItemSet, need to continue 
with WhichID
+        const sal_uInt16 nWhich(m_rSet.GetWhichByOffset(m_nCurrent));
+        eState = m_rSet.GetParent()->GetItemState_ForWhichID(eState, nWhich, 
true, ppItem);
+    }
+
+    return eState;
 }
 
 void SfxItemIter::ClearItem()
 {
-    sal_uInt16 nWhich = (*(m_rSet.m_ppItems + m_nCurrent))->Which();
-    const_cast<SfxItemSet&>(m_rSet).ClearSingleItemImpl(nWhich, m_nCurrent);
+    // we have the offset, so use it to profit. It is always valid, so no need
+    // to check if smaller than TotalCount()
+    const_cast<SfxItemSet&>(m_rSet).ClearSingleItem_ForOffset(m_nCurrent);
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/itemset.cxx b/svl/source/items/itemset.cxx
index 655956f2d1cc..bfc8f74b2010 100644
--- a/svl/source/items/itemset.cxx
+++ b/svl/source/items/itemset.cxx
@@ -45,11 +45,13 @@
  * Don't create ItemSets with full range before FreezeIdRanges()!
  */
 SfxItemSet::SfxItemSet(SfxItemPool& rPool)
-    : m_pPool(&rPool), m_pParent(nullptr),
-    m_ppItems(new SfxPoolItem const 
*[svl::detail::CountRanges(rPool.GetFrozenIdRanges())]{}),
-    m_pWhichRanges(rPool.GetFrozenIdRanges()),
-    m_nCount(0),
-    m_bItemsFixed(false)
+    : m_pPool(&rPool)
+    , m_pParent(nullptr)
+    , m_nCount(0)
+    , m_nTotalCount(svl::detail::CountRanges(rPool.GetFrozenIdRanges()))
+    , m_ppItems(new SfxPoolItem const *[m_nTotalCount]{})
+    , m_pWhichRanges(rPool.GetFrozenIdRanges())
+    , m_bItemsFixed(false)
 {
     assert(svl::detail::validRanges2(m_pWhichRanges));
 }
@@ -57,19 +59,21 @@ SfxItemSet::SfxItemSet(SfxItemPool& rPool)
 SfxItemSet::SfxItemSet( SfxItemPool& rPool, SfxAllItemSetFlag )
     : m_pPool(&rPool)
     , m_pParent(nullptr)
-    , m_ppItems(nullptr)
     , m_nCount(0)
+    , m_nTotalCount(0)
+    , m_ppItems(nullptr)
     , m_bItemsFixed(false)
 {
 }
 
 /** special constructor for SfxItemSetFixed */
-SfxItemSet::SfxItemSet( SfxItemPool& rPool, WhichRangesContainer&& ranges, 
SfxPoolItem const ** ppItems )
+SfxItemSet::SfxItemSet( SfxItemPool& rPool, WhichRangesContainer&& ranges, 
SfxPoolItem const ** ppItems, sal_uInt16 nTotalCount )
     : m_pPool(&rPool)
     , m_pParent(nullptr)
+    , m_nCount(0)
+    , m_nTotalCount(nTotalCount)
     , m_ppItems(ppItems)
     , m_pWhichRanges(std::move(ranges))
-    , m_nCount(0)
     , m_bItemsFixed(true)
 {
     assert(ppItems);
@@ -78,12 +82,13 @@ SfxItemSet::SfxItemSet( SfxItemPool& rPool, 
WhichRangesContainer&& ranges, SfxPo
 }
 
 SfxItemSet::SfxItemSet(SfxItemPool& pool, WhichRangesContainer wids)
-    : m_pPool(&pool),
-    m_pParent(nullptr),
-    m_ppItems(new SfxPoolItem const *[svl::detail::CountRanges(wids)]{}),
-    m_pWhichRanges(std::move(wids)),
-    m_nCount(0),
-    m_bItemsFixed(false)
+    : m_pPool(&pool)
+    , m_pParent(nullptr)
+    , m_nCount(0)
+    , m_nTotalCount(svl::detail::CountRanges(wids))
+    , m_ppItems(new SfxPoolItem const *[m_nTotalCount]{})
+    , m_pWhichRanges(std::move(wids))
+    , m_bItemsFixed(false)
 {
     assert(svl::detail::CountRanges(m_pWhichRanges) != 0);
     assert(svl::detail::validRanges2(m_pWhichRanges));
@@ -92,23 +97,23 @@ SfxItemSet::SfxItemSet(SfxItemPool& pool, 
WhichRangesContainer wids)
 SfxItemSet::SfxItemSet( const SfxItemSet& rASet )
     : m_pPool( rASet.m_pPool )
     , m_pParent( rASet.m_pParent )
-    , m_pWhichRanges( rASet.m_pWhichRanges )
     , m_nCount( rASet.m_nCount )
+    , m_nTotalCount( rASet.m_nTotalCount )
+    , m_ppItems(nullptr)
+    , m_pWhichRanges( rASet.m_pWhichRanges )
     , m_bItemsFixed(false)
 {
     if (rASet.m_pWhichRanges.empty())
     {
-        m_ppItems = nullptr;
         return;
     }
 
-    auto nCnt = svl::detail::CountRanges(m_pWhichRanges);
-    m_ppItems = new const SfxPoolItem* [nCnt] {};
+    m_ppItems = new const SfxPoolItem* [TotalCount()] {};
 
     // Copy attributes
     SfxPoolItem const** ppDst = m_ppItems;
     SfxPoolItem const** ppSrc = rASet.m_ppItems;
-    for( sal_uInt16 n = nCnt; n; --n, ++ppDst, ++ppSrc )
+    for( sal_uInt16 n = TotalCount(); n; --n, ++ppDst, ++ppSrc )
         if ( nullptr == *ppSrc ||                 // Current Default?
              IsInvalidItem(*ppSrc) ||       // DontCare?
              IsStaticDefaultItem(*ppSrc) )  // Defaults that are not to be 
pooled?
@@ -132,21 +137,24 @@ SfxItemSet::SfxItemSet( const SfxItemSet& rASet )
 SfxItemSet::SfxItemSet(SfxItemSet&& rASet) noexcept
     : m_pPool( rASet.m_pPool )
     , m_pParent( rASet.m_pParent )
+    , m_nCount( rASet.m_nCount )
+    , m_nTotalCount( rASet.TotalCount() )
     , m_ppItems( rASet.m_ppItems )
     , m_pWhichRanges( std::move(rASet.m_pWhichRanges) )
-    , m_nCount( rASet.m_nCount )
     , m_bItemsFixed(false)
 {
     if (rASet.m_bItemsFixed)
     {
         // have to make a copy
-        int noItems = svl::detail::CountRanges(m_pWhichRanges);
-        m_ppItems = new const SfxPoolItem* [noItems];
-        std::copy(rASet.m_ppItems, rASet.m_ppItems + noItems, m_ppItems);
+        m_ppItems = new const SfxPoolItem* [TotalCount()];
+        std::copy(rASet.m_ppItems, rASet.m_ppItems + TotalCount(), m_ppItems);
     }
     else
+    {
         // taking over ownership
+        rASet.m_nTotalCount = 0;
         rASet.m_ppItems = nullptr;
+    }
     rASet.m_pPool = nullptr;
     rASet.m_pParent = nullptr;
     rASet.m_nCount = 0;
@@ -159,9 +167,8 @@ SfxItemSet::~SfxItemSet()
     {
         if( Count() )
         {
-            sal_uInt16 nCount = TotalCount();
             SfxPoolItem const** ppFnd = m_ppItems;
-            for( sal_uInt16 nCnt = nCount; nCnt; --nCnt, ++ppFnd )
+            for( sal_uInt16 nCnt = TotalCount(); nCnt; --nCnt, ++ppFnd )
                 if( *ppFnd && !IsInvalidItem(*ppFnd) )
                 {
                     if( !(*ppFnd)->Which() )
@@ -192,40 +199,33 @@ sal_uInt16 SfxItemSet::ClearItem( sal_uInt16 nWhich )
     if( !Count() )
         return 0;
     if( nWhich )
-        return ClearSingleItemImpl(nWhich, std::nullopt);
+        return ClearSingleItem_ForWhichID(nWhich);
     else
         return ClearAllItemsImpl();
 }
 
-sal_uInt16 SfxItemSet::ClearSingleItemImpl( sal_uInt16 nWhich, 
std::optional<sal_uInt16> oItemOffsetHint )
+sal_uInt16 SfxItemSet::ClearSingleItem_ForWhichID( sal_uInt16 nWhich )
 {
-    sal_uInt16 nDel = 0;
-    SfxPoolItem const** pFoundOne = nullptr;
+    const sal_uInt16 nOffset(m_pWhichRanges.getOffsetFromWhich(nWhich));
 
-    if (oItemOffsetHint)
+    if (INVALID_WHICHPAIR_OFFSET != nOffset)
     {
-        pFoundOne = m_ppItems + *oItemOffsetHint;
-        assert(!*pFoundOne || IsInvalidItem(*pFoundOne) || 
(*pFoundOne)->IsVoidItem() || (*pFoundOne)->Which() == nWhich);
+        // found, continue with offset
+        return ClearSingleItem_ForOffset(nOffset);
     }
-    else
-    {
-        SfxPoolItem const** ppFnd = m_ppItems;
-        for (const WhichPair& rPair : m_pWhichRanges)
-        {
-            // Within this range?
-            if( rPair.first <= nWhich && nWhich <= rPair.second )
-            {
-                // Actually set?
-                ppFnd += nWhich - rPair.first;
-                pFoundOne = ppFnd;
 
-                // found => break
-                break;
-            }
-            ppFnd += rPair.second - rPair.first + 1;
-        }
-    }
-    if (pFoundOne && *pFoundOne)
+    // not found, return sal_uInt16 nDel = 0;
+    return 0;
+}
+
+sal_uInt16 SfxItemSet::ClearSingleItem_ForOffset( sal_uInt16 nOffset )
+{
+    sal_uInt16 nDel = 0;
+    SfxPoolItem const** pFoundOne = m_ppItems + nOffset;
+    assert(!*pFoundOne || IsInvalidItem(*pFoundOne) || 
(*pFoundOne)->IsVoidItem() || (*pFoundOne)->Which() != 0);
+    assert(nOffset < TotalCount());
+
+    if (*pFoundOne)
     {
         // Due to the assertions in the sub calls, we need to do the following
         --m_nCount;
@@ -234,6 +234,8 @@ sal_uInt16 SfxItemSet::ClearSingleItemImpl( sal_uInt16 
nWhich, std::optional<sal
 
         if ( !IsInvalidItem(pItemToClear) )
         {
+            const sal_uInt16 nWhich(pItemToClear->Which());
+
             if (SfxItemPool::IsWhich(nWhich))
             {
                 const SfxPoolItem& rNew = m_pParent
@@ -314,79 +316,64 @@ void SfxItemSet::ClearInvalidItems()
 void SfxItemSet::InvalidateAllItems()
 {
     assert( !m_nCount && "There are still Items set" );
-    m_nCount = TotalCount();
-    memset(static_cast<void*>(m_ppItems), -1, m_nCount * sizeof(SfxPoolItem*));
+    memset(static_cast<void*>(m_ppItems), -1, TotalCount() * 
sizeof(SfxPoolItem*));
 }
 
 SfxItemState SfxItemSet::GetItemState( sal_uInt16 nWhich,
                                         bool bSrchInParent,
                                         const SfxPoolItem **ppItem ) const
 {
-    return GetItemStateImpl(nWhich, bSrchInParent, ppItem, std::nullopt);
+    // use local helper, start value for looped-through SfxItemState value
+    // is SfxItemState::UNKNOWN
+    return GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, 
bSrchInParent, ppItem);
 }
 
-SfxItemState SfxItemSet::GetItemStateImpl( sal_uInt16 nWhich,
-                                           bool bSrchInParent,
-                                           const SfxPoolItem **ppItem,
-                                           std::optional<sal_uInt16> 
oItemsOffsetHint) const
+SfxItemState SfxItemSet::GetItemState_ForWhichID( SfxItemState eState, 
sal_uInt16 nWhich, bool bSrchInParent, const SfxPoolItem **ppItem) const
 {
-    // Find the range in which the Which is located
-    const SfxItemSet* pCurrentSet = this;
-    SfxItemState eRet = SfxItemState::UNKNOWN;
-    do
+    const sal_uInt16 nOffset(m_pWhichRanges.getOffsetFromWhich(nWhich));
+
+    if (INVALID_WHICHPAIR_OFFSET != nOffset)
     {
-        SfxPoolItem const** pFoundOne = nullptr;
-        if (oItemsOffsetHint)
-        {
-            pFoundOne = pCurrentSet->m_ppItems + *oItemsOffsetHint;
-            assert(!*pFoundOne || IsInvalidItem(*pFoundOne) || 
(*pFoundOne)->IsVoidItem() || (*pFoundOne)->Which() == nWhich);
-            oItemsOffsetHint.reset(); // in case we need to search parent
-        }
-        else
-        {
-            SfxPoolItem const** ppFnd = pCurrentSet->m_ppItems;
-            for (const WhichPair& rPair : pCurrentSet->m_pWhichRanges)
-            {
-                if ( rPair.first <= nWhich && nWhich <= rPair.second )
-                {
-                    // Within this range
-                    pFoundOne = ppFnd + nWhich - rPair.first;
-                    break;
-                }
-                ppFnd += rPair.second - rPair.first + 1;
-            }
-        }
+        // found, continue with offset
+        eState = GetItemState_ForOffset(nOffset, ppItem);
+    }
 
-        if (pFoundOne)
-        {
-            if ( !*pFoundOne )
-            {
-                eRet = SfxItemState::DEFAULT;
-                if( !bSrchInParent )
-                    return eRet; // Not present
-                // Keep searching in the parents!
-            }
-            else
-            {
-                if ( IsInvalidItem(*pFoundOne) )
-                    // Different ones are present
-                    return SfxItemState::DONTCARE;
+    // search in parent?
+    if (bSrchInParent && nullptr != GetParent() && (SfxItemState::UNKNOWN == 
eState || SfxItemState::DEFAULT == eState))
+    {
+        // nOffset was only valid for *local* SfxItemSet, need to continue 
with WhichID
+        // Use the *highest* SfxItemState as result
+        return GetParent()->GetItemState_ForWhichID( eState, nWhich, true, 
ppItem);
+    }
 
-                if ( (*pFoundOne)->IsVoidItem() )
-                    return SfxItemState::DISABLED;
+    return eState;
+}
 
-                if (ppItem)
-                {
-                    *ppItem = *pFoundOne;
-                }
-                return SfxItemState::SET;
-            }
-        }
-        if (!bSrchInParent)
-            break;
-        pCurrentSet = pCurrentSet->m_pParent;
-    } while (nullptr != pCurrentSet);
-    return eRet;
+SfxItemState SfxItemSet::GetItemState_ForOffset( sal_uInt16 nOffset, const 
SfxPoolItem **ppItem) const
+{
+    // check and assert fr iinvaliid offset. The caller is responsible for
+    // ensuring a valid offset (see callers, all checked & safe)
+    assert(nOffset < TotalCount());
+    SfxPoolItem const** pFoundOne = m_ppItems + nOffset;
+
+    if ( nullptr == *pFoundOne )
+        // set to Default
+        return SfxItemState::DEFAULT;
+
+    if ( IsInvalidItem(*pFoundOne) )
+        // Different ones are present
+        return SfxItemState::DONTCARE;
+
+    if ( (*pFoundOne)->IsVoidItem() )
+        // Item is Disabled
+        return SfxItemState::DISABLED;
+
+    if (ppItem)
+        // if we have the Item, add it to output an hand back
+        *ppItem = *pFoundOne;
+
+    // Item is set
+    return SfxItemState::SET;
 }
 
 bool SfxItemSet::HasItem(sal_uInt16 nWhich, const SfxPoolItem** ppItem) const
@@ -405,93 +392,91 @@ const SfxPoolItem* SfxItemSet::PutImpl( const 
SfxPoolItem& rItem, sal_uInt16 nWh
         return nullptr; //FIXME: Only because of Outliner bug
     }
 
-    SfxPoolItem const** ppFnd = m_ppItems;
-    for (const WhichPair& rPair : m_pWhichRanges)
+    const sal_uInt16 nOffset(m_pWhichRanges.getOffsetFromWhich(nWhich));
+
+    if (INVALID_WHICHPAIR_OFFSET != nOffset)
     {
-        if( rPair.first <= nWhich && nWhich <= rPair.second )
+        SfxPoolItem const** ppFnd(m_ppItems + nOffset);
+
+        if( *ppFnd ) // Already one present
         {
-            // Within this range
-            ppFnd += nWhich - rPair.first;
-            if( *ppFnd ) // Already one present
+            // Same Item already present?
+            if ( *ppFnd == &rItem )
             {
-                // Same Item already present?
-                if ( *ppFnd == &rItem )
-                {
-                    assert(!bPassingOwnership);
-                    return nullptr;
-                }
+                assert(!bPassingOwnership);
+                return nullptr;
+            }
 
-                // Will 'dontcare' or 'disabled' be overwritten with some real 
value?
-                if ( rItem.Which() && ( IsInvalidItem(*ppFnd) || 
!(*ppFnd)->Which() ) )
-                {
-                    auto const old = *ppFnd;
-                    *ppFnd = &m_pPool->PutImpl( rItem, nWhich, 
bPassingOwnership );
-                    if (!IsInvalidItem(old)) {
-                        assert(old->Which() == 0);
-                        delete old;
-                    }
-                    return *ppFnd;
+            // Will 'dontcare' or 'disabled' be overwritten with some real 
value?
+            if ( rItem.Which() && ( IsInvalidItem(*ppFnd) || 
!(*ppFnd)->Which() ) )
+            {
+                auto const old = *ppFnd;
+                *ppFnd = &m_pPool->PutImpl( rItem, nWhich, bPassingOwnership );
+                if (!IsInvalidItem(old)) {
+                    assert(old->Which() == 0);
+                    delete old;
                 }
+                return *ppFnd;
+            }
 
-                // Turns into disabled?
-                if( !rItem.Which() )
+            // Turns into disabled?
+            if( !rItem.Which() )
+            {
+                if (IsInvalidItem(*ppFnd) || (*ppFnd)->Which() != 0) {
+                    *ppFnd = rItem.Clone(m_pPool);
+                }
+                if (bPassingOwnership)
+                    delete &rItem;
+                return nullptr;
+            }
+            else
+            {
+                // Same value already present?
+                if ( rItem == **ppFnd )
                 {
-                    if (IsInvalidItem(*ppFnd) || (*ppFnd)->Which() != 0) {
-                        *ppFnd = rItem.Clone(m_pPool);
-                    }
                     if (bPassingOwnership)
                         delete &rItem;
                     return nullptr;
                 }
-                else
-                {
-                    // Same value already present?
-                    if ( rItem == **ppFnd )
-                    {
-                        if (bPassingOwnership)
-                            delete &rItem;
-                        return nullptr;
-                    }
 
-                    // Add the new one, remove the old one
-                    const SfxPoolItem& rNew = m_pPool->PutImpl( rItem, nWhich, 
bPassingOwnership );
-                    const SfxPoolItem* pOld = *ppFnd;
-                    *ppFnd = &rNew;
-                    if (SfxItemPool::IsWhich(nWhich))
-                        Changed( *pOld, rNew );
-                    m_pPool->Remove( *pOld );
-                }
+                // Add the new one, remove the old one
+                const SfxPoolItem& rNew = m_pPool->PutImpl( rItem, nWhich, 
bPassingOwnership );
+                const SfxPoolItem* pOld = *ppFnd;
+                *ppFnd = &rNew;
+                if (SfxItemPool::IsWhich(nWhich))
+                    Changed( *pOld, rNew );
+                m_pPool->Remove( *pOld );
+            }
+        }
+        else
+        {
+            ++m_nCount;
+            if( !rItem.Which() )
+            {
+                *ppFnd = rItem.Clone(m_pPool);
+                if (bPassingOwnership)
+                    delete &rItem;
             }
             else
             {
-                ++m_nCount;
-                if( !rItem.Which() )
-                {
-                    *ppFnd = rItem.Clone(m_pPool);
-                    if (bPassingOwnership)
-                        delete &rItem;
-                }
-                else
+                const SfxPoolItem& rNew = m_pPool->PutImpl( rItem, nWhich, 
bPassingOwnership );
+                *ppFnd = &rNew;
+                if (SfxItemPool::IsWhich(nWhich))
                 {
-                    const SfxPoolItem& rNew = m_pPool->PutImpl( rItem, nWhich, 
bPassingOwnership );
-                    *ppFnd = &rNew;
-                    if (SfxItemPool::IsWhich(nWhich))
-                    {
-                        const SfxPoolItem& rOld = m_pParent
-                            ? m_pParent->Get( nWhich )
-                            : m_pPool->GetDefaultItem( nWhich );
-                        Changed( rOld, rNew );
-                    }
+                    const SfxPoolItem& rOld = m_pParent
+                        ? m_pParent->Get( nWhich )
+                        : m_pPool->GetDefaultItem( nWhich );
+                    Changed( rOld, rNew );
                 }
             }
-            SAL_WARN_IF(!bPassingOwnership && m_pPool->IsItemPoolable(nWhich) 
&&
-                        dynamic_cast<const SfxSetItem*>( &rItem ) == nullptr &&
-                        **ppFnd != rItem,
-                        "svl.items", "putted Item unequal, with ID/pos " << 
nWhich );
-            return *ppFnd;
         }
-        ppFnd += rPair.second - rPair.first + 1;
+        SAL_WARN_IF(!bPassingOwnership && m_pPool->IsItemPoolable(nWhich) &&
+                    dynamic_cast<const SfxSetItem*>( &rItem ) == nullptr &&
+                    **ppFnd != rItem,
+                    "svl.items", "putted Item unequal, with ID/pos " << nWhich 
);
+        return *ppFnd;
     }
+
     if (bPassingOwnership)
         delete &rItem;
     return nullptr;
@@ -647,11 +632,11 @@ void SfxItemSet::SetRanges( WhichRangesContainer&& 
pNewRanges )
 void SfxItemSet::RecreateRanges_Impl(const WhichRangesContainer& pNewRanges)
 {
     // create new item-array (by iterating through all new ranges)
-    const auto nSize = svl::detail::CountRanges(pNewRanges);
-    SfxPoolItem const** aNewItems = new const SfxPoolItem* [ nSize ];
+    const sal_uInt16 nTotalCount(svl::detail::CountRanges(pNewRanges));
+    SfxPoolItem const** aNewItems = new const SfxPoolItem* [ nTotalCount ];
     sal_uInt16 nNewCount = 0;
     if (m_nCount == 0)
-        memset( aNewItems, 0, nSize * sizeof( SfxPoolItem* ) );
+        memset( aNewItems, 0, nTotalCount * sizeof( SfxPoolItem* ) );
     else
     {
         sal_uInt16 n = 0;
@@ -687,8 +672,7 @@ void SfxItemSet::RecreateRanges_Impl(const 
WhichRangesContainer& pNewRanges)
             }
         }
         // free old items
-        sal_uInt16 nOldTotalCount = TotalCount();
-        for ( sal_uInt16 nItem = 0; nItem < nOldTotalCount; ++nItem )
+        for ( sal_uInt16 nItem = 0; nItem < TotalCount(); ++nItem )
         {
             const SfxPoolItem *pItem = m_ppItems[nItem];
             if ( pItem && !IsInvalidItem(pItem) && pItem->Which() )
@@ -701,6 +685,7 @@ void SfxItemSet::RecreateRanges_Impl(const 
WhichRangesContainer& pNewRanges)
         m_bItemsFixed = false;
     else
         delete[] m_ppItems;
+    m_nTotalCount = nTotalCount;
     m_ppItems = aNewItems;
     m_nCount = nNewCount;
 }
@@ -795,46 +780,34 @@ const SfxPoolItem* SfxItemSet::GetItem(sal_uInt16 nId, 
bool bSearchInParent) con
 const SfxPoolItem& SfxItemSet::Get( sal_uInt16 nWhich, bool bSrchInParent) 
const
 {
     // Search the Range in which the Which is located in:
-    const SfxItemSet* pCurrentSet = this;
-    do
+    const sal_uInt16 nOffset(m_pWhichRanges.getOffsetFromWhich(nWhich));
+
+    if (INVALID_WHICHPAIR_OFFSET != nOffset)
     {
-        if( pCurrentSet->Count() )
+        SfxPoolItem const** ppFnd(m_ppItems + nOffset);
+
+        if( *ppFnd )
         {
-            SfxPoolItem const** ppFnd = pCurrentSet->m_ppItems;
-            for (auto const & pPtr : pCurrentSet->m_pWhichRanges)
-            {
-                if( pPtr.first <= nWhich && nWhich <= pPtr.second )
-                {
-                    // In this Range
-                    ppFnd += nWhich - pPtr.first;
-                    if( *ppFnd )
-                    {
-                        if( IsInvalidItem(*ppFnd) ) {
-                            //FIXME: The following code is duplicated further 
down
-                            assert(m_pPool);
-                            //!((SfxAllItemSet 
*)this)->aDefault.SetWhich(nWhich);
-                            //!return aDefault;
-                            return m_pPool->GetDefaultItem( nWhich );
-                        }
+            if( IsInvalidItem(*ppFnd) ) {
+                //FIXME: The following code is duplicated further down
+                assert(m_pPool);
+                //!((SfxAllItemSet *)this)->aDefault.SetWhich(nWhich);
+                //!return aDefault;
+                return m_pPool->GetDefaultItem( nWhich );
+            }
 #ifdef DBG_UTIL
-                        const SfxPoolItem *pItem = *ppFnd;
-                        if ( pItem->IsVoidItem() || !pItem->Which() )
-                            SAL_INFO("svl.items", "SFX_WARNING: Getting 
disabled Item");
+            const SfxPoolItem *pItem = *ppFnd;
+            if ( pItem->IsVoidItem() || !pItem->Which() )
+                SAL_INFO("svl.items", "SFX_WARNING: Getting disabled Item");
 #endif
-                        return **ppFnd;
-                    }
-                    break; // Continue with Parent
-                }
-                ppFnd += pPtr.second - pPtr.first + 1;
-            }
+            return **ppFnd;
         }
-//TODO: Search until end of Range: What are we supposed to do now? To the 
Parent or Default??
-//      if( !*pPtr )            // Until the end of the search Range?
-//      break;
-        if (!bSrchInParent)
-            break;
-        pCurrentSet = pCurrentSet->m_pParent;
-    } while (nullptr != pCurrentSet);
+    }
+
+    if (bSrchInParent && nullptr != m_pParent)
+    {
+        return m_pParent->Get( nWhich, bSrchInParent);
+    }
 
     // Get the Default from the Pool and return
     assert(m_pPool);
@@ -848,11 +821,6 @@ void SfxItemSet::Changed( const SfxPoolItem&, const 
SfxPoolItem& )
 {
 }
 
-sal_uInt16 SfxItemSet::TotalCount() const
-{
-    return svl::detail::CountRanges(m_pWhichRanges);
-}
-
 /**
  * Only retain the Items that are also present in rSet
  * (nevermind their value).
@@ -873,7 +841,7 @@ void SfxItemSet::Intersect( const SfxItemSet& rSet )
     // If the Ranges are identical, we can easily process it
     if( m_pWhichRanges == rSet.m_pWhichRanges )
     {
-        sal_uInt16 nSize = TotalCount();
+        sal_uInt16 nSize(TotalCount());
         SfxPoolItem const** ppFnd1 = m_ppItems;
         SfxPoolItem const** ppFnd2 = rSet.m_ppItems;
 
@@ -919,7 +887,7 @@ void SfxItemSet::Differentiate( const SfxItemSet& rSet )
     // If the Ranges are identical, we can easily process it
     if( m_pWhichRanges == rSet.m_pWhichRanges )
     {
-        sal_uInt16 nSize = TotalCount();
+        sal_uInt16 nSize(TotalCount());
         SfxPoolItem const** ppFnd1 = m_ppItems;
         SfxPoolItem const** ppFnd2 = rSet.m_ppItems;
 
@@ -1108,7 +1076,7 @@ void SfxItemSet::MergeValues( const SfxItemSet& rSet )
     // If the Ranges match, they are easier to process!
     if( m_pWhichRanges == rSet.m_pWhichRanges )
     {
-        sal_uInt16 nSize = TotalCount();
+        sal_uInt16 nSize(TotalCount());
         SfxPoolItem const** ppFnd1 = m_ppItems;
         SfxPoolItem const** ppFnd2 = rSet.m_ppItems;
 
@@ -1156,45 +1124,44 @@ void SfxItemSet::MergeValue( const SfxPoolItem& rAttr, 
bool bIgnoreDefaults )
 
 void SfxItemSet::InvalidateItem( sal_uInt16 nWhich )
 {
-    SfxPoolItem const** ppFnd = m_ppItems;
-    for( auto const & pPtr : m_pWhichRanges )
+    const sal_uInt16 nOffset(m_pWhichRanges.getOffsetFromWhich(nWhich));
+
+    if (INVALID_WHICHPAIR_OFFSET != nOffset)
     {
-        if( pPtr.first <= nWhich && nWhich <= pPtr.second )
-        {
-            // In this Range?
-            ppFnd += nWhich - pPtr.first;
+        SfxPoolItem const** ppFnd(m_ppItems + nOffset);
 
-            if( *ppFnd ) // Set for me
-            {
-                if( !IsInvalidItem(*ppFnd) )
-                {
-                    m_pPool->Remove( **ppFnd );
-                    *ppFnd = INVALID_POOL_ITEM;
-                }
-            }
-            else
+        if( *ppFnd ) // Set for me
+        {
+            if( !IsInvalidItem(*ppFnd) )
             {
+                m_pPool->Remove( **ppFnd );
                 *ppFnd = INVALID_POOL_ITEM;
-                ++m_nCount;
             }
-            break;
         }
-        ppFnd += pPtr.second - pPtr.first + 1;
+        else
+        {
+            *ppFnd = INVALID_POOL_ITEM;
+            ++m_nCount;
+        }
     }
 }
 
-sal_uInt16 SfxItemSet::GetWhichByPos( sal_uInt16 nPos ) const
+sal_uInt16 SfxItemSet::GetWhichByOffset( sal_uInt16 nOffset ) const
 {
-    sal_uInt16 n = 0;
-    for( auto const & pPtr : m_pWhichRanges )
-    {
-        n = ( pPtr.second - pPtr.first ) + 1;
-        if( nPos < n )
-            return pPtr.first + nPos;
-        nPos = nPos - n;
-    }
-    assert(false);
-    return 0;
+    assert(nOffset < TotalCount());
+
+    // 1st try to get a set SfxPoolItem and fetch the WhichID from there.
+    const SfxPoolItem* pItem(nullptr);
+    GetItemState_ForOffset(nOffset, &pItem);
+
+    if (nullptr != pItem)
+        return pItem->Which();
+
+    // 2nd have to get from WhichRangesContainer. That might use
+    // the buffering, too. We might assert a return value of zero
+    // (which means invalid WhichID), but we already assert for
+    // a valid offset at the start of this method
+    return m_pWhichRanges.getWhichFromOffset(nOffset);
 }
 
 bool SfxItemSet::operator==(const SfxItemSet &rCmp) const
@@ -1214,8 +1181,8 @@ bool SfxItemSet::Equals(const SfxItemSet &rCmp, bool 
bComparePool) const
     // If we reach here and bDifferentPools==true that means 
bComparePool==false.
 
     // Counting Ranges takes longer; they also need to be the same, however
-    sal_uInt16 nCount1 = TotalCount();
-    sal_uInt16 nCount2 = rCmp.TotalCount();
+    const sal_uInt16 nCount1(TotalCount());
+    const sal_uInt16 nCount2(rCmp.TotalCount());
     if ( nCount1 != nCount2 )
         return false;
 
@@ -1326,41 +1293,34 @@ SfxItemSet SfxItemSet::CloneAsValue(bool bItems, 
SfxItemPool *pToPool ) const
 
 void SfxItemSet::PutDirect(const SfxPoolItem &rItem)
 {
-    SfxPoolItem const** ppFnd = m_ppItems;
-    const sal_uInt16 nWhich = rItem.Which();
 #ifdef DBG_UTIL
     IsPoolDefaultItem(&rItem) || m_pPool->CheckItemInPool(&rItem);
         // Only cause assertion in the callees
 #endif
-    for( auto const & pPtr : m_pWhichRanges)
+    const sal_uInt16 nOffset(m_pWhichRanges.getOffsetFromWhich(rItem.Which()));
+
+    if (INVALID_WHICHPAIR_OFFSET != nOffset)
     {
-        if( pPtr.first <= nWhich && nWhich <= pPtr.second )
+        SfxPoolItem const** ppFnd(m_ppItems + nOffset);
+        const SfxPoolItem* pOld = *ppFnd;
+        if( pOld ) // One already present
         {
-            // In this Range?
-            ppFnd += nWhich - pPtr.first;
-            const SfxPoolItem* pOld = *ppFnd;
-            if( pOld ) // One already present
-            {
-                if( rItem == **ppFnd )
-                    return; // Already present!
-                m_pPool->Remove( *pOld );
-            }
-            else
-                ++m_nCount;
-
-            // Add the new one
-            if( IsPoolDefaultItem(&rItem) )
-                *ppFnd = &m_pPool->Put( rItem );
-            else
-            {
-                *ppFnd = &rItem;
-                if( !IsStaticDefaultItem( &rItem ) )
-                    rItem.AddRef();
-            }
+            if( rItem == **ppFnd )
+                return; // Already present!
+            m_pPool->Remove( *pOld );
+        }
+        else
+            ++m_nCount;
 
-            return;
+        // Add the new one
+        if( IsPoolDefaultItem(&rItem) )
+            *ppFnd = &m_pPool->Put( rItem );
+        else
+        {
+            *ppFnd = &rItem;
+            if( !IsStaticDefaultItem( &rItem ) )
+                rItem.AddRef();
         }
-        ppFnd += pPtr.second - pPtr.first + 1;
     }
 }
 
@@ -1445,6 +1405,9 @@ WhichRangesContainer::WhichRangesContainer( const 
WhichPair* wids, sal_Int32 nSi
     m_pairs = p;
     m_size = nSize;
     m_bOwnRanges = true;
+    m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+    m_aLastWhichPairFirst = 0;
+    m_aLastWhichPairSecond = 0;
 }
 
 WhichRangesContainer::WhichRangesContainer(sal_uInt16 nWhichStart, sal_uInt16 
nWhichEnd)
@@ -1453,6 +1416,9 @@ WhichRangesContainer::WhichRangesContainer(sal_uInt16 
nWhichStart, sal_uInt16 nW
     auto p = new WhichPair[1];
     p[0] = { nWhichStart, nWhichEnd };
     m_pairs = p;
+    m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+    m_aLastWhichPairFirst = 0;
+    m_aLastWhichPairSecond = 0;
 }
 
 WhichRangesContainer::WhichRangesContainer(WhichRangesContainer && other)
@@ -1460,6 +1426,9 @@ 
WhichRangesContainer::WhichRangesContainer(WhichRangesContainer && other)
     std::swap(m_pairs, other.m_pairs);
     std::swap(m_size, other.m_size);
     std::swap(m_bOwnRanges, other.m_bOwnRanges);
+    m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+    m_aLastWhichPairFirst = 0;
+    m_aLastWhichPairSecond = 0;
 }
 
 WhichRangesContainer& WhichRangesContainer::operator=(WhichRangesContainer && 
other)
@@ -1467,6 +1436,9 @@ WhichRangesContainer& 
WhichRangesContainer::operator=(WhichRangesContainer && ot
     std::swap(m_pairs, other.m_pairs);
     std::swap(m_size, other.m_size);
     std::swap(m_bOwnRanges, other.m_bOwnRanges);
+    m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+    m_aLastWhichPairFirst = 0;
+    m_aLastWhichPairSecond = 0;
     return *this;
 }
 
@@ -1475,6 +1447,9 @@ WhichRangesContainer& 
WhichRangesContainer::operator=(WhichRangesContainer const
     reset();
     m_size = other.m_size;
     m_bOwnRanges = other.m_bOwnRanges;
+    m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+    m_aLastWhichPairFirst = 0;
+    m_aLastWhichPairSecond = 0;
     if (m_bOwnRanges)
     {
         auto p = new WhichPair[m_size];
@@ -1511,6 +1486,136 @@ void WhichRangesContainer::reset()
     }
     m_pairs = nullptr;
     m_size = 0;
+    m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+    m_aLastWhichPairFirst = 0;
+    m_aLastWhichPairSecond = 0;
+}
+
+#ifdef DBG_UTIL
+static size_t g_nHit(0);
+static size_t g_nMiss(1);
+static bool g_bShowWhichRangesHitRate(getenv("SVL_SHOW_WHICHRANGES_HITRATE"));
+static void isHit() { g_nHit++; }
+static void isMiss()
+{
+    g_nMiss++;
+    const double fHitRate(double(g_nHit) /double(g_nMiss));
+    if (0 == g_nMiss % 1000 && g_bShowWhichRangesHitRate)
+        SAL_WARN("svl", "ITEM: hits: " << g_nHit << " misses: " << g_nMiss << 
" hits/misses(rate): " << fHitRate);
+}
+#endif
+
+sal_uInt16 WhichRangesContainer::getOffsetFromWhich(sal_uInt16 nWhich) const
+{
+    if (empty())
+        return INVALID_WHICHPAIR_OFFSET;
+
+    // special case for single entry - happens often e.g. UI stuff
+    if (1 == m_size)
+    {
+        if( m_pairs->first <= nWhich && nWhich <= m_pairs->second )
+            return nWhich - m_pairs->first;
+
+        // we have only one WhichPair entry and it's not contained -> failed
+        return INVALID_WHICHPAIR_OFFSET;
+    }
+
+    // check if nWhich is inside last sucessfully used WhichPair
+    if (INVALID_WHICHPAIR_OFFSET != m_aLastWhichPairOffset
+        && m_aLastWhichPairFirst <= nWhich
+        && nWhich <= m_aLastWhichPairSecond)
+    {
+#ifdef DBG_UTIL
+        isHit();
+#endif
+        // we can re-use the last found WhichPair
+        return m_aLastWhichPairOffset + (nWhich - m_aLastWhichPairFirst);
+    }
+
+#ifdef DBG_UTIL
+    isMiss();
+#endif
+
+    // we have to find the correct WhichPair, iterate linear. This
+    // also directly updates the buffered m_aLastWhichPair* values
+    m_aLastWhichPairOffset = 0;
+
+    for (const WhichPair& rPair : *this)
+    {
+        // Within this range?
+        if( rPair.first <= nWhich && nWhich <= rPair.second )
+        {
+            // found, remember parameters for buffered hits
+            m_aLastWhichPairFirst = rPair.first;
+            m_aLastWhichPairSecond = rPair.second;
+
+            // ...and return
+            return m_aLastWhichPairOffset + (nWhich - m_aLastWhichPairFirst);
+        }
+
+        m_aLastWhichPairOffset += rPair.second - rPair.first + 1;
+    }
+
+    // *need* to reset: if 1st WhichPair only one entry it could be 1
+    // what could wrongly trigger re-use above for next search
+    m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+
+    return m_aLastWhichPairOffset;
+}
+
+sal_uInt16 WhichRangesContainer::getWhichFromOffset(sal_uInt16 nOffset) const
+{
+    // check for empty, if yes, return null which is an invalid WhichID
+    if (empty())
+        return 0;
+
+    // special case for single entry - happens often e.g. UI stuff
+    if (1 == m_size)
+    {
+        if (nOffset <= m_pairs->second - m_pairs->first)
+            return m_pairs->first + nOffset;
+
+        // we have only one WhichPair entry and it's not contained -> failed
+        return 0;
+    }
+
+    // check if nWhich is inside last sucessfully used WhichPair
+    if (INVALID_WHICHPAIR_OFFSET != m_aLastWhichPairOffset)
+    {
+        // only try if we are beyond or at m_aLastWhichPairOffset to
+        // not get numerically negative
+        if (nOffset >= m_aLastWhichPairOffset)
+        {
+            const sal_uInt16 nAdaptedOffset(nOffset - m_aLastWhichPairOffset);
+
+            if (nAdaptedOffset <= m_aLastWhichPairSecond - 
m_aLastWhichPairFirst)
+            {
+#ifdef DBG_UTIL
+                isHit();
+#endif
+                return m_aLastWhichPairFirst + nAdaptedOffset;
+            }
+        }
+    }
+
+#ifdef DBG_UTIL
+    isMiss();
+#endif
+
+    // Iterate over WhichPairs in WhichRangesContainer
+    // Do not update buffered last hit (m_aLastWhichPair*), these calls
+    // are potetially more rare than getOffsetFromWhich calls. Still,
+    // it could also be done here
+    for( auto const & pPtr : *this )
+    {
+        const sal_uInt16 nWhichPairRange(pPtr.second - pPtr.first);
+        if( nOffset <= nWhichPairRange )
+            return pPtr.first + nOffset;
+        nOffset -= nWhichPairRange + 1;
+    }
+
+    // no WhichID found, return invalid one
+    return 0;
 }
 
 // Adds a range to which ranges, keeping the ranges in valid state (sorted, 
non-overlapping)
@@ -1522,6 +1627,8 @@ WhichRangesContainer 
WhichRangesContainer::MergeRange(sal_uInt16 nFrom,
     if (empty())
         return WhichRangesContainer(nFrom, nTo);
 
+    m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+
     // create vector of ranges (sal_uInt16 pairs of lower and upper bound)
     const size_t nOldCount = size();
     // Allocate one item more than we already have.
diff --git a/svl/source/items/whiter.cxx b/svl/source/items/whiter.cxx
index c89c6ed2794b..13915415df08 100644
--- a/svl/source/items/whiter.cxx
+++ b/svl/source/items/whiter.cxx
@@ -65,16 +65,29 @@ sal_uInt16 SfxWhichIter::FirstWhich()
 
 SfxItemState SfxWhichIter::GetItemState(bool bSrchInParent, const 
SfxPoolItem** ppItem) const
 {
-    sal_uInt16 nWhich = m_pCurrentWhichPair->first + 
m_nOffsetFromStartOfCurrentWhichPair;
-    sal_uInt16 nItemsOffsetHint = m_nItemsOffset + 
m_nOffsetFromStartOfCurrentWhichPair;
-    return m_rItemSet.GetItemStateImpl(nWhich, bSrchInParent, ppItem, 
nItemsOffsetHint);
+    const sal_uInt16 nOffset(m_nItemsOffset + 
m_nOffsetFromStartOfCurrentWhichPair);
+
+    // we have the offset, so use it to profit. It is always valid, so no need
+    // to check if smaller than TotalCount()
+    SfxItemState eState(m_rItemSet.GetItemState_ForOffset(nOffset, ppItem));
+
+    // search in parent?
+    if (bSrchInParent && nullptr != m_rItemSet.GetParent() && 
(SfxItemState::UNKNOWN == eState || SfxItemState::DEFAULT == eState))
+    {
+        // nOffset was only valid for *local* SfxItemSet, need to continue 
with WhichID
+        // Use the *highest* SfxItemState as result
+        const sal_uInt16 nWhich(m_pCurrentWhichPair->first + 
m_nOffsetFromStartOfCurrentWhichPair);
+        return m_rItemSet.GetParent()->GetItemState_ForWhichID( eState, 
nWhich, true, ppItem);
+    }
+
+    return eState;
 }
 
 void SfxWhichIter::ClearItem()
 {
-    sal_uInt16 nWhich = m_pCurrentWhichPair->first + 
m_nOffsetFromStartOfCurrentWhichPair;
-    sal_uInt16 nItemOffsetHint = m_nItemsOffset + 
m_nOffsetFromStartOfCurrentWhichPair;
-    const_cast<SfxItemSet&>(m_rItemSet).ClearSingleItemImpl(nWhich, 
nItemOffsetHint);
+    // we have the offset, so use it to profit. It is always valid, so no need
+    // to check if smaller than TotalCount()
+    
const_cast<SfxItemSet&>(m_rItemSet).ClearSingleItem_ForOffset(m_nItemsOffset + 
m_nOffsetFromStartOfCurrentWhichPair);
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/source/dialog/srchdlg.cxx b/svx/source/dialog/srchdlg.cxx
index 1e0bcd36a944..7f496535e2b7 100644
--- a/svx/source/dialog/srchdlg.cxx
+++ b/svx/source/dialog/srchdlg.cxx
@@ -211,7 +211,7 @@ void SearchAttrItemList::Put( const SfxItemSet& rSet )
         // only test that it is available?
         if( IsInvalidItem( pItem ) )
         {
-            nWhich = rSet.GetWhichByPos( aIter.GetCurPos() );
+            nWhich = rSet.GetWhichByOffset( aIter.GetCurPos() );
             aItem.pItem = const_cast<SfxPoolItem*>(pItem);
         }
         else
diff --git a/sw/source/core/crsr/findattr.cxx b/sw/source/core/crsr/findattr.cxx
index 29c5c8f7ef71..9492cfdd6c70 100644
--- a/sw/source/core/crsr/findattr.cxx
+++ b/sw/source/core/crsr/findattr.cxx
@@ -221,8 +221,8 @@ SwAttrCheckArr::SwAttrCheckArr( const SfxItemSet& rSet, 
bool bFwd,
 
     // determine area of Fnd/Stack array (Min/Max)
     SfxItemIter aIter( m_aComapeSet );
-    m_nArrStart = m_aComapeSet.GetWhichByPos( aIter.GetFirstPos() );
-    m_nArrLen = m_aComapeSet.GetWhichByPos( aIter.GetLastPos() ) - 
m_nArrStart+1;
+    m_nArrStart = m_aComapeSet.GetWhichByOffset( aIter.GetFirstPos() );
+    m_nArrLen = m_aComapeSet.GetWhichByOffset( aIter.GetLastPos() ) - 
m_nArrStart+1;
 
     char* pFndChar  = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ];
     char* pStackChar = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ];
@@ -273,7 +273,7 @@ void SwAttrCheckArr::SetNewSet( const SwTextNode& rTextNd, 
const SwPaM& rPam )
     {
         if( IsInvalidItem( pItem ) )
         {
-            nWhich = m_aComapeSet.GetWhichByPos( aIter.GetCurPos() );
+            nWhich = m_aComapeSet.GetWhichByOffset( aIter.GetCurPos() );
             if( RES_TXTATR_END <= nWhich )
                 break; // end of text attributes
 
@@ -887,7 +887,7 @@ static bool lcl_Search( const SwContentNode& rCNd, const 
SfxItemSet& rCmpSet, bo
     {
         if( IsInvalidItem( pItem ))
         {
-            nWhich = rCmpSet.GetWhichByPos( aIter.GetCurPos() );
+            nWhich = rCmpSet.GetWhichByOffset( aIter.GetCurPos() );
             if( SfxItemState::SET != rNdSet.GetItemState( nWhich, !bNoColls, 
&pNdItem )
                 || CmpAttr( *pNdItem, rNdSet.GetPool()->GetDefaultItem( nWhich 
) ))
                 return false;
diff --git a/sw/source/core/doc/DocumentRedlineManager.cxx 
b/sw/source/core/doc/DocumentRedlineManager.cxx
index 898590d6201b..75c5ba1ccc10 100644
--- a/sw/source/core/doc/DocumentRedlineManager.cxx
+++ b/sw/source/core/doc/DocumentRedlineManager.cxx
@@ -436,7 +436,7 @@ namespace
             {
                 for( sal_uInt16 nItem = 0; nItem < aTmp.TotalCount(); ++nItem)
                 {
-                    sal_uInt16 nWhich = aTmp.GetWhichByPos(nItem);
+                    sal_uInt16 nWhich = aTmp.GetWhichByOffset(nItem);
                     if( SfxItemState::SET == aTmp.GetItemState( nWhich, false 
) &&
                         SfxItemState::SET != aTmp2.GetItemState( nWhich, false 
) )
                             aTmp2.Put( aTmp.GetPool()->GetDefaultItem(nWhich), 
nWhich );
diff --git a/sw/source/core/txtnode/ndtxt.cxx b/sw/source/core/txtnode/ndtxt.cxx
index 8eea1b74f11e..19a2bffbf562 100644
--- a/sw/source/core/txtnode/ndtxt.cxx
+++ b/sw/source/core/txtnode/ndtxt.cxx
@@ -2568,7 +2568,7 @@ void SwTextNode::CutImpl( SwTextNode * const pDest, const 
SwContentIndex & rDest
                 {
                     // check current item
                     const sal_uInt16 nWhich = IsInvalidItem( pItem )
-                        ? pDest->GetpSwAttrSet()->GetWhichByPos( 
aIter.GetCurPos() )
+                        ? pDest->GetpSwAttrSet()->GetWhichByOffset( 
aIter.GetCurPos() )
                         : pItem->Which();
                     if( RES_FRMATR_STYLE_NAME != nWhich &&
                         RES_FRMATR_CONDITIONAL_STYLE_NAME != nWhich &&
diff --git a/sw/source/core/undo/undobj1.cxx b/sw/source/core/undo/undobj1.cxx
index 6db3d9315114..781c5555a6f6 100644
--- a/sw/source/core/undo/undobj1.cxx
+++ b/sw/source/core/undo/undobj1.cxx
@@ -572,7 +572,7 @@ void SwUndoSetFlyFormat::UndoImpl(::sw::UndoRedoContext & 
rContext)
     for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = 
aIter.NextItem())
     {
         if( IsInvalidItem( pItem ))
-            m_pFrameFormat->ResetFormatAttr( m_oItemSet->GetWhichByPos(
+            m_pFrameFormat->ResetFormatAttr( m_oItemSet->GetWhichByOffset(
                                     aIter.GetCurPos() ));
         else
             m_pFrameFormat->SetFormatAttr( *pItem );
diff --git a/sw/source/filter/ww8/writerhelper.cxx 
b/sw/source/filter/ww8/writerhelper.cxx
index 528859565667..86cf8d622200 100644
--- a/sw/source/filter/ww8/writerhelper.cxx
+++ b/sw/source/filter/ww8/writerhelper.cxx
@@ -385,7 +385,7 @@ namespace sw
                 for( sal_uInt16 nItem =0; nItem < nTotal; ++nItem )
                 {
                     const SfxPoolItem* pItem = nullptr;
-                    if( SfxItemState::SET == rSet.GetItemState( 
rSet.GetWhichByPos( nItem ), true, &pItem ) )
+                    if( SfxItemState::SET == rSet.GetItemState( 
rSet.GetWhichByOffset( nItem ), true, &pItem ) )
                     {
                         rItems[pItem->Which()] = pItem;
                     }
diff --git a/sw/source/uibase/app/docstyle.cxx 
b/sw/source/uibase/app/docstyle.cxx
index 456aae85c5e9..52f9253252b0 100644
--- a/sw/source/uibase/app/docstyle.cxx
+++ b/sw/source/uibase/app/docstyle.cxx
@@ -1848,7 +1848,7 @@ void SwDocStyleSheet::SetItemSet( const SfxItemSet& rSet, 
const bool bBroadcast,
             {
                 // use method <SwDoc::ResetAttrAtFormat(..)> in order to
                 // create an Undo object for the attribute reset.
-                
aWhichIdsToReset.emplace_back(rSet.GetWhichByPos(aIter.GetCurPos()));
+                
aWhichIdsToReset.emplace_back(rSet.GetWhichByOffset(aIter.GetCurPos()));
             }
 
             pItem = aIter.NextItem();
diff --git a/sw/source/uibase/uiview/srcview.cxx 
b/sw/source/uibase/uiview/srcview.cxx
index 0a936b67f311..8ebae23d3d4b 100644
--- a/sw/source/uibase/uiview/srcview.cxx
+++ b/sw/source/uibase/uiview/srcview.cxx
@@ -334,7 +334,7 @@ void SwSrcView::Execute(SfxRequest& rReq)
         {
             const SfxItemSet* pTmpArgs = rReq.GetArgs();
 
-            const sal_uInt16 nWhich = pTmpArgs->GetWhichByPos( 0 );
+            const sal_uInt16 nWhich = pTmpArgs->GetWhichByOffset( 0 );
             OSL_ENSURE( nWhich, "Which for SearchItem ?" );
             const SfxPoolItem& rItem = pTmpArgs->Get( nWhich );
             SetSearchItem( static_cast<const SvxSearchItem&>(rItem));

Reply via email to