Skip to content

base64: Reduce module size and improve base32 decoding speed. - #1154

Open
agatti wants to merge 3 commits into
micropython:masterfrom
agatti:shorten-base32
Open

base64: Reduce module size and improve base32 decoding speed.#1154
agatti wants to merge 3 commits into
micropython:masterfrom
agatti:shorten-base32

Conversation

@agatti

@agatti agatti commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR performs a few minor updates to the base64 module to reduce on its footprint (both on flash and in RAM) and improve Base32 decoding speed.

Base32 encoding/decoding support was updated to use a different method for symbols forward- and reverse-lookup. Rather than using two dictionaries and a list, two bytes objects are used. The forward lookup table was replaced with a single bytes object containing all symbols in sequence (since they were already mapped to 0..31, which incidentally is also the symbols' position in the data chunk), and the reverse lookup table was precomputed off-line to provide a 1:1 map between a byte value and its matching 0..31 index (if one is there).

That reduces the byte-compiled size by 106 bytes: one of the side-effects of that optimisation is the reduced number of objects being created to handle the Base32 encoding/decoding, reducing the overhead on the running interpreter and shrinking the objects table of the compiled version of the module. The other being Base32 decoding should be faster since there's no need for a full dictionary lookup to figure out which 5-bits sequence a byte maps to (which was incidentally done twice in the original code). The Base32 part of the test suite was also updated to feature the same non-ASCII buffer doing an encoding/decoding round-trip, as its Base64 counterpart.

As its CPython equivalent, base64 is supposed to be able to run as a simple base64 encode/decode tool via the -m base64 command line argument passed to the Python interpreter. However, unlike the CPython implementation, this module also presents a -t argument that performs some basic encoding and decoding tests.

Given the existence of a test harness, the usefulness of this option is rather limited as test code is better placed in test scripts.  It is also rather unlikely that this module is executed by regular users as a standalone entity with the -t command line argument (this is even more relevant for embedded targets, as this functionality needs the getopt module being available to function). Moving the test code from the module to the test harness and updating the help output reduced the byte-compiled size by 192 bytes.

The combined size savings of the two proposed changes amount to 298 bytes. Maybe I could have made it to 300 with some more time spent on it, but that's quite good nonetheless :)

Testing

The updated python-stdlib/base64/test_base64.py script was run successfully on Linux/x64 using the current MicroPython master. The test's output was also checked by running the test on CPython 3.14 once the existing base64.py file was renamed (to let CPython use its own base64 module).

Trade-offs and Alternatives

The module could be even shorter by replacing the reverse base32 lookup table with a call to bytes.find instead, however that's going to impact on the base32 decoding performance. If that's not a concern then I will update the PR to do so.

Generative AI

I did not use generative AI tools when creating this PR.

This commit moves the test code present in the `base64` module to the
already existing test harness.

The module is able to be executed as a stand-alone script or via the
`-m base64` command line parameter passed to the interpreter, to act
as a simple base64 encoder/decoder utility.  This is following the
behaviour of the CPython's equivalent module.

However unlike the CPython implementation, this module also presents a
`-t` argument that performs some basic encoding and decoding tests.
Given the existence of a test harness, the usefulness of this option is
rather limited as test code is better placed in test scripts.  It is
also rather unlikely that this module is executed by regular users as a
standalone entity with the `-t` command line argument (this is even
more relevant for embedded targets, as this functionality needs the
`getopt` module being available to function).

Moving the bits of code in question shortens the byte-compiled version
of the module by 192 bytes, with the test code still being executed as
part of CI jobs.

Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
This commit updates the base32 encoding and decoding functions in order
to reduce the footprint of the base64 module when byte-compiled, lower
the amount of memory taken by the module once loaded, and finally to
speed encoding and decoding operations up.

Before these changes the base32 symbols were stored in a dictionary and
forward and reverse lookup tables were built as lists upon module
import.  The symbols are just the letter A to Z and the numbers
2 to 7 in sequence with no gaps, and each symbol maps to an integer
between 0 and 31.

A forward lookup table can be trivially made by creating a bytes object
with the symbols in sequence, as an index lookup is the same as
accessing the n-th byte in the bytes object.  The reverse lookup table
can be also precomputed as a 256 bytes long bytes object filled with a
sentinel value for representing a non-match or otherwise with the
integer index in the forward lookup table.  That brings down the
byte-compiled size by 106 bytes.

Regarding the memory footprint, besides the raw 288 bytes of data to
store, the overhead is much lower as there's only two `bytes` objects
being created behind the scenes.

Finally, whilst forward lookups were still done by accessing a list via
an integer, reverse lookups involved a dictionary search.  Accessing a
bytes object via an index is probably faster than a dictionary lookup,
improving base32 decode speed.

Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
This commit updates the version number of the `base64` package, bumping
up the minor version.

Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant