Comparison of overheads of micropython operations

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
cefn
Posts: 230
Joined: Tue Aug 09, 2016 10:58 am

Comparison of overheads of micropython operations

Post by cefn » Fri Feb 16, 2018 7:09 pm

Noted @peterhinch's comment at viewtopic.php?f=16&t=2894#p17176
there is a good reason why methods should be used in device drivers rather than properties
But the reason wasn't mentioned. Perhaps it's obvious to everyone, but I found myself wondering what is the reasoning here?

If it's an efficiency question, is there any reference material or approach I can take to get a feel for what operations are more or less expensive for the constrained CPU, FLASH and RAM resources and the underlying strategy Micropython uses for each python operation?

I found http://docs.micropython.org/en/v1.9.3/e ... ained.html really informative but that mostly looks into the choice of ways to store data, strategies for putting code on the board and ways to avoid fragmentation. It doesn't suggest what sort of resources are committed by any particular choices a developer might make when implementing a particular behaviour in python.

I wonder what is the cost of a new module, a new class, a new function, a module-level list, a dict. Are there any language structures which interfere with other optimisation strategies, slowing down the whole thing? Ideally as I code I would be seeing hotspots around operations which I only use if forced, and have the confidence that other operations will be translated into really efficient and minimal bytecode.

Although some of these questions are fundamental computer science ones, and hence explain themselves (e.g. searching through a list for equality vs using a lookup) there are a bunch which relate to strategies which Micropython has adopted compared to CPython.

Presumably the tradeoffs go both ways - some things which are cheap in CPython are expensive in Micropython. Not only is there an inevitable tradeoff between memory usage and processor time, (and Micropython is by definition low-memory), I imagine there are features which are supported in Micropython for python3.4 compatibility, but have never been optimised since experts would simply work around using them.

When I have two complete alternate implementations to try out, I can do some simple profiling (e.g. repeat an operation 100000 times), but there are usually lots of ways to skin the cat, it would be pretty useful to know what operations are pricy as this would help to choose the small number of candidate implementations to test.

What are the things which people in the know about Micropython internals tend to avoid because of the overheads and why?

stijn
Posts: 735
Joined: Thu Apr 24, 2014 9:13 am

Re: Comparison of overheads of micropython operations

Post by stijn » Fri Feb 16, 2018 7:58 pm

Here's one typical example: viewtopic.php?f=2&t=4250
Also there's http://docs.micropython.org/en/latest/e ... ython.html

For the rest: more than once I assumed some technique was more performant than another one, only to find out via profiling the difference was too small to care about inside an actual application. Sometimes I'll first try to see the amount of bytecode executed (e.g. in mp_execute_bytecode in vm.c, or by dumping it) to get a rough estimation, but that still isn't as accurate as actualy profiling.
So if you really want to make sure, measure it.
Also unless you actually are having performance problems, time might be spent better elsewhere instead of trying to make something more performant which hasn't proven the need for it in the first place..

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Comparison of overheads of micropython operations

Post by dhylands » Fri Feb 16, 2018 8:58 pm

I try to allocate a buffer and reuse it. Often times this means I need to generate more code, but by reducing the amount of reallocations then you reduce heap fragmentation.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Comparison of overheads of micropython operations

Post by pythoncoder » Sat Feb 17, 2018 7:53 am

@cefn The reason that properties are avoided in the official MicroPython library is that access is slow compared to methods.

It's also worth noting that MicroPython is optimised for code size rather than performance. In some instances CPython can have a major speed advantage, e.g. this example where MicroPython lacks this mathematical optimisation.
Peter Hinch
Index to my micropython libraries.

cefn
Posts: 230
Joined: Tue Aug 09, 2016 10:58 am

Re: Comparison of overheads of micropython operations

Post by cefn » Sat Feb 17, 2018 9:49 am

The reason that properties are avoided in the official MicroPython library is that access is slow compared to methods.
Thanks for clarifying. When you say slow, do you mean the kind of thing which a programmer might expect based simply on complexity (e.g. there being an extra lookup) or something much worse, like an enumeration which scales O(n) for the number of properties.

I can see how any indirection has a cost, but mainly trying to get an idea of areas where the resource demands of a micropython operation are counter-intuitively high and perhaps develop a model of why. Similarly if certain styles of programming go with the grain of micropython where others should be avoided (by contrast with python).

