include/linguistic/misc.hxx     |    4 +-
 include/linguistic/spelldta.hxx |    3 +
 linguistic/source/dlistimp.cxx  |    3 +
 linguistic/source/misc.cxx      |   65 +++++++++++++++++++++++++---------------
 linguistic/source/spelldsp.cxx  |   26 ++++++++--------
 linguistic/source/spelldsp.hxx  |    1 
 linguistic/source/spelldta.cxx  |    5 +--
 7 files changed, 67 insertions(+), 40 deletions(-)

New commits:
commit b2da15234473c8bda598813c707efb7038c12840
Author:     Noel Grandin <[email protected]>
AuthorDate: Wed Jan 28 11:27:09 2026 +0200
Commit:     Noel Grandin <[email protected]>
CommitDate: Thu Jan 29 12:10:54 2026 +0100

    tdf#148218 reduce OString alloc in dictionary searching
    
    cache the information we need from the dictionary, so we avoid repeatedly 
calling
    LinguLocaleToLanguage in SearchDicList for every single word we spellcheck.
    
    Shaves 17% of the temporary allocations
    
    Change-Id: Ie28dab4224a35ec703ff7f698e9ea89775594c74
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198281
    Reviewed-by: Noel Grandin <[email protected]>
    Tested-by: Jenkins

diff --git a/include/linguistic/misc.hxx b/include/linguistic/misc.hxx
index f2db0d3ad5ed..eda5152701de 100644
--- a/include/linguistic/misc.hxx
+++ b/include/linguistic/misc.hxx
@@ -32,6 +32,7 @@
 #include <rtl/ref.hxx>
 #include <linguistic/lngdllapi.h>
 
+#include <map>
 #include <vector>
 
 namespace com::sun::star::beans { class XPropertySet; }
@@ -154,7 +155,8 @@ css::uno::Reference<
         SearchDicList(
             const css::uno::Reference< 
css::linguistic2::XSearchableDictionaryList >& rDicList,
             const OUString& rWord, LanguageType nLanguage,
-            bool bSearchPosDics, bool bSearchSpellEntry );
+            bool bSearchPosDics, bool bSearchSpellEntry,
+            std::map<LanguageType, 
std::vector<css::uno::Reference<css::linguistic2::XDictionary>>>& 
rDictionaryMap );
 
 LNG_DLLPUBLIC DictionaryError AddEntryToDic(
     css::uno::Reference< css::linguistic2::XDictionary > const &rxDic,
diff --git a/include/linguistic/spelldta.hxx b/include/linguistic/spelldta.hxx
index d0057ffbd426..921a832936d5 100644
--- a/include/linguistic/spelldta.hxx
+++ b/include/linguistic/spelldta.hxx
@@ -42,7 +42,8 @@ std::vector< OUString >
 void    SeqRemoveNegEntries(
                 std::vector< OUString > &rSeq,
                 css::uno::Reference< 
css::linguistic2::XSearchableDictionaryList > const &rxDicList,
-                LanguageType nLanguage );
+                LanguageType nLanguage,
+                std::map<LanguageType, 
std::vector<css::uno::Reference<css::linguistic2::XDictionary>>>& 
rDictionaryMap );
 
 void SearchSimilarText( const OUString &rText, LanguageType nLanguage,
         css::uno::Reference< css::linguistic2::XSearchableDictionaryList > 
const &xDicList,
diff --git a/linguistic/source/dlistimp.cxx b/linguistic/source/dlistimp.cxx
index 56c24db1d4df..6157acea51b5 100644
--- a/linguistic/source/dlistimp.cxx
+++ b/linguistic/source/dlistimp.cxx
@@ -509,8 +509,9 @@ uno::Reference< XDictionaryEntry > SAL_CALL
             sal_Bool bSearchPosDics, sal_Bool bSearchSpellEntry )
 {
     osl::MutexGuard aGuard( GetLinguMutex() );
+    std::map<LanguageType, 
std::vector<css::uno::Reference<css::linguistic2::XDictionary>>> aDictionaryMap;
     return SearchDicList( this, rWord, LinguLocaleToLanguage( rLocale ),
-                            bSearchPosDics, bSearchSpellEntry );
+                            bSearchPosDics, bSearchSpellEntry, aDictionaryMap 
);
 }
 
 
