Compact data tables, and grapheme forward fast path - #177
Conversation
|
Hmm. I don't really feel comfortable reviewing such a large and monolithic change, even if the tests still pass. Small piecewise PRs might be fine. |
|
I totally understand. The range inlining can be easily extracted. They are simple, clear improvements for reducing the data table and binary search range. However, |
|
I will break down the improvement steps into a few PRs. I expect that changes to codegen( |
In that case I may not have the capacity to review this, sorry. FIne to still make the PR but it's unlikely I'll get around to it. |
|
One is #178, let me know if that's an acceptable size then. |
Hello. It may be a bit sudden, but I'd like to share an experiment.
TL;DR: Ports the techniques from unicode-segmenter (a JS implementation originally derived from this crate, since heavily reworked) back to Rust. It results in 4-5x faster forward-only grapheme segmentation.
First, I want to thank the maintainers of this library.
I'm the maintainer of the JS library unicode-segmenter, which was initially based on this library which I manually ported from. There is an article that shares the story.
I optimized it to match the language characteristics and its own priority. Because JS clients care a lot about the code size.
On the initial port, I focused on data packing to improve the compression ratio. Simple caching or code generation is not allowed in JS because it inflates code size. I explored the grapheme area further to find inlining opportunities, and aside from that, it was a repetition of minor improvements.
At that time, even asking AI didn't make things better, but starting with Opus 4.8 and GLM 5.2, they began to discover real ideas. They discovered an effective way to encode not only data but also state, leading to significant improvements in both code size and performance.
Afterward, I wondered if this could be applied to the Rust side as well, and this PR is the result.
Changes
GraphemeFwd), UAX#29's pairwise rules are evaluated at compile time into a 256-byte mask table, and the context-sensitive rules (GB9c, GB11, GB12/13) become bits in a packed u8 carried forward, so a boundary isstate & PAIR_MASK[before << 4 | after] == 0.(start << bits | category), and tiered around known computable ranges.InCB_Extend_tableand a binary search over 377 ranges for every codepoint. Now derived a simple inline functionis_incb_extend.grapheme_category_raw. Only a 279-range residual reaches the binary search.Result
Speed (benchmarked on Apple M4 Pro)
(No change detected in other benches)
And
__TEXT,__constsection in the rlib: 74,723 -> 39,507 bytes reductionRegardless of the outcome, I fully understand that as a maintainer, you may not happy with questionable and massive changes.
I'm leaving this as a draft, but please feel free to let me know if you intend to merge it or if there is anything I can help with.