From 25f1dc489ff7587ac883551272900b536a252da9 Mon Sep 17 00:00:00 2001 From: Bob Owen Date: Tue, 26 Oct 2021 09:58:51 +0000 Subject: [PATCH] Bug 1713973 p1: Add caching for calls to NS_GetComplexLineBreaks. r=jfkthame Differential Revision: https://phabricator.services.mozilla.com/D129125 --- intl/lwbrk/LineBreaker.cpp | 2 +- intl/lwbrk/WordBreaker.cpp | 6 +- intl/lwbrk/moz.build | 5 + intl/lwbrk/nsComplexBreaker.cpp | 173 +++++++++++++++++++++++++++++++ intl/lwbrk/nsComplexBreaker.h | 19 ++++ layout/build/nsLayoutStatics.cpp | 5 + 6 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 intl/lwbrk/nsComplexBreaker.cpp diff --git a/intl/lwbrk/LineBreaker.cpp b/intl/lwbrk/LineBreaker.cpp index c832976e7f61..0b6196873d1c 100644 --- a/intl/lwbrk/LineBreaker.cpp +++ b/intl/lwbrk/LineBreaker.cpp @@ -1090,7 +1090,7 @@ void LineBreaker::ComputeBreakPositions(const char16_t* aChars, aBreakBefore[ci - aChars] = true; } } else { - NS_GetComplexLineBreaks(aChars + cur, end - cur, aBreakBefore + cur); + ComplexBreaker::GetBreaks(aChars + cur, end - cur, aBreakBefore + cur); // restore breakability at chunk begin, which was always set to false // by the complex line breaker aBreakBefore[cur] = allowBreak; diff --git a/intl/lwbrk/WordBreaker.cpp b/intl/lwbrk/WordBreaker.cpp index d2ca96532e4a..b688947c5cb6 100644 --- a/intl/lwbrk/WordBreaker.cpp +++ b/intl/lwbrk/WordBreaker.cpp @@ -125,8 +125,8 @@ WordRange WordBreaker::FindWord(const char16_t* aText, uint32_t aLen, // shorter answer AutoTArray breakBefore; breakBefore.SetLength(range.mEnd - range.mBegin); - NS_GetComplexLineBreaks(aText + range.mBegin, range.mEnd - range.mBegin, - breakBefore.Elements()); + ComplexBreaker::GetBreaks(aText + range.mBegin, range.mEnd - range.mBegin, + breakBefore.Elements()); // Scan forward for (uint32_t i = aPos + 1; i < range.mEnd; i++) { @@ -169,7 +169,7 @@ int32_t WordBreaker::Next(const char16_t* aText, uint32_t aLen, uint32_t aPos) { const uint32_t segLen = nextBreakPos - aPos + 1; AutoTArray breakBefore; breakBefore.SetLength(segLen); - NS_GetComplexLineBreaks(segStart, segLen, breakBefore.Elements()); + ComplexBreaker::GetBreaks(segStart, segLen, breakBefore.Elements()); for (uint32_t i = aPos + 1; i < nextBreakPos; ++i) { if (breakBefore[i - aPos]) { diff --git a/intl/lwbrk/moz.build b/intl/lwbrk/moz.build index 6c1a25c0f2af..96204bd8a445 100644 --- a/intl/lwbrk/moz.build +++ b/intl/lwbrk/moz.build @@ -8,6 +8,7 @@ TEST_DIRS += ["gtest"] EXPORTS.mozilla.intl += [ "LineBreaker.h", + "nsComplexBreaker.h", "WordBreaker.h", ] @@ -16,6 +17,10 @@ UNIFIED_SOURCES += [ "WordBreaker.cpp", ] +SOURCES += [ + "nsComplexBreaker.cpp", +] + if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": SOURCES += [ "nsPangoBreaker.cpp", diff --git a/intl/lwbrk/nsComplexBreaker.cpp b/intl/lwbrk/nsComplexBreaker.cpp new file mode 100644 index 000000000000..66838812c8fe --- /dev/null +++ b/intl/lwbrk/nsComplexBreaker.cpp @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsComplexBreaker.h" + +#include + +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Services.h" +#include "mozilla/UniquePtr.h" +#include "nsTHashMap.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +using CacheMap = nsTHashMap>; + +static UniquePtr sBreakCache; + +// The underlying hash table extends capacity, when it hits .75 full and uses +// powers of 2 for sizing. This cache limit will hopefully mean most pages fit +// within the cache, while keeping it to a reasonable size. Also by holding the +// previous cache even if pages are bigger than the cache the most commonly used +// should still remain fast. +static const int kCacheLimit = 3072; + +static UniquePtr sOldBreakCache; + +// Simple runnable to delete caches off the main thread. +class CacheDeleter final : public Runnable { + public: + explicit CacheDeleter(UniquePtr aCacheToDelete) + : Runnable("ComplexBreaker CacheDeleter"), + mCacheToDelete(std::move(aCacheToDelete)) {} + + NS_IMETHOD Run() { + MOZ_ASSERT(!NS_IsMainThread()); + mCacheToDelete = nullptr; + return NS_OK; + } + + private: + UniquePtr mCacheToDelete; +}; + +class ComplexBreakObserver final : public nsIObserver { + ~ComplexBreakObserver() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +}; + +NS_IMPL_ISUPPORTS(ComplexBreakObserver, nsIObserver) + +NS_IMETHODIMP ComplexBreakObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + + if (strcmp(aTopic, "memory-pressure") == 0) { + if (sOldBreakCache) { + // We have an old cache, so delete that one first. + NS_DispatchBackgroundTask( + MakeAndAddRef(std::move(sOldBreakCache))); + } else if (sBreakCache) { + NS_DispatchBackgroundTask( + MakeAndAddRef(std::move(sBreakCache))); + } + } + + return NS_OK; +} + +void ComplexBreaker::Initialize() { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr obs = services::GetObserverService(); + if (obs) { + obs->AddObserver(new ComplexBreakObserver(), "memory-pressure", false); + } +} + +void ComplexBreaker::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + sBreakCache = nullptr; + sOldBreakCache = nullptr; +} + +static void AddToCache(const char16_t* aText, uint32_t aLength, + nsTArray aBreakBefore) { + if (NS_WARN_IF(!sBreakCache->InsertOrUpdate( + nsString(aText, aLength), std::move(aBreakBefore), fallible))) { + return; + } + + if (sBreakCache->Count() <= kCacheLimit) { + return; + } + + if (sOldBreakCache) { + NS_DispatchBackgroundTask( + MakeAndAddRef(std::move(sOldBreakCache))); + } + + sOldBreakCache = std::move(sBreakCache); +} + +static void CopyAndFill(const nsTArray& aCachedBreakBefore, + uint8_t* aBreakBefore, uint8_t* aEndBreakBefore) { + auto* startFill = std::copy(aCachedBreakBefore.begin(), + aCachedBreakBefore.end(), aBreakBefore); + std::fill(startFill, aEndBreakBefore, false); +} + +void ComplexBreaker::GetBreaks(const char16_t* aText, uint32_t aLength, + uint8_t* aBreakBefore) { + // It is believed that this is only called on the main thread, so we don't + // need to lock the caching structures. A diagnostic assert is used in case + // our tests don't exercise all code paths. + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(aText, "aText shouldn't be null"); + MOZ_ASSERT(aLength, "aLength shouldn't be zero"); + MOZ_ASSERT(aBreakBefore, "aBreakBefore shouldn't be null"); + + // If we have a cache then check it, if not then create it. + if (sBreakCache) { + if (auto entry = + sBreakCache->Lookup(nsDependentSubstring(aText, aLength))) { + auto& breakBefore = entry.Data(); + CopyAndFill(breakBefore, aBreakBefore, aBreakBefore + aLength); + return; + } + } else { + sBreakCache = MakeUnique(); + } + + // We keep the previous cache when we hit our limit, so that the most recently + // used fragments remain fast. + if (sOldBreakCache) { + auto breakBefore = + sOldBreakCache->Extract(nsDependentSubstring(aText, aLength)); + if (breakBefore) { + CopyAndFill(*breakBefore, aBreakBefore, aBreakBefore + aLength); + // Move the entry to the latest cache. + AddToCache(aText, aLength, std::move(*breakBefore)); + return; + } + } + + NS_GetComplexLineBreaks(aText, aLength, aBreakBefore); + + // As a very simple memory saving measure we trim off trailing elements that + // are false before caching. + auto* afterLastTrue = aBreakBefore + aLength; + while (!*(afterLastTrue - 1)) { + if (--afterLastTrue == aBreakBefore) { + break; + } + } + + AddToCache(aText, aLength, + nsTArray(aBreakBefore, afterLastTrue - aBreakBefore)); +} diff --git a/intl/lwbrk/nsComplexBreaker.h b/intl/lwbrk/nsComplexBreaker.h index 0b508a464547..5e461fc7d00c 100644 --- a/intl/lwbrk/nsComplexBreaker.h +++ b/intl/lwbrk/nsComplexBreaker.h @@ -15,4 +15,23 @@ void NS_GetComplexLineBreaks(const char16_t* aText, uint32_t aLength, uint8_t* aBreakBefore); +class ComplexBreaker { + public: + static void Initialize(); + + static void Shutdown(); + + /** + * A wrapper around the platform specific NS_GetComplexLineBreaks, which adds + * caching of the results to mitigate sometimes expensive implementation. + * @param aText - pointer to the text to process for possible line breaks + * @param aLength - the length to process + * @param aBreakBefore - result array correlated to aText, where element is + * set to true if line can be broken before + * corresponding character in aText and false otherwise + */ + static void GetBreaks(const char16_t* aText, uint32_t aLength, + uint8_t* aBreakBefore); +}; + #endif /* nsComplexBreaker_h__ */ diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index 86c82227d399..724962a9d7ee 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -126,6 +126,7 @@ #include "mozilla/css/ImageLoader.h" #include "gfxUserFontSet.h" #include "RestoreTabContentObserver.h" +#include "mozilla/intl/nsComplexBreaker.h" using namespace mozilla; using namespace mozilla::net; @@ -291,6 +292,8 @@ nsresult nsLayoutStatics::Initialize() { RestoreTabContentObserver::Initialize(); + ComplexBreaker::Initialize(); + return NS_OK; } @@ -399,4 +402,6 @@ void nsLayoutStatics::Shutdown() { mozilla::net::UrlClassifierFeatureFactory::Shutdown(); RestoreTabContentObserver::Shutdown(); + + ComplexBreaker::Shutdown(); }