Skip to content

Commit cd99f1c

Browse files
committed
binascii: Reduce module RAM overhead and compiled footprint.
This commit makes some changes to the `binascii` module to reduce its footpint once compiled, and marginally improve the time it takes to import the module itself. The decoding table is now precomputed as a 256 entries long `bytes` object rather than doing the table building at import time. The table was built as a list of integers, so there's some considerable overhead compared to a single `bytes` object. Decoding data would also do some unnecessary roundtrips between integers and characters for the table lookup to figure out the 6-bits sequence to use for a given character. The original code, despite having a function called "a2b_base64" does not work with strings, so the roundtrips were not needed to begin with. CPython's equivalent works with strings too, but the changes proposed here do not change the function's behaviour. There was some dead code in the encoder function, used to calculate the final size of the output buffer. The code uses a list anyway, so this is not needed. The tables are now built using `const()`, and made private (hence the minor version number being bumped up). This saves even more space when compiled. Finally, the base64 part of the test suite was updated a bit to cover more padding cases. These changes reduce the compiled module size by 204 bytes. Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
1 parent ee4bb8f commit cd99f1c

3 files changed

Lines changed: 37 additions & 286 deletions

File tree

‎python-stdlib/binascii/binascii.py‎

Lines changed: 31 additions & 284 deletions
Original file line numberDiff line numberDiff line change
@@ -14,277 +14,27 @@ def unhexlify(data):
1414

1515
# ____________________________________________________________
1616

17-
PAD = "="
18-
19-
table_a2b_base64 = [
20-
-1,
21-
-1,
22-
-1,
23-
-1,
24-
-1,
25-
-1,
26-
-1,
27-
-1,
28-
-1,
29-
-1,
30-
-1,
31-
-1,
32-
-1,
33-
-1,
34-
-1,
35-
-1,
36-
-1,
37-
-1,
38-
-1,
39-
-1,
40-
-1,
41-
-1,
42-
-1,
43-
-1,
44-
-1,
45-
-1,
46-
-1,
47-
-1,
48-
-1,
49-
-1,
50-
-1,
51-
-1,
52-
-1,
53-
-1,
54-
-1,
55-
-1,
56-
-1,
57-
-1,
58-
-1,
59-
-1,
60-
-1,
61-
-1,
62-
-1,
63-
62,
64-
-1,
65-
-1,
66-
-1,
67-
63,
68-
52,
69-
53,
70-
54,
71-
55,
72-
56,
73-
57,
74-
58,
75-
59,
76-
60,
77-
61,
78-
-1,
79-
-1,
80-
-1,
81-
-1,
82-
-1,
83-
-1, # Note PAD->-1 here
84-
-1,
85-
0,
86-
1,
87-
2,
88-
3,
89-
4,
90-
5,
91-
6,
92-
7,
93-
8,
94-
9,
95-
10,
96-
11,
97-
12,
98-
13,
99-
14,
100-
15,
101-
16,
102-
17,
103-
18,
104-
19,
105-
20,
106-
21,
107-
22,
108-
23,
109-
24,
110-
25,
111-
-1,
112-
-1,
113-
-1,
114-
-1,
115-
-1,
116-
-1,
117-
26,
118-
27,
119-
28,
120-
29,
121-
30,
122-
31,
123-
32,
124-
33,
125-
34,
126-
35,
127-
36,
128-
37,
129-
38,
130-
39,
131-
40,
132-
41,
133-
42,
134-
43,
135-
44,
136-
45,
137-
46,
138-
47,
139-
48,
140-
49,
141-
50,
142-
51,
143-
-1,
144-
-1,
145-
-1,
146-
-1,
147-
-1,
148-
-1,
149-
-1,
150-
-1,
151-
-1,
152-
-1,
153-
-1,
154-
-1,
155-
-1,
156-
-1,
157-
-1,
158-
-1,
159-
-1,
160-
-1,
161-
-1,
162-
-1,
163-
-1,
164-
-1,
165-
-1,
166-
-1,
167-
-1,
168-
-1,
169-
-1,
170-
-1,
171-
-1,
172-
-1,
173-
-1,
174-
-1,
175-
-1,
176-
-1,
177-
-1,
178-
-1,
179-
-1,
180-
-1,
181-
-1,
182-
-1,
183-
-1,
184-
-1,
185-
-1,
186-
-1,
187-
-1,
188-
-1,
189-
-1,
190-
-1,
191-
-1,
192-
-1,
193-
-1,
194-
-1,
195-
-1,
196-
-1,
197-
-1,
198-
-1,
199-
-1,
200-
-1,
201-
-1,
202-
-1,
203-
-1,
204-
-1,
205-
-1,
206-
-1,
207-
-1,
208-
-1,
209-
-1,
210-
-1,
211-
-1,
212-
-1,
213-
-1,
214-
-1,
215-
-1,
216-
-1,
217-
-1,
218-
-1,
219-
-1,
220-
-1,
221-
-1,
222-
-1,
223-
-1,
224-
-1,
225-
-1,
226-
-1,
227-
-1,
228-
-1,
229-
-1,
230-
-1,
231-
-1,
232-
-1,
233-
-1,
234-
-1,
235-
-1,
236-
-1,
237-
-1,
238-
-1,
239-
-1,
240-
-1,
241-
-1,
242-
-1,
243-
-1,
244-
-1,
245-
-1,
246-
-1,
247-
-1,
248-
-1,
249-
-1,
250-
-1,
251-
-1,
252-
-1,
253-
-1,
254-
-1,
255-
-1,
256-
-1,
257-
-1,
258-
-1,
259-
-1,
260-
-1,
261-
-1,
262-
-1,
263-
-1,
264-
-1,
265-
-1,
266-
-1,
267-
-1,
268-
-1,
269-
-1,
270-
-1,
271-
-1,
272-
-1,
273-
-1,
274-
-1,
275-
-1,
276-
]
277-
278-
279-
def _transform(n):
280-
if n == -1:
281-
return "\xff"
282-
else:
283-
return chr(n)
284-
285-
286-
table_a2b_base64 = "".join(map(_transform, table_a2b_base64))
287-
assert len(table_a2b_base64) == 256
17+
_PAD = const("=")
18+
19+
# a2b = bytearray('\xff' * 256); for i, j in enumerate(b2a): a2b[ord(j)] = i
20+
_TABLE_A2B_BASE64 = const(
21+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
22+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
23+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3e\xff\xff\xff\x3f"
24+
b"\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\xff\xff\xff\xff\xff\xff"
25+
b"\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e"
26+
b"\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\xff\xff\xff\xff\xff"
27+
b"\xff\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28"
28+
b"\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\xff\xff\xff\xff\xff"
29+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
30+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
31+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
32+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
33+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
34+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
35+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
36+
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
37+
)
28838

