Shrink compressed translations: dense alphabet, per-class Huffman, optimal parsing - #11369
Open
dhalbert wants to merge 3 commits into
Open
Shrink compressed translations: dense alphabet, per-class Huffman, optimal parsing#11369dhalbert wants to merge 3 commits into
dhalbert wants to merge 3 commits into
Conversation
…timal parsing Rework the translation compressor in `py/maketranslationdata.py` and the decoder in `translate.c`: - Renumber a language's non-ASCII characters into 0x80.. by frequency and map them back through `alphabet[]`, so every symbol fits in 8 bits and the dictionary gets the remaining code points. Languages with more than 127 distinct non-ASCII characters (ja, ko) keep the 16-bit fallback. - Choose Huffman codes per class of the previously decoded byte (start or space, letter, other, ...) through `class_map[]`; the generator tries several class presets and keeps the smallest result. - Tokenize each string by dynamic programming for the fewest bits instead of greedy longest match, deciding per occurrence whether a qstr reference pays off. Dictionary words left unused are dropped. The format is documented in `compressed_string.h`. Same-commit gains on the tightest languages: metro_m4_express fr +1,208 bytes free, pl +1,160; metro_m0_express pl +672. The decoder grows 76-108 bytes. Verified on the unix coverage port (979 tests) and on a Metro M4 Express in fr and en_US. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add `translation_class_t` and `translation_symbol_t` enums to `compressed_string.h` and use the names in `base_class()`, the decode loop, and the matching Python constants in `maketranslationdata.py`, instead of bare 0..6 and 1. No change to generated data or firmware. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Write each preset as the list of Huffman tables to build, each naming the base classes that share it, and derive `class_map[]` with `class_map_for()` instead of spelling out index tuples. Generated output is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Claude wrote the code and did the measurements in this PR, with @dhalbert directing and reviewing each step.
Motivation
The tightest SAMD builds are limited by whichever language compresses worst, so a new feature has to fit in every language. This PR shrinks the per-language translation block so there's more room for new firmware.
What changes
py/maketranslationdata.py: a language's non-ASCII characters are renumbered into 0x80.. by frequency and mapped back through a newalphabet[]table, so every symbol fits in 8 bits and the dictionary gets the remaining code points. Languages with more than 127 distinct non-ASCII characters (ja, ko) keep the existing 16-bit fallback.a-z,A-Z,0-9,%, other ASCII, and>= 0x80. Seven tables are not always worth their size, so the generator emits a smallclass_map[]that assigns each of the seven classes to a table; classes that share a table share a code. The generator tries a few such assignments (one table, three, six, seven), and keeps the one with the smallest total of string data plus tables. On the boards measured here the three-table split (start or space, letter, other) won.supervisor/shared/translate/translate.c: the decoder indexeslengths[]andvalues[]per class and maps alphabet symbols back to code points.decompress()anddecompress_length()keep their signatures, so there are no caller changes.supervisor/shared/translate/compressed_string.h: format comment rewritten for the new layout. Symbol values 2 and 3 are reserved for a follow-up PR for CJK languages (now ja and ko).Results
Bytes free in flash, same commit, before and after:
The tightest language on metro_m4_express goes from 1,608 to 2,696 bytes free, and on metro_m0_express from 500 to 804. The decoder grows 108 bytes on Cortex-M4 and 76 on Cortex-M0+. The generator takes about 1 to 2 seconds longer per build.
Testing
locale.getlocale(),ValueError,%qand%dformatted messages, the errno text path,SyntaxErrorand accented French text all decode correctly at the REPL.mpy-crossbuilds.ports/zephyr-cpinvokes the generator with the same flags but was not built.Follow-ups