本文主要介绍了MicroPython代码性能优化的方法,包括语言以及编译等方面的内容。MicroPython开发高性能代码一般遵循以下阶段:
- 设计时就要考虑性能
- 优化代码及性能调试
- 性能调优的步骤:
- 确定代码中最慢的部分
- 提高Python代码执行效率
- 使用本机代码发射器
- 使用viper代码发射器
- 针对硬件进行优化
在设计时就考虑性能问题
性能问题应该一开始就应该考虑,特别是要关注最关键的代码部分的设计。当进行代码测试时,性能优化就开始了,如果设计得当,优化就会很简单,甚至是不必要的。
算法
程序性能设计时最重要的是要确保采用最佳算法,这是教科书的观点而不是MicroPython手册主题,但有时采用有效率的算法能实现性能的大幅提升。
内存分配
为了设计高效的MicroPython代码,有必要了解一下解释器的内存分配方式。当创建对象或增大对象大小时(例如,将条目添加到列表),解释器会从内存堆块中分配必要的内存,这个过程需要大量的时间,有时还会触发垃圾收集机制,可能需要几毫秒的时间。因此,减少对象的创建并控制其变化可以提高函数或方法的性能,同时也意味着对象在其使用期间会持续存在。通常,对象在类的构造函数中创建,并在各种方法中使用。
缓冲区
上面提到的内存分配是需要缓冲区的常见情况,就像用于与设备通信的缓冲区,驱动程序会在构造函数中创建缓冲区,并在调用I/O方法时反复使用它。
MicroPython库提供了对预分配缓冲区的支持,例如,为支持流接口(文件或UART)的对象提供读取时分配新缓冲区的方法read(),还提供了将数据读入现有缓冲区的方法readinto()。
浮点数
一些MicroPython端口在堆上分配浮点数,而另一些端口可能缺乏专用的浮点协处理器,不得不在“软件”中以比整数低得多的速度执行算术运算。如果性能很重要,请使用整数运算,并将浮点的使用限制在性能不重要的代码部分。例如,将ADC读数作为整数值快速捕获到数组中,然后将其转换为浮点数进行信号处理。
数组
为减少内存分配,可以考虑使用各种类型的数组类作为列表的替代方案。array
模块支持各种元素类型,其中Python内置bytes和bytearray类支持8位元素。这些数据结构都将元素存储在连续的内存位置。为了避免在关键代码中分配内存,应该预先分配内存,并将其作为参数或绑定对象传递。
当传递对象切片(例如bytearray实例)时,Python 会创建一个副本,其中涉及到与切片大小成比例的内存分配,可以使用memoryview
进行改进。memoryview
本身是在堆上分配的,但它是一个小的、固定大小的对象,无论它指向的切片的大小如何。对memoryview
切片会创建一个新的memoryview
,因此不能在中断服务例程中进行此操作。此外,切片语法a:b会因为实例化对象slice(a, b)引起进一步的内存分配。
ba = bytearray(10000) # 大数组
func(ba[30:2000]) # 传递副本, 新分配 ~2K
mv = memoryview(ba) # 分配一个小对象
func(mv[30:2000]) # 传递内存指针
memoryview
只能用于支持缓冲区协议的对象(包括数组但不包括列表)。需要注意的是,当memoryview
对象处于活动状态时,它也会使原始缓冲区对象保持活动状态。所以,memoryview
并不是万能的灵丹妙药。例如,在上面的示例中,如果使用了10K缓冲区,且只需要其中的30:2000字节,最好还是进行切片,释放这10K缓冲区(为垃圾回收做好准备),而不是使用一个长期存在的memoryview
阻止GC回收这10K的缓冲区