28939

29040
def a2b_base64(ascii):
@@ -297,13 +47,12 @@ def a2b_base64(ascii):
29747
last_char_was_a_pad = False
29848

29949
for c in ascii:
300-
c = chr(c)
301-
if c == PAD:
50+
if c == 61: # ord('=')
30251
if quad_pos > 2 or (quad_pos == 2 and last_char_was_a_pad):
30352
break # stop on 'xxx=' or on 'xx=='
30453
last_char_was_a_pad = True
30554
else:
306-
n = ord(table_a2b_base64[ord(c)])
55+
n = _TABLE_A2B_BASE64[c]
30756
if n == 0xFF:
30857
continue # ignore strange characters
30958
#
@@ -328,14 +77,12 @@ def a2b_base64(ascii):
32877

32978
# ____________________________________________________________
33079

331-
table_b2a_base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
80+
_TABLE_B2A_BASE64 = const("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
33281

33382

33483
def b2a_base64(bin, newline=True):
33584
"Base64-code line of data."
33685

337-
newlength = (len(bin) + 2) // 3
338-
newlength = newlength * 4 + 1
33986
res = []
34087

34188
leftchar = 0
@@ -344,19 +91,19 @@ def b2a_base64(bin, newline=True):
34491
# Shift into our buffer, and output any 6bits ready
34592
leftchar = (leftchar << 8) | c
34693
leftbits += 8
347-
res.append(table_b2a_base64[(leftchar >> (leftbits - 6)) & 0x3F])
94+
res.append(_TABLE_B2A_BASE64[(leftchar >> (leftbits - 6)) & 0x3F])
34895
leftbits -= 6
34996
if leftbits >= 6:
350-
res.append(table_b2a_base64[(leftchar >> (leftbits - 6)) & 0x3F])
97+
res.append(_TABLE_B2A_BASE64[(leftchar >> (leftbits - 6)) & 0x3F])
35198
leftbits -= 6
35299
#
353100
if leftbits == 2:
354-
res.append(table_b2a_base64[(leftchar & 3) << 4])
355-
res.append(PAD)
356-
res.append(PAD)
101+
res.append(_TABLE_B2A_BASE64[(leftchar & 3) << 4])
102+
res.append(_PAD)
103+
res.append(_PAD)
357104
elif leftbits == 4:
358-
res.append(table_b2a_base64[(leftchar & 0xF) << 2])
359-
res.append(PAD)
105+
res.append(_TABLE_B2A_BASE64[(leftchar & 0xF) << 2])
106+
res.append(_PAD)
360107
if newline:
361108
res.append("\n")
362109
return "".join(res).encode("ascii")

‎python-stdlib/binascii/manifest.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
metadata(version="2.4.1")
1+
metadata(version="2.5.0")
22

33
module("binascii.py")

‎python-stdlib/binascii/test_binascii.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
if data2 != data:
1313
raise Exception("Error")
1414

15-
a2b_base64(b"as==") == b"j"
15+
s = b"Hello from MicroPython!\n\xff\x80\xa0\xcf"
16+
for i in range(len(s) + 1):
17+
substr = s[:i]
18+
if a2b_base64(b2a_base64(substr)) != substr:
19+
raise Exception("Error")
1620

1721
start = time.time()
1822
for x in range(100000):

0 commit comments

Comments
 (0)