diff --git a/linguistic/source/misc.cxx b/linguistic/source/misc.cxx
index 9b1909ca5cf7..2969ed6c5f28 100644
--- a/linguistic/source/misc.cxx
+++ b/linguistic/source/misc.cxx
@@ -18,6 +18,7 @@
  */
 
 #include <memory>
+#include <map>
 #include <optional>
 #include <sal/log.hxx>
 #include <svl/lngmisc.hxx>
@@ -250,39 +251,57 @@ static bool lcl_HasHyphInfo( const 
uno::Reference<XDictionaryEntry> &xEntry )
 uno::Reference< XDictionaryEntry > SearchDicList(
         const uno::Reference< XSearchableDictionaryList > &xDicList,
         const OUString &rWord, LanguageType nLanguage,
-        bool bSearchPosDics, bool bSearchSpellEntry )
+        bool bSearchPosDics, bool bSearchSpellEntry,
+        std::map<LanguageType, 
std::vector<css::uno::Reference<css::linguistic2::XDictionary>>>& 
rDictionaryMap )
 {
     MutexGuard  aGuard( GetLinguMutex() );
 
-    uno::Reference< XDictionaryEntry > xEntry;
-
     if (!xDicList.is())
-        return xEntry;
-
-    const uno::Sequence< uno::Reference< XDictionary > >
-            aDics( xDicList->getDictionaries() );
+        return uno::Reference< XDictionaryEntry >();
 
-    for (const uno::Reference<XDictionary>& axDic : aDics)
+    if (rDictionaryMap.empty())
     {
-        DictionaryType  eType = axDic->getDictionaryType();
-        LanguageType    nLang = LinguLocaleToLanguage( axDic->getLocale() );
-
-        if ( axDic.is() && axDic->isActive()
-            && (nLang == nLanguage  ||  LinguIsUnspecified( nLang)) )
+        const uno::Sequence< uno::Reference< XDictionary > >
+                aDics( xDicList->getDictionaries() );
+        for (const uno::Reference<XDictionary>& rxDic : aDics)
         {
-            // DictionaryType_MIXED is deprecated
-            SAL_WARN_IF(eType == DictionaryType_MIXED, "linguistic", 
"unexpected dictionary type");
+            assert( rxDic.is() );
+            LanguageType nLang = LinguLocaleToLanguage( rxDic->getLocale() );
+            rDictionaryMap[nLang].push_back(rxDic);
+        }
+    }
 
-            if (   (!bSearchPosDics  &&  eType == DictionaryType_NEGATIVE)
-                || ( bSearchPosDics  &&  eType == DictionaryType_POSITIVE))
+    auto lcl_search = [&rDictionaryMap, bSearchPosDics, bSearchSpellEntry, 
&rWord](LanguageType nLang)
+    {
+        auto it = rDictionaryMap.find(nLang);
+        if (it != rDictionaryMap.end())
+            for (const uno::Reference<XDictionary>& rxDic : it->second)
             {
-                xEntry = axDic->getEntry( rWord );
-                if ( xEntry.is() && (bSearchSpellEntry || lcl_HasHyphInfo( 
xEntry )) )
-                    break;
-                xEntry = nullptr;
+                if ( rxDic->isActive() )
+                {
+                    DictionaryType  eType = rxDic->getDictionaryType();
+                    if (   (!bSearchPosDics  &&  eType == 
DictionaryType_NEGATIVE)
+                        || ( bSearchPosDics  &&  eType == 
DictionaryType_POSITIVE))
+                    {
+                        uno::Reference< XDictionaryEntry > xEntry = 
rxDic->getEntry( rWord );
+                        if ( xEntry.is() && (bSearchSpellEntry || 
lcl_HasHyphInfo( xEntry )) )
+                            return xEntry;
+                    }
+                }
             }
-        }
-    }
+        return uno::Reference< XDictionaryEntry >();
+    };
+
+    uno::Reference< XDictionaryEntry > xEntry;
+    if ((xEntry = lcl_search(nLanguage)))
+        return xEntry;
+
+    // check the same list as in the LinguIsUnspecified() function
+    if ((xEntry = lcl_search(LANGUAGE_NONE)))
+        return xEntry;
+    if ((xEntry = lcl_search(LANGUAGE_UNDETERMINED)))
+        return xEntry;
+    xEntry = lcl_search(LANGUAGE_MULTIPLE);
 
     return xEntry;
 }
