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 <noel.grandin@collabora.co.uk>
Tested-by: Jenkins
This commit is contained in:
Noel Grandin
2026-01-29 12:10:54 +01:00
parent 33101d3533
commit b2da152344
7 changed files with 68 additions and 41 deletions
+3 -1
View File
@@ -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,
+2 -1
View File
@@ -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,
+2 -1
View File
@@ -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 );
}
+43 -24
View File
@@ -18,6 +18,7 @@
*/
#include <memory>
#include <map>
#include <optional>
#include <sal/log.hxx>
#include <svl/lngmisc.hxx>
@@ -250,40 +251,58 @@ 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;
return uno::Reference< XDictionaryEntry >();
const uno::Sequence< uno::Reference< XDictionary > >
aDics( xDicList->getDictionaries() );
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");
if ( (!bSearchPosDics && eType == DictionaryType_NEGATIVE)
|| ( bSearchPosDics && eType == DictionaryType_POSITIVE))
{
xEntry = axDic->getEntry( rWord );
if ( xEntry.is() && (bSearchSpellEntry || lcl_HasHyphInfo( xEntry )) )
break;
xEntry = nullptr;
}
assert( rxDic.is() );
LanguageType nLang = LinguLocaleToLanguage( rxDic->getLocale() );
rDictionaryMap[nLang].push_back(rxDic);
}
}
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)
{
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;
}
+14 -12
View File
@@ -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())
+1
View File
@@ -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)
+3 -2
View File
@@ -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();