From 0e210159d4d68123adfb463b7a7069fd2cadf1f2 Mon Sep 17 00:00:00 2001 From: LeanBitLab <245915690+LeanBitLab@users.noreply.github.com> Date: Fri, 9 Oct 2026 10:25:49 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Pre-compile=20Regex=20insta?= =?UTF-8?q?nces=20in=20spellchecker=20hot-path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: Extracted inline `Regex` instantiations for quotes and periods, as well as the `String` to `Regex` mapping for punctuation, into `companion object` private constants in `AndroidWordLevelSpellCheckerSession`. 🎯 Why: `onGetSuggestionsInternal` is a hot-path method called frequently during user typing. Creating new `Regex` objects inline on every call causes unnecessary object allocation and regex compilation overhead, increasing garbage collection pressure and latency. 📊 Expected Impact: Reduced memory allocation per keystroke and faster spellchecker suggestion lookups by reusing pre-compiled regex patterns. 🔬 Verification: Ran `./gradlew :app:assembleStandardDebug` and `./gradlew :app:testStandardDebugUnitTest`. Verified no regressions and no em dashes were used in the changes. --- .jules/bolt.md | 3 +++ .../AndroidWordLevelSpellCheckerSession.kt | 15 +++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index d783bb454..4a3d4b649 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -12,3 +12,6 @@ ## 2024-05-18 - Pre-compile Regex in model importers **Learning:** `TranslationModelImporter` and `HandwritingModelImporter` create `Regex` instances inside `detectLanguageCode` and `detectLanguageTag` methods, sometimes inside a loop parsing zip entries. This causes unnecessary overhead on every detection call. **Action:** Extract frequently used `Regex` instances into `companion object`s as `private val`s to ensure they are compiled only once, reducing execution time and object allocation. +## 2024-05-18 - Pre-compile Regex in SpellCheckerSession +**Learning:** `AndroidWordLevelSpellCheckerSession` previously instantiated new `Regex` objects for every quote matching and punctuation splitting operation during `onGetSuggestionsInternal`, which is a highly invoked method on the spellchecker hot-path. +**Action:** Always extract frequently instantiated `Regex` objects from hot paths (like text replacement or splitting) into `companion object` private constants (e.g. `periodRegex`, `quotesStartRegex`) to reduce allocation and regex compilation overhead per keystroke. diff --git a/app/src/main/java/helium314/keyboard/latin/spellcheck/AndroidWordLevelSpellCheckerSession.kt b/app/src/main/java/helium314/keyboard/latin/spellcheck/AndroidWordLevelSpellCheckerSession.kt index 92dea371d..759fd5e74 100644 --- a/app/src/main/java/helium314/keyboard/latin/spellcheck/AndroidWordLevelSpellCheckerSession.kt +++ b/app/src/main/java/helium314/keyboard/latin/spellcheck/AndroidWordLevelSpellCheckerSession.kt @@ -127,12 +127,12 @@ abstract class AndroidWordLevelSpellCheckerSession( val locale = mLocale ?: return AndroidSpellCheckerService.getNotInDictEmptySuggestions(false) var text = textInfo.text .replace(AndroidSpellCheckerService.APOSTROPHE, AndroidSpellCheckerService.SINGLE_QUOTE) - .replace(Regex("^$quotesRegexp"), "") - .replace(Regex("$quotesRegexp$"), "") + .replace(quotesStartRegex, "") + .replace(quotesEndRegex, "") val localeRegex = scriptToPunctuationRegexMap[locale.script()] if (localeRegex != null) { - text = text.replace(Regex(localeRegex), "") + text = text.replace(localeRegex, "") } val cachedSuggestionsParams = mSuggestionsCache.getSuggestionsFromCache(text) @@ -148,7 +148,7 @@ abstract class AndroidWordLevelSpellCheckerSession( if (CHECKABILITY_CHECKABLE != checkability) { val periodOnlyAtLastIndex = text.indexOf(Constants.CODE_PERIOD.toChar()) == (text.length - 1) if (CHECKABILITY_CONTAINS_PERIOD == checkability) { - val splitText = text.split(Regex(Constants.REGEXP_PERIOD)) + val splitText = text.split(periodRegex) var allWordsAreValid = true for (word in splitText) { if (word.isNotEmpty() && !mService.isValidWord(locale, word) && !mService.isValidWord(locale, word.lowercase(locale))) { @@ -218,8 +218,11 @@ abstract class AndroidWordLevelSpellCheckerSession( val EMPTY_STRING_ARRAY = emptyArray() private const val quotesRegexp = "([\u0022\u0027\u0060\u00B4\u2018\u2019\u201C\u201D])" - private val scriptToPunctuationRegexMap = TreeMap().apply { - put(ScriptUtils.SCRIPT_ARMENIAN, "(\u0028|\u0029|\u0027|\u2026|\u055E|\u055C|\u055B|\u055D|\u058A|\u2015|\u00AB|\u00BB|\u002C|\u0589|\u2024)") + private val quotesStartRegex = Regex("^$quotesRegexp") + private val quotesEndRegex = Regex("$quotesRegexp$") + private val periodRegex = Regex(Constants.REGEXP_PERIOD) + private val scriptToPunctuationRegexMap = TreeMap().apply { + put(ScriptUtils.SCRIPT_ARMENIAN, Regex("(\u0028|\u0029|\u0027|\u2026|\u055E|\u055C|\u055B|\u055D|\u058A|\u2015|\u00AB|\u00BB|\u002C|\u0589|\u2024)")) } private const val CHECKABILITY_CHECKABLE = 0