diff --git a/linguistic/source/spelldsp.cxx b/linguistic/source/spelldsp.cxx
index 3ef247f6e788..6c50a4c9c65a 100644
--- a/linguistic/source/spelldsp.cxx
+++ b/linguistic/source/spelldsp.cxx
@@ -232,7 +232,9 @@ Reference< XSpellAlternatives > SAL_CALL
 // including the IgnoreAll list
 static Reference< XDictionaryEntry > lcl_GetRulingDictionaryEntry(
     const OUString &rWord,
-    LanguageType nLanguage )
+    LanguageType nLanguage,
+    const Reference< XSearchableDictionaryList >& xDList,
+    std::map<LanguageType, 
std::vector<css::uno::Reference<css::linguistic2::XDictionary>>>& 
rDictionaryMap )
 {
     Reference< XDictionaryEntry > xRes;
 
@@ -244,15 +246,14 @@ static Reference< XDictionaryEntry > 
lcl_GetRulingDictionaryEntry(
         xRes = xIgnoreAll->getEntry( rWord );
     if (!xRes.is())
     {
-        Reference< XSearchableDictionaryList > xDList( GetDictionaryList() );
         Reference< XDictionaryEntry > xNegEntry( SearchDicList( xDList,
-                rWord, nLanguage, false, true ) );
+                rWord, nLanguage, false, true, rDictionaryMap ) );
         if (xNegEntry.is())
             xRes = std::move(xNegEntry);
         else
         {
             Reference< XDictionaryEntry > xPosEntry( SearchDicList( xDList,
-                    rWord, nLanguage, true, true ) );
+                    rWord, nLanguage, true, true, rDictionaryMap ) );
             if (xPosEntry.is())
                 xRes = std::move(xPosEntry);
         }
@@ -413,16 +414,17 @@ bool SpellCheckerDispatcher::isValid_Impl(
         }
 
         // cross-check against results from dictionaries which have precedence!
-        if (GetDicList().is()  &&  IsUseDicList( rProperties, GetPropSet() ))
+        auto xDicList = GetDicList();
+        if (xDicList  &&  IsUseDicList( rProperties, GetPropSet() ))
         {
-            Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( 
aChkWord, nLanguage ) );
+            Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( 
aChkWord, nLanguage, xDicList, m_aDictionaryMap ) );
             if (xTmp.is()) {
                 bRes = !xTmp->isNegative();
             } else {
                 setCharClass(LanguageTag(nLanguage));
                 CapType ct = capitalType(aChkWord, m_oCharClass ? 
&*m_oCharClass : nullptr);
                 if (ct == CapType::INITCAP || ct == CapType::ALLCAP) {
-                    Reference< XDictionaryEntry > xTmp2( 
lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_oCharClass), nLanguage 
) );
+                    Reference< XDictionaryEntry > xTmp2( 
lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_oCharClass), nLanguage, 
xDicList, m_aDictionaryMap ) );
                     if (xTmp2.is()) {
                         bRes = !xTmp2->isNegative();
                     }
@@ -636,7 +638,7 @@ Reference< XSpellAlternatives > 
SpellCheckerDispatcher::spell_Impl(
     // cross-check against results from user-dictionaries which have 
precedence!
     if (xDList.is())
     {
-        Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( 
aChkWord, nLanguage ) );
+        Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( 
aChkWord, nLanguage, xDList, m_aDictionaryMap ) );
         if (xTmp.is())
         {
             if (xTmp->isNegative())    // negative entry found
@@ -648,7 +650,7 @@ Reference< XSpellAlternatives > 
SpellCheckerDispatcher::spell_Impl(
 
                 // replacement text must not be in negative dictionary itself
                 if (!aAddRplcTxt.isEmpty() &&
-                    !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, 
true ).is())
+                    !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, 
true, m_aDictionaryMap ).is())
                 {
                     aProposalList.Prepend( aAddRplcTxt );
                 }