Your range example is a good one, but is there something more mainstream that should be avoided because it breaks optimisations, such as choosing to split a module in two where one would do, or adding keyword args to a method. I had no intuition that 'in range(z)' had optimisations in the first place, so that was lucky!

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Comparison of overheads of micropython operations

Post by pythoncoder » Sat Feb 17, 2018 11:53 am

My understanding is that accessing properties involves a greater search depth than methods; each search is O(1). But I'm only going on what I've gathered from Damien and Paul.

I think this observation needs to be taken in context. In device drivers, especially for objects like Pin instances, μs matter: performance is paramount. In code which is not time critical I would use properties where there was a clear API benefit in doing so. As always, the key to optimisation is profiling: finding the 5% of code which is most critical and give it hell :!:

In cases where performance problems might arise with some feature, the beauty of Python is the ease of hacking a quick benchmark...
Peter Hinch
Index to my micropython libraries.

stijn
Posts: 735
Joined: Thu Apr 24, 2014 9:13 am

Re: Comparison of overheads of micropython operations

Post by stijn » Sat Feb 17, 2018 11:57 am

pythoncoder wrote:
Sat Feb 17, 2018 7:53 am
that access is slow compared to methods.
In general, yes, though it's not always that simple. I measured this once and on a pc differences for a user-defined Python class, getters were on average slower than a function (in the order of some nanoseconds per call IIRC). Likewise for setters, but the difference was smaller.
However for a custom class defined in C it was the other way around because it implemented it's own mp_obj_type_t->attr which happened to have a faster lookup for getters/setters than the default one.
Which still leads to the same conclusion: 'slow' is hard to quantify to begin with, and it's also hard to make claims which always hold true, so if you want to make sure you'll have to measure in your specific situation anyway.

cefn
Posts: 230
Joined: Tue Aug 09, 2016 10:58 am

Re: Comparison of overheads of micropython operations

Post by cefn » Sat Feb 17, 2018 12:27 pm

the beauty of Python is the ease of hacking a quick benchmark
I take your point but given the number of decisions to exploit language features or make structural commitments in a typical library design it seems infeasible to compare literally everything with benchmarks. Without a deeper understanding I might find myself comparing two equally non-performant approaches, having overlooked the one which had inherent optimisations on the platform.

Let me share an example from a different type of execution environment. I found this document to be very informative for ensuring that server code became accelerated, and the differences in performance by ensuring these were taken into account were orders of magnitude.

https://github.com/petkaantonov/bluebir ... on-killers

Of course, Micropython thankfully doesn't have the black magic of a JIT compiler but I wonder to myself whether I really know the underlying cost of development choices I'm making, e.g. to use a Class rather than a dict, a lambda rather than a named function defined in a module. It may be that actually Micropython just doesn't have these sorts of issues because the resource costs correspond closely with the computer-science fundamentals as a routine matter because of the nature of the project.
Last edited by cefn on Sat Feb 17, 2018 12:31 pm, edited 1 time in total.

cefn
Posts: 230
Joined: Tue Aug 09, 2016 10:58 am

Re: Comparison of overheads of micropython operations

Post by cefn » Sat Feb 17, 2018 12:30 pm

However for a custom class defined in C it was the other way around because it implemented it's own mp_obj_type_t->attr which happened to have a faster lookup for getters/setters than the default one
...and I should clarify I am exploring if there are any gotchas in the use of python language structures running against a stock firmware image. Certainly there is a lot to be gained in performance terms by stepping into C and optimising there if you can.

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: Comparison of overheads of micropython operations

Post by deshipu » Sat Feb 17, 2018 12:47 pm

I don't think you should be choosing the programming constructs in your program based on microsecond differences in execution time. First of all, this is going to change dramatically between different ports and different versions of MicroPython. Second, if you really care about such differences, you should really be implementing your function in assembly.

You should always be choosing your constructs based on their meaning. For example, don't use properties for things that have side effects — programmers will expect them to be idempotent. Don't return None or NaN on error, there are exceptions for that, and figuring out where that NaN came from after the fact is really not fun. Don't use global state and functions to represent devices that would be much better represented with classes — the user might want to have several of them connected, or to mock them for testing. And so on.

In the end, Python is so slow, that it doesn't really matter much what constructs you use. If you care about speed, write that part in C or assembly.

Post Reply