extmod/moddeflate.c: Add deflate.DeflateIO providing compression and decompression.#11905
Conversation
|
Code size report: |
|
Some quick performance tests: For compression (on the unix port), MicroPython (wbits=8) is about 8x slower than Python (wbits=15) to compress the same file. (4x if you use wbits=6). In terms of bytes/second: MicroPython's decompression performance is impacted slightly by not buffering its input as it reads byte at a time from the underlying stream. On PYBV11 it can be increased to 280kiB/s by adding a 4 byte buffer. Diminishing marginal returns for a bigger buffer. Similar relative gain on Unix (22MiB/s). MicroPython's compression performance is limited by the linear searching in the history window (it's not meant to be fast, it's meant to use less memory). |
|
I'm really excited to see this coming together, thanks @jimmo ! |
Codecov Report
@@ Coverage Diff @@
## master #11905 +/- ##
========================================
Coverage 98.41% 98.41%
========================================
Files 155 156 +1
Lines 20564 20696 +132
========================================
+ Hits 20238 20368 +130
- Misses 326 328 +2
|
|
Re performance: compiling relevant |
| // -----CMF------ ----------FLG--------------- | ||
| // CINFO(5) CM(3) FLEVEL(2) FDICT(1) FCHECK(5) | ||
| uint8_t buf[] = { 0x08, 0x80 }; // CM=2 (deflate), FLEVEL=2 (default), FDICT=0 (no dictionary) | ||
| buf[0] |= MAX(self->window_bits - 8, 1) << 4; // base-2 logarithm of the LZ77 window size, minus eight. |
There was a problem hiding this comment.
Should this use wbits? In case self->window_bits==0?
| static const mp_arg_t allowed_args[] = { | ||
| { MP_QSTR_stream, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, | ||
| { MP_QSTR_format, MP_ARG_INT, {.u_int = DEFLATEIO_FORMAT_NONE} }, | ||
| { MP_QSTR_wbits, MP_ARG_INT, {.u_int = 0} }, |
There was a problem hiding this comment.
Perhaps the default here should be DEFLATEIO_DEFAULT_WBITS, and then the code that checks for wbits==0 elsewhere in this file can be removed?
Edit: or does wbits=0 mean something special?
There was a problem hiding this comment.
Edit: or does
wbits=0mean something special?
Yes.
| { MP_QSTR_close, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_FALSE} }, | ||
| }; | ||
| mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; | ||
| mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); |
There was a problem hiding this comment.
Possible make these positional-only args, to reduce code size and allow a natmod version.
|
I added a new test for stream errors. |
| STATIC mp_uint_t deflateio_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { | ||
| mp_obj_deflateio_t *self = MP_OBJ_TO_PTR(o_in); | ||
|
|
||
| if (!deflateio_init_read(self) || self->stream == MP_OBJ_NULL) { |
There was a problem hiding this comment.
These checks should be swapped.
ea972d4 to
b67d30e
Compare
|
Added |
|
Made compression enabled at the "full" level by default, and updated docs to match. |
680da47 to
a78e5be
Compare
|
Updated to address comments. Added |
This will be replaced with a new deflate module providing the same functionality, with an optional frozen Python wrapper providing a replacement zlib module. binascii.crc32 is temporarily disabled. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <[email protected]>
There are enough places that implement __exit__ by forwarding directly to mp_stream_close that this saves code size. For the cases where __exit__ is a no-op, additionally make their MP_STREAM_CLOSE ioctl handled as a no-op. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <[email protected]>
The compression algorithm implemented in this commit uses much less memory compared to the standard way of implementing it using a hash table and large look-back window. In particular the algorithm here doesn't allocate hash table to store indices into the history of the previously seen text. Instead it simply does a brute-force-search of the history text to find a match for the compressor. This is slower (linear search vs hash table lookup) but with a small enough history (eg 512 bytes) it's not that slow. And a small history does not impact the compression too much. To give some more concrete numbers comparing memory use between the approaches: - Standard approach: inplace compression, all text to compress must be in RAM (or at least memory addressable), and then an additional 16k bytes RAM of hash table pointers, pointing into the text - The approach in this commit: streaming compression, only a limited amount of previous text must be in RAM (user selectable, defaults to 512 bytes). To compress, say, 1k of data, the standard approach requires all that data to be in RAM, plus an additional 16k of RAM for the hash table pointers. With this commit, you only need the 1k of data in RAM. Or if it's streaming from a file (or elsewhere), you could get away with only 256 bytes of RAM for the sliding history and still get very decent compression. In summary: because compression takes such a large amount of RAM (in the standard algorithm) and it's not really suitable for microcontrollers, the approach taken in this commit is to minimise RAM usage as much as possible, and still have acceptable performance (speed and compression ratio). Signed-off-by: Damien George <[email protected]>
Because we only use the streaming source, this is just extra code size. Saves 64 bytes on PYBV11. Signed-off-by: Jim Mussared <[email protected]>
This commit makes the following changes: - Replace 256-byte reverse-bits-in-byte lookup table with computation. - Replace length and distance code lookup tables with computation. - Remove comp_disabled check (it's unused). - Make the dest_write_cb take the data pointer directly, rather than the Outbuf. Saves 500 bytes on PYBV11. Signed-off-by: Jim Mussared <[email protected]>
This library used a mix of "tinf" and "uzlib" to refer to itself. Remove all use of "tinf" in the public API. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <[email protected]>
This supports `wbits` values between +40 to +47. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <[email protected]>
Saves 68 bytes on PYBV11. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <[email protected]>
Collapsing the two adjacent calls to outbits saves 32 bytes. Bringing defl_static.c into lz77.c allows better inlining, saves 24 bytes. Merge the Outbuf/uzlib_lz77_state_t structs, a minor simplification that doesn't change code size. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <[email protected]>
For better abstraction for users of this API. Signed-off-by: Jim Mussared <[email protected]>
This provides similar functionality to the former zlib.DecompIO and especially CPython's gzip.GzipFile for both compression and decompression. This class can be used directly, and also can be used from Python to implement (via io.BytesIO) zlib.decompress and zlib.compress, as well as gzip.GzipFile. Enable/disable this on all ports/boards that zlib was previously configured for. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <[email protected]>
Signed-off-by: Jim Mussared <[email protected]>
Also update zlib & gzip docs to describe the micropython-lib modules. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <[email protected]>
This replaces the previous zlib version. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <[email protected]>
|
Now merged! Thanks @jimmo for all the hard work on this. |
This replaces #11879 -- because there was no way to provide a
gzip.GzipFilethat exactly matched CPython's (e.g. because we need thewbitsparameter), instead we will put this functionality in a separate module/class. I have updated micropython/micropython-lib#694 to also provide a CPython-compatiblegzipmodule.Updated copy of the #11879 description below:
Summary: Adds gzip/zlib/raw-deflate compression support. (See issues #7228, #5590, and earlier PRs #8195, #5613).
Historically MicroPython provided a CPython-compatible implementation of
zlib.decompressas well as a MicroPython-specificzlib.DecompIOwhich is similar to CPython'szlib.decompressobjexcept more useful as a stream wrapper, for example to decompress a file or socket, because you pull (read) decompressed data out of it, rather than pushing (write) compressed data into it.However, CPython also provides
gzip.GzipFilewhich works exactly like MicroPython'sDecompIO, the only limitation is that it only supports gzip-format data (whereasDecompIOsupported the samewbitsargument thatzlib.decompresssupported).This PR removes the
zlibmodule, and replaces it with adeflatemodule providing a stream wrapper classDeflateIOthat is essentially DeflateIO with compression support, but also suitable as a building block for implementinggzip.GzipFile. Because it no longer part of thezlibmodule, it also has a simpler API for specifying format and window size.For backwards compatibility, I have written a pure-Python implementation of the functionality of the former
zlibmodule (including nowzlib.compress), as well as thegzipmodule (includingcompress,decompress,open, andGzipFile), which will be published to micropython-lib (micropython/micropython-lib#694) and can be optionally installed via mip (or frozen). Even though they are very small (~450 bytes each), they are not frozen by default, with the justification:This PR includes the following changes:
mp_stream___exit___objthat avoids each file/stream-like object needing to implement this themselves. (This is a code size optimisation saving ~200 bytes)zlibmodule.deflatemodule.deflate.DeflateIO.zliband add docs forgzipto describe the micropython-lib versions.deflate.Overall this PR adds +1156 bytes to PYBV11 (i.e. adding compression support). This is made up of -2840 (remove zlib), +2928 (add decompression-only DeflateIO), +1068 (add compression support to DeflateIO).
Currently compression support (via
MICROPY_PY_DEFLATE_COMPRESS) is enabled at the same level ("extra features") as decompression (viaMICROPY_PY_DEFLATE), but I think we should consider making it either "full features" or "everything". I do think we should enable it on Unix/Windows though (but really I think that means we should make it "full" and move Unix/Windows to "full").Compared to #8195 & #5613 which are quite similar (cc @harbaum, @andrewleech) this PR adds support for streaming as well as non-gzip (i.e. zlib/raw) formats and a code size reduction as well as reduced memory usage in the compressor (see #11879 (comment) for more notes from @dpgeorge).
This work was funded through GitHub Sponsors, by specific request from a supporter.