@@ -665,7 +667,7 @@ Reference< XSpellAlternatives > 
SpellCheckerDispatcher::spell_Impl(
             CapType ct = capitalType(aChkWord, m_oCharClass ? &*m_oCharClass : 
nullptr);
             if (ct == CapType::INITCAP || ct == CapType::ALLCAP)
             {
-                Reference< XDictionaryEntry > xTmp2( 
lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_oCharClass), nLanguage 
) );
+                Reference< XDictionaryEntry > xTmp2( 
lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_oCharClass), nLanguage, 
xDList, m_aDictionaryMap ) );
                 if (xTmp2.is())
                 {
                     if (xTmp2->isNegative())    // negative entry found
@@ -677,7 +679,7 @@ Reference< XSpellAlternatives > 
SpellCheckerDispatcher::spell_Impl(
 
                         // replacement text must not be in negative dictionary 
itself
                         if (!aAddRplcTxt.isEmpty() &&
-                            !SearchDicList( xDList, aAddRplcTxt, nLanguage, 
false, true ).is())
+                            !SearchDicList( xDList, aAddRplcTxt, nLanguage, 
false, true, m_aDictionaryMap ).is())
                         {
                             switch ( ct )
                             {
@@ -715,7 +717,7 @@ Reference< XSpellAlternatives > 
SpellCheckerDispatcher::spell_Impl(
         // remove entries listed in negative dictionaries
         // (we don't want to display suggestions that will be regarded as 
misspelled later on)
         if (xDList.is())
-            SeqRemoveNegEntries( aProposals, xDList, nLanguage );
+            SeqRemoveNegEntries( aProposals, xDList, nLanguage, 
m_aDictionaryMap );
 
         uno::Reference< linguistic2::XSetSpellAlternatives > xSetAlt( xRes, 
uno::UNO_QUERY );
         if (xSetAlt.is())
diff --git a/linguistic/source/spelldsp.hxx b/linguistic/source/spelldsp.hxx
index c05676e1c9f0..a479f7bc7296 100644
--- a/linguistic/source/spelldsp.hxx
+++ b/linguistic/source/spelldsp.hxx
@@ -51,6 +51,7 @@ class SpellCheckerDispatcher :
 
     css::uno::Reference< css::linguistic2::XLinguProperties >           
m_xPropSet;
     css::uno::Reference< css::linguistic2::XSearchableDictionaryList >  
m_xDicList;
+    std::map<LanguageType, 
std::vector<css::uno::Reference<css::linguistic2::XDictionary>>> 
m_aDictionaryMap;
 
     LngSvcMgr                       &m_rMgr;
     mutable std::unique_ptr<linguistic::SpellCache> m_pCache; // Spell Cache 
(holds known words)
diff --git a/linguistic/source/spelldta.cxx b/linguistic/source/spelldta.cxx
index f3d6eca2be62..c1a50b22c4de 100644
--- a/linguistic/source/spelldta.cxx
+++ b/linguistic/source/spelldta.cxx
@@ -97,14 +97,15 @@ void SearchSimilarText( const OUString &rText, LanguageType 
nLanguage,
 
 void SeqRemoveNegEntries( std::vector< OUString > &rSeq,
         Reference< XSearchableDictionaryList > const &rxDicList,
-        LanguageType nLanguage )
+        LanguageType nLanguage,
+        std::map<LanguageType, 
std::vector<css::uno::Reference<css::linguistic2::XDictionary>>>& 
rDictionaryMap )
 {
     bool bSthRemoved = false;
     sal_Int32 nLen = rSeq.size();
     for (sal_Int32 i = 0;  i < nLen;  ++i)
     {
         Reference< XDictionaryEntry > xNegEntry( SearchDicList( rxDicList,
-                    rSeq[i], nLanguage, false, true ) );
+                    rSeq[i], nLanguage, false, true, rDictionaryMap ) );
         if (xNegEntry.is())
         {
             rSeq[i].clear();

Reply via email to