Skip to content

Commit 3c78db6

Browse files
authored
Merge pull request #309 from BrianPugh/esp32-static-mpy
introduce TAMP_STATIC_MPY to address esp-idf micropython native module issues.
2 parents d02adae + 5ebb7df commit 3c78db6

5 files changed

Lines changed: 63 additions & 33 deletions

File tree

tamp/_c_src/tamp/common.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
/* Per-literal-size seed tables. All 16 entries must be unique and fit within
1616
* (1 << literal) - 1. literal=7,8 share the original table (all < 0x80). */
1717
// clang-format off
18-
static const unsigned char common_characters_8[] = {' ', 0, '0', 'e', 'i', '>', 't', 'o',
19-
'<', 'a', 'n', 's', '\n', 'r', '/', '.'};
18+
TAMP_STATIC_CONST unsigned char common_characters_8[] = {' ', 0, '0', 'e', 'i', '>', 't', 'o',
19+
'<', 'a', 'n', 's', '\n', 'r', '/', '.'};
2020
/* Common English characters, downshifted to 6 bits */
21-
static const unsigned char common_characters_6[] = {' ' & 0x3F, 'e' & 0x3F, 't' & 0x3F, 'a' & 0x3F, 'o' & 0x3F, 'i' & 0x3F, 'n' & 0x3F, 's' & 0x3F,
22-
'h' & 0x3F, 'r' & 0x3F, 'd' & 0x3F, 'l' & 0x3F, 'c' & 0x3F, 'u' & 0x3F, 'm' & 0x3F, 'w' & 0x3F};
21+
TAMP_STATIC_CONST unsigned char common_characters_6[] = {' ' & 0x3F, 'e' & 0x3F, 't' & 0x3F, 'a' & 0x3F, 'o' & 0x3F, 'i' & 0x3F, 'n' & 0x3F, 's' & 0x3F,
22+
'h' & 0x3F, 'r' & 0x3F, 'd' & 0x3F, 'l' & 0x3F, 'c' & 0x3F, 'u' & 0x3F, 'm' & 0x3F, 'w' & 0x3F};
2323
/* Common English characters, downshifted to 5 bits */
24-
static const unsigned char common_characters_5[] = {' ' & 0x1F, 'e' & 0x1F, 't' & 0x1F, 'a' & 0x1F, 'o' & 0x1F, 'i' & 0x1F, 'n' & 0x1F, 's' & 0x1F,
25-
'h' & 0x1F, 'r' & 0x1F, 'd' & 0x1F, 'l' & 0x1F, 'c' & 0x1F, 'u' & 0x1F, 'm' & 0x1F, 'w' & 0x1F};
24+
TAMP_STATIC_CONST unsigned char common_characters_5[] = {' ' & 0x1F, 'e' & 0x1F, 't' & 0x1F, 'a' & 0x1F, 'o' & 0x1F, 'i' & 0x1F, 'n' & 0x1F, 's' & 0x1F,
25+
'h' & 0x1F, 'r' & 0x1F, 'd' & 0x1F, 'l' & 0x1F, 'c' & 0x1F, 'u' & 0x1F, 'm' & 0x1F, 'w' & 0x1F};
2626
// clang-format on
2727

