airborne12 opened a new pull request, #70266:
URL: https://github.com/apache/doris/pull/70266
> Stacked PR in the SNII / text analysis / ANN CLucene removal series (index
at the end). It depends on #70265 (T6), whose commits show in this diff until
they merge; review only this PR's own commits: `82aa141fe10` [refactor](be)
Move jieba into the Doris tree for the chinese parser, `058fb5c4aef`
[improvement](be) Cut chinese text over a flat jieba dictionary. It stays a
draft until its dependencies merge.
### What problem does this PR solve?
Issue Number: None
Related PR: #70265
Problem Summary:
**[refactor](be) Move jieba into the Doris tree for the chinese parser**
(`82aa141fe10`)
The chinese parser was the last builtin parser still on a
CLucene analyzer: LanguageBasedAnalyzer behind LuceneBackedTextAnalyzer, over
the copy of cppjieba in CLucene's contribs-lib. jieba's segmenters now live
in
the BE as analysis/tokenizer/jieba/, ported without a change in how they cut,
and the parser runs them through a tokenizer of the new stream interface.
This commit only moves the code; a later one changes its data layout.
- analysis/tokenizer/jieba/: trie.h, dict_trie.h, mp_segment.h, hmm_model.h,
hmm_segment.h, mix_segment.h, full_segment.h, pre_filter.h, segment_base.h,
unicode.h and jieba.h from cppjieba, string_util.h from limonp, with
cppjieba's and limonp's namespaces replaced by
doris::segment_v2::inverted_index::jieba, so that doris_be_test links both
copies without merging them. No namespace std, XLOG or XCHECK: a
dictionary,
user dictionary or model file that cannot be read, or has an illegal line,
throws INVERTED_INDEX_ANALYZER_ERROR instead of aborting the process.
Decoding comes from analysis/common/rune.h, which ik already uses. The
keyword extractor, POS tagger, query segment, logging and std extensions
are
not brought over, so idf.utf8 is no longer read; it stays in the package.
The asserts on dictionary data are DCHECKs, active in the same builds.
- JiebaTokenizer: copies the value, cuts it with MixSegment and the HMM in
the
coarse mode or with FullSegment in the fine mode, and hands the words out
in
batches, cut to 255 bytes. With lower_case, a word whose first byte is
ASCII
is lowercased where it lies when it is handed out, as the old tokenizer did
to its buffer, so a word sees the lowercasing of the words before it. The
fine mode's words overlap: it hands out a run of ASCII letters and digits
whole and then the runs of letters and of digits in it, and jieba reads a
stray continuation byte and the byte after it as one character, so a run
can start with a byte that is not ASCII, keep its capitals, and be lowered
by the next word. A word that still has a capital after its turn is
therefore copied into the batch; the other terms are views of the copy of
the value. The dictionaries are loaded once per directory, and a failed
load is tried again by the next analyzer.
- analyzer.cpp builds the chinese parser on JiebaTokenizer, and with
lucene_builtin_analyzer and the CLuceneError catch gone it includes no
CLucene: it leaves the CLucene exception tables of check-header-deps.py.
- LICENSE.txt, dist/LICENSE-dist.txt, dist/licenses/LICENSE-cppjieba.txt,
dist/licenses/LICENSE-limonp.txt and .licenserc.yaml carry the MIT terms of
the derived files.
**[improvement](be) Cut chinese text over a flat jieba dictionary**
(`058fb5c4aef`)
jieba, moved into the BE unchanged in the previous commit,
still walked a trie of hash maps behind pointers, kept a 20-byte RuneStr and
a
DAG entry with an inline vector per character, and looked separators, stop
words and HMM emissions up in hash sets and maps. The chinese parser now cuts
over flat arrays built once from the same dictionaries, with every decision
the old segmenters made kept, so the tokens do not change.
- flat_dict.h: FlatDict, built from the loaded DictTrie, HMMModel and stop
word file, which are dropped afterwards. The trie is breadth-first arrays
(child_begin, key, weight, has_value), so the children of a node are
consecutive and a node's count is the difference of two child_begin
entries; the first rune has a table of 0x10000 entries, supplementary first
runes are searched for among the root's children, up to 8 children are
scanned and more are bisected. User single-rune words are a bitmap with a
set for supplementary runes, the emission probabilities a rune-major table
of 0x10000 x 4 with a map for supplementary runes, and stop words of up to
8 bytes an open-addressing table keyed by their bytes, longer ones a sorted
vector. The pointer trie is never built: a stable sort keeps the last unit
of each word, which is what inserting the units in order kept.
- segmenter.h: the mix and full cuts of MPSegment, HMMSegment, MixSegment,
FullSegment and PreFilter over a JiebaScratch (jieba_scratch.h): runes as
code point and byte offset arrays, the DAG walked straight into the DP or
the full cut in its old order instead of being stored, one column of
Viterbi weights. Kept as they decide tokens: the lenient decoding of
rune.h, no token for a value that does not decode, the DP adding weights in
the old order and keeping the first maximum (strict >), MAX_WORD_LENGTH
512, FullSegment's word_len carried from one rune to the next, the whole
alphanumeric run before its runs of letters and digits, user single-rune
words, stop words dropped before lowercasing. The separators are compared
directly: space, \t, \n, U+FF0C and U+3002, all SPECIAL_SEPARATORS held.
- jieba.h loads the files and keeps only the FlatDict; JiebaTokenizer owns a
JiebaScratch, releases it above the high water mark at reset() like its
other buffers and counts it in reserved_bytes(). next_batch() is unchanged.
- trie.h, mp_segment.h, hmm_segment.h, mix_segment.h, full_segment.h,
pre_filter.h and segment_base.h are gone; LICENSE.txt and .licenserc.yaml
list the derived files that remain.
### Release note
The chinese parser tokenizes about 3.2 times as fast in the coarse mode and
2.3 times as fast in the fine mode, and its dictionaries take less memory.
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [x] Unit Test
- [x] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
Details:
- `82aa141fe10` [refactor](be) Move jieba into the Doris tree for the
chinese parser:
- BE UT under ASAN: the analysis, index and search suites with the
T5 to
T7 ones, 4191 of 4198 tests pass; the 7 skipped are opt-in reports
and
generators, the opt-in code point scan and the vector width check
of the
build without AVX2. The code point scan passes with
ANALYSIS_GOLDEN_FULL=1. JiebaTokenizerTest runs the parser beside
LanguageBasedAnalyzer in both modes, with and without lowercase, on
random values with invalid bytes, on every word of jieba.dict.utf8
and
user.dict.utf8 (64 to a value, half with raised letters) and on
mixed-case, unknown and undecodable words. A death test does the
same on
a dictionary of its own, with words such as 卡拉OK, Q版本 and words of
a
supplementary plane, in a process of its own because CLucene's
jieba
keeps the first dictionary it loads. Other new cases: 你好\xE4\xB8
makes
no token, the first byte decides the lowercase, supplementary-plane
characters and user words, loading without idf.utf8, a failed load
tried
again, eight threads on one analyzer, batches around 256 tokens,
the
stream contract, and the buffers released after a long value.
- Mutation: lowercasing the words of a batch in reverse order fails
AnalysisDifferential and JiebaTokenizerTest's random values, both
on
the shipped dictionaries, where a stray continuation byte starts a
run
of letters that keeps its capitals, and the batching test. The
source
was restored before the final runs.
- The analysis golden files are unchanged, and the benchmark's
digest mode
gives the same tokens as the T1 baseline binary in all 5,240,060
rows,
chinese_coarse and chinese_fine among them.
- The port was also compared with the original headers directly,
outside
the BE build: 3,408,272 calls of cut and cut_all with 0
differences, on
the shipped dictionaries (every byte and pairs and triples of
chosen
bytes, 300k random sentences with invalid bytes, every dictionary
word
alone and joined to the next, about 70 MB of corpora) and on
edited ones
(an undecodable word, user dictionaries of 1 to 4 columns, CRLF);
15
broken files fail on both sides.
- check-build-hygiene.sh passes, with analyzer.cpp out of both
CLucene
exception tables (12+11 now); clang-format 16 leaves the changed
files
as they are; run-clang-tidy.sh reports nothing on the changed
lines.
- `058fb5c4aef` [improvement](be) Cut chinese text over a flat jieba
dictionary:
- BE UT under ASAN: the analysis, index and search suites with the
T5 to
T7 ones, 4191 of 4198 tests pass and the 7 skipped are the opt-in
ones;
the code point scan passes with ANALYSIS_GOLDEN_FULL=1.
JiebaTokenizerTest
passes unchanged: the live differential with LanguageBasedAnalyzer
on
random values, every dictionary word, mixed-case, unknown and
undecodable words, and on a dictionary of its own in a death test.
ComponentStreamTest counts the scratch among the bytes a long value
leaves. IndexFileWriterTest.CacheEvictionTest failed once in a run
of the
wider suite list and passes on its own: it depends on what earlier
tests
leave in a shared searcher cache, not on this change.
- The analysis golden files are unchanged, and the benchmark's
digest mode
gives the same tokens as the T1 baseline binary in all 5,240,060
rows,
chinese_coarse and chinese_fine among them.
- The new code was also compared with the original CLucene headers
directly, outside the BE build and with the real
jieba_tokenizer.cpp:
4,208,058 cuts and 7,364,072 tokenizer runs (both modes, lowercase
on
and off, the scratch reused and fresh) on every byte and pairs and
triples of chosen bytes, 300k random sentences with invalid bytes,
every
dictionary word alone and joined to the next, long values, all
corpora,
and edited dictionaries (an undecodable main word, duplicate main
words,
supplementary-plane user words including words of 512 and 513
runes,
supplementary-plane emissions, CRLF files, 12k stop words): 0
mismatches, in a release and in an ASAN+UBSan build. 9 of 10
deliberate
mutations of the new code were caught; the tenth swaps E and S on
a tie
at the last rune, which ends a word either way.
- Throughput of the benchmark binary of this change with
be/benchmark/analyzer_ab.py: five alternating rounds, the median
of the
per-round ratios on each corpus and their geometric mean, reuse/
and
row/. Against the T1 baseline: coarse 3.62 and 3.23 (zh_wiki 2.85
and
2.70, ja_wiki 3.18 and 2.93, the four mixed corpora 3.97 and
3.47), fine
2.44 and 2.28 (zh_wiki 2.48 and 2.41, ja_wiki 2.66 and 2.54, mixed
2.37
and 2.18). Against the previous commit: coarse 3.54 and 3.10, fine
2.43
and 2.19, so the move itself kept the old speed.
- Memory of loading the shipped dictionaries, each in a process of
its own
with glibc malloc: the old cppjieba::Jieba with its five files
+132.3 MB
of RSS after loading (+145 MB at the peak); the new holder +40.5 MB
right after loading and +11.2 MB after malloc_trim (+51 MB at the
peak),
the difference being freed loader memory that glibc keeps and the
BE's
jemalloc returns on decay. Loading takes 0.19 s instead of 0.51 s.
- check-build-hygiene.sh passes; clang-format 16 leaves the changed
files
as they are; run-clang-tidy.sh reports nothing on the changed
lines.
- Behavior changed:
- [x] No.
- [ ] Yes.
`82aa141fe10`: Every token of the chinese parser is the same as
before (above). A missing dictionary file still fails the analyzer, now with
INVERTED_INDEX_ANALYZER_ERROR from the BE.
`058fb5c4aef`: Every token of the chinese parser is the same as
before (above).
- Does this need documentation?
- [x] No.
- [ ] Yes.
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
🤖 Generated with [Claude Code](https://claude.com/claude-code)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]