8000 Support constant values of aggregate types · Issue #722 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

Support constant values of aggregate types #722

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
pfalcon opened this issue Jun 27, 2014 · 8 comments
Closed

Support constant values of aggregate types #722

pfalcon opened this issue Jun 27, 2014 · 8 comments

Comments

@pfalcon
Copy link
Contributor
pfalcon commented Jun 27, 2014

Currently, unlike CPython, uPy doesn't support constant tuples, dicts, etc. Any such object is constructed at runtime, which takes time, (lot of) VM stack, etc.

It also precludes efficient usage of almost-core features, like in-place OrderedDict's.

@pfalcon pfalcon changed the title Support constant aggregate types Support constant values of aggregate types Jun 28, 2014
@dpgeorge
Copy link
Member
dpgeorge commented Jul 3, 2014

This could be fixed. Would need a new bytecode: LOAD_CONST, and its argument would be a pointer to a uPy object. This pointer would need to be aligned (and we can do that easily). In fact, doing this we could combine all the LOAD_CONST_xxx bytecodes into this one. And then ints, decs, bytes and strings would not need to be allocated using a qstr. Could even combine LOAD_CONST_SMALL_INT. But, all this would increase size of the bytecode by about 4 bytes per LOAD_CONST_xxx opcode on stmhal (2 bytes average for pointer alignment, 2 going from 2 byte qstr to 4 byte pointer).

The reason this wasn't done from the start is because I didn't want any pointers in the bytecode, and didn't want to make a constant table (as CPython does).

@pfalcon
Copy link
Contributor Author
pfalcon commented Jul 3, 2014

and didn't want to make a constant table (as CPython does).

But why? That's what I thought about to implement this, would also probably help with bytecode persistence.

@dpgeorge
Copy link
Member
dpgeorge commented Jul 4, 2014

But why?

Wanted to keep it simple. But I'd rather just put pointers directly in the bytecode: they would take about the same number of bytes because with a table you need an index to the table (1-2 bytes) and then the pointer in the table (4 bytes on 32 bit). With inline pointers, you need alignment bytes (average 2) plus the pointer, making it about the same.

I think this change is a good idea (be it a const table or inline pointers). It will reduce RAM usage overall, since at the moment, eg a float, is made a qstr at parse time, then each time it is used new RAM is allocated for the object and the qstr is parsed. Not optimal at all, in terms of speed or RAM.

You would add LOAD_CONST_OBJ, which simply pushed the given ptr (inline or from table) onto the stack. This would replace LOAD_CONST_INT, LOAD_CONST_DEC, LOAD_CONST_BYTES and LOAD_CONST_ELLIPSIS. And could be used for pushing whole tuples. In all these cases the overall RAM usage (bytecode + objects) would decrease or remain about the same.

The more frequently used ones, LOAD_CONST_{FALSE, TRUE, NONE, SMALL_INT} would remain, for reasons of keeping the bytecode small in RAM. So would LOAD_CONST_STR, because that simply takes a qstr as argument and turns it into an object by setting some bits.

@dpgeorge
Copy link
Member
dpgeorge commented Jul 6, 2014

Um, note that CPython doesn't make constant dicts. Only true constants (numbers, strings, tuples) are put in the constant table for the bytecode. Any dicts that you specify in a script are generated at runtime using the bytecode (as uPy implements at the moment).

Of course, this is because dicts are mutable, so a new one must be created each time that part of the bytecode is run.

If you wanted to do something like OrderedDict({1:2,3:4}), that would require a fair bit of extra code in the compiler to work efficiently. And anyway, the OrderedDict that you create can't be constant because it may be mutated. Furthermore, it's not well defined initialising an OrderedDict from a dict, because the order of the items in the dict is not definied. Eg try OrderedDict({1:2,3:4,5:6,7:8,9:10}) in CPython.

@dpgeorge
Copy link
Member
dpgeorge commented Jul 7, 2014

For us, we could us the following idiom to make constant dicts: const({1:2,3:4}).

@pfalcon
Copy link
Contributor Author
pfalcon commented Jul 12, 2014

with a table you need an index to the table (1-2 bytes) and then the pointer in the table (4 bytes on 32 bit). With inline pointers, you need alignment bytes (average 2) plus the pointer, making it about the same.

Exactly, inline pointers will waste 2 bytes on average. But table indexes would be varlen, and with great probability fit in 1 byte (well, that would largely depend on whether strings are stored in this table). So, 1 byte saving can be counted on here. And if same constant is accessed few times, it will be even higher.

Of course, table means extra dereference (and cache pollution), but well, that's the price to pay. The main reason I keep thinking about a table is because of the need to implement #222. And I always thought of inline pointers within bytecode as a hack. They were introduced to resolve bigger issue of global code object array, so I didn't say much against them, but they're dirty hack ;-). And here's the latest proof they're: #747 ;-).

dpgeorge added a commit that referenced this issue Jan 13, 2015
This allows to directly load a Python object to the Python stack.  See
issue #722 for background.
dpgeorge added a commit that referenced this issue Feb 8, 2015
Previous to this patch, a big-int, float or imag constant was interned
(made into a qstr) and then parsed at runtime to create an object each
time it was needed.  This is wasteful in RAM and not efficient.  Now,
these constants are parsed straight away in the parser and turned into
objects.  This allows constants with large numbers of digits (so
addresses issue #1103) and takes us a step closer to #722.
@dpgeorge
Copy link
Member

I've had a patch for some time now for making a const table of pointers per function and having the bytecode index that table. But...

Exactly, inline pointers will waste 2 bytes on average.

No, surprisingly they don't. They waste 0 bytes in almost all cases due to a very fortuitous layout of the bytecodes that mean that the ptr argument to the bytecode op is always aligned. The main use of pointers in bytecode is the MAKE_FUNCTION opcode, which is almost always followed by a STORE_NAME:

MAKE_FUNCTION <ptr> -- 1 + 4 bytes
STORE_NAME <qstr> -- 1 + 2 bytes

As you can see, combined they make exactly 8 bytes, so a long sequence of these pair of ops will always have the ptr aligned if the first one is aligned (valid for a 32 bit machine). And a long sequence of these ops is generally what you get when defining functions/methods.

So, with this patch both code size and RAM usage increase noticeably. I've not been able to think of a way to reduce it.

tannewt pushed a commit to tannewt/circuitpython that referenced this issue Mar 28, 2018
dpgeorge added a commit to dpgeorge/micropython that referenced this issue Apr 14, 2022
This commit adds support to the parser so that tuples which contain only
constant elements (bool, int, str, bytes, etc) are immediately converted to
a tuple object.  This makes it more efficient to use tuples containing
constant data because they no longer need to be created at runtime by the
bytecode (or native code).

Furthermore, with this improvement constant tuples that are part of frozen
code are now able to be stored fully in ROM (this will be implemented in
later commits).

Code size is increased by about 400 bytes on Cortex-M4 platforms.

See related issue micropython#722.

Signed-off-by: Damien George <damien@micropython.org>
@dpgeorge
Copy link
Member

Commit f2040bf implemented a global constant table per .mpy file.

Commit 35c0cff added support for constant tuples (where all elements are constant literals).

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

No branches or pull requests

2 participants
0