Kristian Rickert created OPENNLP-1878:
-----------------------------------------
Summary: Performance Enhancements from 1850/1852
Key: OPENNLP-1878
URL: https://issues.apache.org/jira/browse/OPENNLP-1878
Project: OpenNLP
Issue Type: New Feature
Affects Versions: 3.0.0-M4
Reporter: Kristian Rickert
Assignee: Kristian Rickert
Fix For: 3.0.0-M5
{quote}Not ready for review yet: this branch is based on the tip of GitHub PR
apache/opennlp#1105 (the OPENNLP-1850 "dl" rung), which is approved but not yet
merged to main. The PR should stay open or in draft until #1105 lands, at which
point the branch is rebased --onto main and this note is removed.{quote}
h2. Summary
A vetted set of non-breaking performance improvements for the OPENNLP-1850
normalizer, tokenizer, and DL hot paths, aimed at heavy use as a search-index
analyzer and tokenizer. Every item is output-preserving (byte-identical),
offset-safe, thread-safe, free of regex on the text path, and ASCII-only in
source. These were verified against the source by reading the relevant methods
during a performance review and were deliberately deferred out of the
OPENNLP-1850 review so as not to delay it. Land as one follow-up PR (or a small
stack), and pair with a JMH benchmark run in a fresh JVM per case on realistic
mixed-script text to confirm each win before and after.
h2. Scope (ranked, tier one first)
* Identity short-circuit on the non-aligned {{CharClass}} set folds
({{normalize}}, {{collapse}}, {{removeAll}}, and the static {{substitute}}).
They currently allocate a {{StringBuilder}} and copy code point by code point
even when nothing matches. Change to a lazy builder: scan for the first member,
return the input unchanged when there is none, and allocate the builder
pre-filled with the unchanged prefix only on the first hit. Biggest win on
ASCII-heavy token streams; roughly neutral on text saturated with folded
characters. Scope to the non-aligned path; leave the {{*Aligned}} variants
alone unless the identity-{{Alignment}} compose is verified a true no-op.
* {{Confusables.skeleton}} fast-path plus an optional bounded cache. Add a
{{BitSet}} pre-filter of "is this code point ever a prototype key" so the
common no-mapping case skips the two {{java.text.Normalizer}} NFD passes and
the per-code-point boxing, and add a size-bounded, thread-safe value cache.
Both fire only when {{CONFUSABLE_FOLD}} is configured. The two NFD passes are
required by UTS #39 when a mapping does apply, so the floor stays at two for
actual confusables.
* Hoist the per-code-point volatile reads in tokenization.
{{WordBreakProperty.ordinalOf}} does a volatile {{data()}} read per code point
and {{ExtendedPictographic.is}} reads a volatile field per call. Resolve the
resolved-data snapshot and the {{BitSet}} once per document in
{{WordSegmenter.forEachSegment}} and {{WordType.of}} through package-private
overloads, keeping the lazy first-resolution and the supplementary path.
Removes a volatile load and a call frame per code point on the hottest
tokenization op.
* BMP fast-path the {{Character.codePointAt}} and {{Character.charCount}} pairs
in {{WordSegmenter}}, {{WordType.of}}, and the {{CharClass}} cursor loops, so a
non-surrogate {{char}} skips the JDK call and the redundant surrogate re-test
(over 99 percent of Latin text).
* {{DigitCharSequenceNormalizer}}: replace the per-digit {{String.valueOf}}
allocation with a precomputed {{static final String[]}} for "0" through "9".
Output-preserving (length still one).
* {{Alignment.Builder}}: add a pre-size constructor {{Builder(int
expectedLength)}} seeding the {{starts}}/{{ends}} arrays; every caller already
knows {{text.length()}}. Kills the array-regrow chain and makes the defensive
trim near-exact on the offset and highlighting path. Keep the no-arg
constructor.
* DL small cleanups: size the score and token lists in
{{DocumentCategorizerDL}} and {{NameFinderDL}} from the known counts
({{ArrayList}} rather than {{LinkedList}}), and pre-size the ONNX input
{{HashMap}}. Correctness-neutral.
h2. Acceptance criteria
* Every change is output-preserving (byte-identical), offset-safe, and
thread-safe; the existing OPENNLP-1850 test suites pass unchanged.
* A JMH benchmark, one fresh JVM per case, on realistic mixed-script text
confirms the tier-one wins (the identity short-circuit and the {{Confusables}}
fast-path) before and after.
* No new dependencies, ASCII-only source, no regex on the text path.
h2. Out of scope
* Any behavior or output change.
* The {{*Aligned}} {{CharClass}} variants, unless the identity-{{Alignment}}
compose is verified a true no-op.
* Paths already verified optimal and not to be re-investigated:
{{CodePointSet}} BitSet membership, the compiled UAX-29 transition tables, the
{{Alignment}} binary searches and {{andThen}}, and the
{{mergeOverlappingSpans}} TreeMap (deliberately O(n log n) to yield document
order).
--
This message was sent by Atlassian Jira
(v8.20.10#820010)