2828
static inline uint32_t xorshift32(uint32_t *state) {

tamp/_c_src/tamp/common.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@ extern "C" {
8888
} while (0)
8989
#endif
9090

91+
/* TAMP_STATIC_CONST: declaration prefix for file-local read-only tables.
92+
* Normally `static const`, but drops `static` on Xtensa MicroPython native
93+
* modules, where `static const` rodata returns incorrect values.
94+
* See micropython/micropython#14429. */
95+
#if defined(__XTENSA__) && defined(MICROPY_ENABLE_DYNRUNTIME)
96+
#define TAMP_STATIC_CONST const
97+
#else
98+
#define TAMP_STATIC_CONST static const
99+
#endif
100+
91101
/* Include stream API (tamp_compress_stream, tamp_decompress_stream).
92102
* Enabled by default. Disable with -DTAMP_STREAM=0 to save ~2.8KB.
93103
*/

tamp/_c_src/tamp/compressor.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
#define TAMP_POLL_CONTINUE ((tamp_res)127)
3131

3232
// encodes [min_pattern_bytes, min_pattern_bytes + 14] pattern lengths (14 = FLUSH pattern, used in secondary reads)
33-
static const uint8_t huffman_codes[] = {0x0, 0x3, 0x8, 0xb, 0x14, 0x24, 0x26, 0x2b,
34-
0x4b, 0x54, 0x94, 0x95, 0xaa, 0x27, 0xab};
33+
TAMP_STATIC_CONST uint8_t huffman_codes[] = {0x0, 0x3, 0x8, 0xb, 0x14, 0x24, 0x26, 0x2b,
34+
0x4b, 0x54, 0x94, 0x95, 0xaa, 0x27, 0xab};
3535
// These bit lengths pre-add the 1 bit for the 0-value is_literal flag.
36-
static const uint8_t huffman_bits[] = {0x2, 0x3, 0x5, 0x5, 0x6, 0x7, 0x7, 0x7, 0x8, 0x8, 0x9, 0x9, 0x9, 0x7, 0x09};
36+
TAMP_STATIC_CONST uint8_t huffman_bits[] = {0x2, 0x3, 0x5, 0x5, 0x6, 0x7, 0x7, 0x7, 0x8, 0x8, 0x9, 0x9, 0x9, 0x7, 0x09};
3737

3838
#if TAMP_EXTENDED_COMPRESS
3939
#define RLE_MAX_COUNT ((14 << 4) + 15 + 2) // 241

tamp/_c_src/tamp/decompressor.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* Note: A 64-byte table with special-cased symbol 1 was tried but was ~10% slower
2727
* and only saved 8 bytes in final firmware due to added branch logic.
2828
*/
29-
static const uint8_t HUFFMAN_TABLE[128] = {
29+
TAMP_STATIC_CONST uint8_t HUFFMAN_TABLE[128] = {
3030
50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 85, 85, 85, 85, 122, 123, 104, 104, 86, 86,
3131
86, 86, 93, 93, 93, 93, 68, 68, 68, 68, 68, 68, 68, 68, 105, 105, 124, 126, 87, 87, 87, 87, 51, 51, 51, 51,
3232
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,

tools/on-device-compression-benchmark.py

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,65 @@
11
"""Micropython code to be ran on-device."""
22

33
import gc
4-
import io
4+
import os
55
import time
66

77
import tamp
88

99

1010
def main():
11-
# Load input data into RAM before timing
11+
# Small enough to allocate on a fragmented ESP32 heap,
12+
# large enough to amortize readinto + write call overhead.
13+
block_size = 16384
14+
input_path = "enwik8-100kb"
15+
output_path = "enwik8-100kb.tamp"
16+
17+
decompressed_len = os.stat(input_path)[6]
18+
1219
gc.collect()
13-
print("Loading input data into RAM...")
14-
with open("enwik8-100kb", "rb") as f:
15-
input_data = f.read()
16-
decompressed_len = len(input_data)
20+
read_buf = bytearray(block_size)
21+
mv = memoryview(read_buf)
22+
23+
# Tare: flash-read cost alone, so we can subtract it from the full pass
24+
# and recover a pure-compression throughput number.
25+
print("Measuring flash-read tare...")
26+
start_us = time.ticks_us()
27+
with open(input_path, "rb") as f:
28+
while f.readinto(read_buf):
29+
pass
30+
read_elapsed_us = time.ticks_diff(time.ticks_us(), start_us)
1731

18-
# Pre-allocate output buffer
1932
gc.collect()
20-
output_buffer = io.BytesIO()
2133

22-
# Time only the compression (RAM to RAM)
34+
# Compression: read chunk -> feed compressor -> compressor writes to flash.
35+
out = open(output_path, "wb")
2336
print("Compressing...")
24-
start_ms = time.ticks_ms()
25-
with tamp.open(output_buffer, "wb") as compressor:
26-
compressor.write(input_data)
27-
elapsed_ms = time.ticks_diff(time.ticks_ms(), start_ms)
28-
29-
compressed_data = output_buffer.getvalue()
30-
compressed_len = len(compressed_data)
37+
try:
38+
start_us = time.ticks_us()
39+
with open(input_path, "rb") as f_in, tamp.open(out, "wb") as compressor:
40+
while True:
41+
n = f_in.readinto(read_buf)
42+
if not n:
43+
break
44+
compressor.write(mv[:n])
45+
total_elapsed_us = time.ticks_diff(time.ticks_us(), start_us)
46+
finally:
47+
out.close()
3148

32-
# Write to flash for verification (not timed)
33-
with open("enwik8-100kb.tamp", "wb") as f:
34-
f.write(compressed_data)
49+
compressed_len = os.stat(output_path)[6]
50+
compress_only_us = total_elapsed_us - read_elapsed_us
3551

36-
elapsed_s = elapsed_ms / 1000
37-
bytes_per_sec = decompressed_len / elapsed_s if elapsed_s > 0 else 0
52+
total_s = total_elapsed_us / 1_000_000
53+
compress_only_s = compress_only_us / 1_000_000
54+
total_bps = decompressed_len / total_s if total_s > 0 else 0
55+
compress_bps = decompressed_len / compress_only_s if compress_only_s > 0 else 0
3856

3957
print(f"{decompressed_len=:,}")
4058
print(f"{compressed_len=:,}")
41-
print(f"elapsed={elapsed_s:.3f}s")
42-
print(f"compression={bytes_per_sec:,.0f} bytes/s")
59+
print(f"read_tare={read_elapsed_us / 1_000_000:.6f}s")
60+
print(f"total_elapsed={total_s:.6f}s")
61+
print(f"end-to-end={total_bps:,.0f} bytes/s")
62+
print(f"compression-only={compress_bps:,.0f} bytes/s")
4363

4464

4565
if __name__ == "__main__":

0 commit comments

Comments
 (0)