speed_python.rst 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. Maximising MicroPython Speed
  2. ============================
  3. .. contents::
  4. This tutorial describes ways of improving the performance of MicroPython code.
  5. Optimisations involving other languages are covered elsewhere, namely the use
  6. of modules written in C and the MicroPython inline assembler.
  7. The process of developing high performance code comprises the following stages
  8. which should be performed in the order listed.
  9. * Design for speed.
  10. * Code and debug.
  11. Optimisation steps:
  12. * Identify the slowest section of code.
  13. * Improve the efficiency of the Python code.
  14. * Use the native code emitter.
  15. * Use the viper code emitter.
  16. * Use hardware-specific optimisations.
  17. Designing for speed
  18. -------------------
  19. Performance issues should be considered at the outset. This involves taking a view
  20. on the sections of code which are most performance critical and devoting particular
  21. attention to their design. The process of optimisation begins when the code has
  22. been tested: if the design is correct at the outset optimisation will be
  23. straightforward and may actually be unnecessary.
  24. Algorithms
  25. ~~~~~~~~~~
  26. The most important aspect of designing any routine for performance is ensuring that
  27. the best algorithm is employed. This is a topic for textbooks rather than for a
  28. MicroPython guide but spectacular performance gains can sometimes be achieved
  29. by adopting algorithms known for their efficiency.
  30. RAM Allocation
  31. ~~~~~~~~~~~~~~
  32. To design efficient MicroPython code it is necessary to have an understanding of the
  33. way the interpreter allocates RAM. When an object is created or grows in size
  34. (for example where an item is appended to a list) the necessary RAM is allocated
  35. from a block known as the heap. This takes a significant amount of time;
  36. further it will on occasion trigger a process known as garbage collection which
  37. can take several milliseconds.
  38. Consequently the performance of a function or method can be improved if an object is created
  39. once only and not permitted to grow in size. This implies that the object persists
  40. for the duration of its use: typically it will be instantiated in a class constructor
  41. and used in various methods.
  42. This is covered in further detail :ref:`Controlling garbage collection <controlling_gc>` below.
  43. Buffers
  44. ~~~~~~~
  45. An example of the above is the common case where a buffer is required, such as one
  46. used for communication with a device. A typical driver will create the buffer in the
  47. constructor and use it in its I/O methods which will be called repeatedly.
  48. The MicroPython libraries typically provide support for pre-allocated buffers. For
  49. example, objects which support stream interface (e.g., file or UART) provide ``read()``
  50. method which allocates new buffer for read data, but also a ``readinto()`` method
  51. to read data into an existing buffer.
  52. Floating Point
  53. ~~~~~~~~~~~~~~
  54. Some MicroPython ports allocate floating point numbers on heap. Some other ports
  55. may lack dedicated floating-point coprocessor, and perform arithmetic operations
  56. on them in "software" at considerably lower speed than on integers. Where
  57. performance is important, use integer operations and restrict the use of floating
  58. point to sections of the code where performance is not paramount. For example,
  59. capture ADC readings as integers values to an array in one quick go, and only then
  60. convert them to floating-point numbers for signal processing.
  61. Arrays
  62. ~~~~~~
  63. Consider the use of the various types of array classes as an alternative to lists.
  64. The `array` module supports various element types with 8-bit elements supported
  65. by Python's built in `bytes` and `bytearray` classes. These data structures all store
  66. elements in contiguous memory locations. Once again to avoid memory allocation in critical
  67. code these should be pre-allocated and passed as arguments or as bound objects.
  68. When passing slices of objects such as `bytearray` instances, Python creates
  69. a copy which involves allocation of the size proportional to the size of slice.
  70. This can be alleviated using a `memoryview` object. `memoryview` itself
  71. is allocated on heap, but is a small, fixed-size object, regardless of the size
  72. of slice it points too.
  73. .. code:: python
  74. ba = bytearray(10000) # big array
  75. func(ba[30:2000]) # a copy is passed, ~2K new allocation
  76. mv = memoryview(ba) # small object is allocated
  77. func(mv[30:2000]) # a pointer to memory is passed
  78. A `memoryview` can only be applied to objects supporting the buffer protocol - this
  79. includes arrays but not lists. Small caveat is that while memoryview object is live,
  80. it also keeps alive the original buffer object. So, a memoryview isn't a universal
  81. panacea. For instance, in the example above, if you are done with 10K buffer and
  82. just need those bytes 30:2000 from it, it may be better to make a slice, and let
  83. the 10K buffer go (be ready for garbage collection), instead of making a
  84. long-living memoryview and keeping 10K blocked for GC.
  85. Nonetheless, `memoryview` is indispensable for advanced preallocated buffer
  86. management. ``readinto()`` method discussed above puts data at the beginning
  87. of buffer and fills in entire buffer. What if you need to put data in the
  88. middle of existing buffer? Just create a memoryview into the needed section
  89. of buffer and pass it to ``readinto()``.
  90. Identifying the slowest section of code
  91. ---------------------------------------
  92. This is a process known as profiling and is covered in textbooks and
  93. (for standard Python) supported by various software tools. For the type of
  94. smaller embedded application likely to be running on MicroPython platforms
  95. the slowest function or method can usually be established by judicious use
  96. of the timing ``ticks`` group of functions documented in `utime`.
  97. Code execution time can be measured in ms, us, or CPU cycles.
  98. The following enables any function or method to be timed by adding an
  99. ``@timed_function`` decorator:
  100. .. code:: python
  101. def timed_function(f, *args, **kwargs):
  102. myname = str(f).split(' ')[1]
  103. def new_func(*args, **kwargs):
  104. t = utime.ticks_us()
  105. result = f(*args, **kwargs)
  106. delta = utime.ticks_diff(utime.ticks_us(), t)
  107. print('Function {} Time = {:6.3f}ms'.format(myname, delta/1000))
  108. return result
  109. return new_func
  110. MicroPython code improvements
  111. -----------------------------
  112. The const() declaration
  113. ~~~~~~~~~~~~~~~~~~~~~~~
  114. MicroPython provides a ``const()`` declaration. This works in a similar way
  115. to ``#define`` in C in that when the code is compiled to bytecode the compiler
  116. substitutes the numeric value for the identifier. This avoids a dictionary
  117. lookup at runtime. The argument to ``const()`` may be anything which, at
  118. compile time, evaluates to an integer e.g. ``0x100`` or ``1 << 8``.
  119. .. _Caching:
  120. Caching object references
  121. ~~~~~~~~~~~~~~~~~~~~~~~~~~
  122. Where a function or method repeatedly accesses objects performance is improved
  123. by caching the object in a local variable:
  124. .. code:: python
  125. class foo(object):
  126. def __init__(self):
  127. ba = bytearray(100)
  128. def bar(self, obj_display):
  129. ba_ref = self.ba
  130. fb = obj_display.framebuffer
  131. # iterative code using these two objects
  132. This avoids the need repeatedly to look up ``self.ba`` and ``obj_display.framebuffer``
  133. in the body of the method ``bar()``.
  134. .. _controlling_gc:
  135. Controlling garbage collection
  136. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  137. When memory allocation is required, MicroPython attempts to locate an adequately
  138. sized block on the heap. This may fail, usually because the heap is cluttered
  139. with objects which are no longer referenced by code. If a failure occurs, the
  140. process known as garbage collection reclaims the memory used by these redundant
  141. objects and the allocation is then tried again - a process which can take several
  142. milliseconds.
  143. There may be benefits in pre-empting this by periodically issuing `gc.collect()`.
  144. Firstly doing a collection before it is actually required is quicker - typically on the
  145. order of 1ms if done frequently. Secondly you can determine the point in code
  146. where this time is used rather than have a longer delay occur at random points,
  147. possibly in a speed critical section. Finally performing collections regularly
  148. can reduce fragmentation in the heap. Severe fragmentation can lead to
  149. non-recoverable allocation failures.
  150. The Native code emitter
  151. -----------------------
  152. This causes the MicroPython compiler to emit native CPU opcodes rather than
  153. bytecode. It covers the bulk of the MicroPython functionality, so most functions will require
  154. no adaptation (but see below). It is invoked by means of a function decorator:
  155. .. code:: python
  156. @micropython.native
  157. def foo(self, arg):
  158. buf = self.linebuf # Cached object
  159. # code
  160. There are certain limitations in the current implementation of the native code emitter.
  161. * Context managers are not supported (the ``with`` statement).
  162. * Generators are not supported.
  163. * If ``raise`` is used an argument must be supplied.
  164. The trade-off for the improved performance (roughly twices as fast as bytecode) is an
  165. increase in compiled code size.
  166. The Viper code emitter
  167. ----------------------
  168. The optimisations discussed above involve standards-compliant Python code. The
  169. Viper code emitter is not fully compliant. It supports special Viper native data types
  170. in pursuit of performance. Integer processing is non-compliant because it uses machine
  171. words: arithmetic on 32 bit hardware is performed modulo 2**32.
  172. Like the Native emitter Viper produces machine instructions but further optimisations
  173. are performed, substantially increasing performance especially for integer arithmetic and
  174. bit manipulations. It is invoked using a decorator:
  175. .. code:: python
  176. @micropython.viper
  177. def foo(self, arg: int) -> int:
  178. # code
  179. As the above fragment illustrates it is beneficial to use Python type hints to assist the Viper optimiser.
  180. Type hints provide information on the data types of arguments and of the return value; these
  181. are a standard Python language feature formally defined here `PEP0484 <https://www.python.org/dev/peps/pep-0484/>`_.
  182. Viper supports its own set of types namely ``int``, ``uint`` (unsigned integer), ``ptr``, ``ptr8``,
  183. ``ptr16`` and ``ptr32``. The ``ptrX`` types are discussed below. Currently the ``uint`` type serves
  184. a single purpose: as a type hint for a function return value. If such a function returns ``0xffffffff``
  185. Python will interpret the result as 2**32 -1 rather than as -1.
  186. In addition to the restrictions imposed by the native emitter the following constraints apply:
  187. * Functions may have up to four arguments.
  188. * Default argument values are not permitted.
  189. * Floating point may be used but is not optimised.
  190. Viper provides pointer types to assist the optimiser. These comprise
  191. * ``ptr`` Pointer to an object.
  192. * ``ptr8`` Points to a byte.
  193. * ``ptr16`` Points to a 16 bit half-word.
  194. * ``ptr32`` Points to a 32 bit machine word.
  195. The concept of a pointer may be unfamiliar to Python programmers. It has similarities
  196. to a Python `memoryview` object in that it provides direct access to data stored in memory.
  197. Items are accessed using subscript notation, but slices are not supported: a pointer can return
  198. a single item only. Its purpose is to provide fast random access to data stored in contiguous
  199. memory locations - such as data stored in objects which support the buffer protocol, and
  200. memory-mapped peripheral registers in a microcontroller. It should be noted that programming
  201. using pointers is hazardous: bounds checking is not performed and the compiler does nothing to
  202. prevent buffer overrun errors.
  203. Typical usage is to cache variables:
  204. .. code:: python
  205. @micropython.viper
  206. def foo(self, arg: int) -> int:
  207. buf = ptr8(self.linebuf) # self.linebuf is a bytearray or bytes object
  208. for x in range(20, 30):
  209. bar = buf[x] # Access a data item through the pointer
  210. # code omitted
  211. In this instance the compiler "knows" that ``buf`` is the address of an array of bytes;
  212. it can emit code to rapidly compute the address of ``buf[x]`` at runtime. Where casts are
  213. used to convert objects to Viper native types these should be performed at the start of
  214. the function rather than in critical timing loops as the cast operation can take several
  215. microseconds. The rules for casting are as follows:
  216. * Casting operators are currently: ``int``, ``bool``, ``uint``, ``ptr``, ``ptr8``, ``ptr16`` and ``ptr32``.
  217. * The result of a cast will be a native Viper variable.
  218. * Arguments to a cast can be a Python object or a native Viper variable.
  219. * If argument is a native Viper variable, then cast is a no-op (i.e. costs nothing at runtime)
  220. that just changes the type (e.g. from ``uint`` to ``ptr8``) so that you can then store/load
  221. using this pointer.
  222. * If the argument is a Python object and the cast is ``int`` or ``uint``, then the Python object
  223. must be of integral type and the value of that integral object is returned.
  224. * The argument to a bool cast must be integral type (boolean or integer); when used as a return
  225. type the viper function will return True or False objects.
  226. * If the argument is a Python object and the cast is ``ptr``, ``ptr``, ``ptr16`` or ``ptr32``,
  227. then the Python object must either have the buffer protocol with read-write capabilities
  228. (in which case a pointer to the start of the buffer is returned) or it must be of integral
  229. type (in which case the value of that integral object is returned).
  230. The following example illustrates the use of a ``ptr16`` cast to toggle pin X1 ``n`` times:
  231. .. code:: python
  232. BIT0 = const(1)
  233. @micropython.viper
  234. def toggle_n(n: int):
  235. odr = ptr16(stm.GPIOA + stm.GPIO_ODR)
  236. for _ in range(n):
  237. odr[0] ^= BIT0
  238. A detailed technical description of the three code emitters may be found
  239. on Kickstarter here `Note 1 <https://www.kickstarter.com/projects/214379695/micro-python-python-for-microcontrollers/posts/664832>`_
  240. and here `Note 2 <https://www.kickstarter.com/projects/214379695/micro-python-python-for-microcontrollers/posts/665145>`_
  241. Accessing hardware directly
  242. ---------------------------
  243. .. note::
  244. Code examples in this section are given for the Pyboard. The techniques
  245. described however may be applied to other MicroPython ports too.
  246. This comes into the category of more advanced programming and involves some knowledge
  247. of the target MCU. Consider the example of toggling an output pin on the Pyboard. The
  248. standard approach would be to write
  249. .. code:: python
  250. mypin.value(mypin.value() ^ 1) # mypin was instantiated as an output pin
  251. This involves the overhead of two calls to the :class:`~machine.Pin` instance's :meth:`~machine.Pin.value()`
  252. method. This overhead can be eliminated by performing a read/write to the relevant bit
  253. of the chip's GPIO port output data register (odr). To facilitate this the ``stm``
  254. module provides a set of constants providing the addresses of the relevant registers.
  255. A fast toggle of pin ``P4`` (CPU pin ``A14``) - corresponding to the green LED -
  256. can be performed as follows:
  257. .. code:: python
  258. import machine
  259. import stm
  260. BIT14 = const(1 << 14)
  261. machine.mem16[stm.GPIOA + stm.GPIO_ODR] ^= BIT14