# UNDF: UNDF-2026-000001119 --- a/sc/source/filter/excel/xepivotxml.cxx +++ b/sc/source/filter/excel/xepivotxml.cxx @@ -1404,16 +1404,22 @@ void XclExpXmlPivotTables::SavePivotTableXml( XclExpXmlStream& rStrm, const ScD if (eOrient == sheet::DataPilotFieldOrientation_ROW) aRowMemberResultData.initLookupFor(pDim->GetName()); else if (eOrient == sheet::DataPilotFieldOrientation_COLUMN) aColumnMemberResultData.initLookupFor(pDim->GetName()); + // Build O(1) name-to-index lookup to replace O(C) std::find in the loop below. + // Without this map, the loop is O(M * C) where M = aMembers.size() and + // C = aCacheFieldItems.size(). Large pivot tables with many distinct values + // (e.g. 5000-row data with a text dimension) exhibit quadratic save times. + std::unordered_map aCacheItemIndex; + aCacheItemIndex.reserve(aCacheFieldItems.size()); + for (size_t k = 0; k < aCacheFieldItems.size(); ++k) + aCacheItemIndex.emplace(aCacheFieldItems[k], k); + // The pair contains the member index in cache and if it is hidden std::vector< std::pair > aMemberSequence; std::set aUsedCachePositions; sal_Int32 nIndex = 0; for (const auto & rMember : aMembers) { - auto it = std::find(aCacheFieldItems.begin(), aCacheFieldItems.end(), rMember.maName); - if (it != aCacheFieldItems.end()) + auto mapIt = aCacheItemIndex.find(rMember.maName); + if (mapIt != aCacheItemIndex.end()) { - size_t nCachePos = static_cast(std::distance(aCacheFieldItems.begin(), it)); + size_t nCachePos = mapIt->second; auto aInserted = aUsedCachePositions.insert(nCachePos); if (aInserted.second) {