MicroPython核心:字符串驻留(String interning)

MicroPython使用字符串驻留可以同时保存在RAM和ROM,这就避免了存储相同字符串的重复副本,这主要适用于代码中的标识符,因为函数或变量名很可能会在代码中的多个地方出现。在MicroPython中,内部字符串被称为QSTR(uniQue STRing唯一字符串)。

QSTR值(类型为qstr)是QSTR池链接表的索引。QSTR会存储其长度及其内容的哈希值,以便在去重过程中进行快速比较。所有处理字符串的字节码操作都使用QSTR参数。

编译时生成QSTR

在 MicroPython C 代码中,任何应在最终固件中内嵌的字符串都被写成 MP_QSTR_Foo。在编译时,这将评估指向QSTR池中"Foo"索引的qstr值。

Makefile 中的一个多步骤过程实现了这一功能。总的来说,这个过程包括三个部分:

  1. 查找代码中的所有MP_QSTR_Foo标记。
  2. 生成一个静态QSTR池,其中包含所有字符串数据(包括长度和哈希值)。
  3. 用相应的索引替换所有MP_QSTR_Foo(通过预处理器)。

MP_QSTR_Foo标记从两个来源中搜索:

  1. $(SRC_QSTR) 中引用的所有文件。这是所有的C代码(即pyextmodports/stm32)但不包括第三方代码,如 lib

  2. 附加的 $(QSTR_GLOBAL_DEPENDENCIES)(包括mpconfig*.h)。

注意:frozen_mpy.c(由mpy-tool.py生成)有自己的QSTR生成和池。

qstrdefs.hqstrdefsport.h 中,通过 $(QSTR_DEFS) 变量明确提供了一些无法用 MP_QSTR_Foo 语法表示的附加字符串(例如,它们包含非字母字符)。

处理过程分为以下几个阶段:

  1. qstr.i.last 是将每个输入文件通过C预处理器处理后的合并结果。这意味着任何有条件禁用的代码都会被删除,宏也会被扩展,这意味着不会向字符串池中添加固件中不会使用的字符串。由于在这一阶段(QSTR_GEN_CFLAGS添加了NO_QSTR 宏)没有定义MP_QSTR_Foo,因此它不受影响地通过了这一阶段。该文件还包括来自预处理器的注释,其中包含行号信息。请注意,这一步只使用已更改的文件,这意味着qstr.i.last只包含自上次编译以来已更改的文件中的数据。
  2. qstr.split是在qstr.i.last上运行makeqstrdefs.py split后创建的空文件。它只是作为一个依赖项,表示该步骤已运行。该脚本为每个输入 C 文件输出一个文件genhdr/qstr/...file.c.qstr,其中只包含匹配的QSTR。每个QSTR 都打印为Q(Foo)。要将现有文件与qstr.i.last中增量更新生成的新数据结合起来,这一步骤必不可少。
  3. qstrdefs.collected.h是使用makeqstrdefs.py catgenhdr/qstr/*连接起来的输出结果。现在这是代码中MP_QSTR_Foo的全集,格式为Q(Foo),每行一个,有重复。只有当QSTR数据集发生变化时,才会更新该文件。QSTR数据的哈希值会写入另一个文件(qstrdefs.collect.h.hash),这样就能跟踪不同版本中的变化。
  4. 生成一个枚举,其中每个条目都将MP_QSTR_Foo映射到相应的索引。它将 qstrdefs.collected.hqstrdefs*.h连接起来,然后将Q(Foo)的每一行转换为"Q(Foo)",这样它们就能原封不动地通过预处理器。然后使用预处理器处理qstrdefs*.h中的任何条件编译。然后撤销转换,返回Q(Foo),并保存为qstrdefs.preprocessed.h
  5. qstrdefs.generated.hmakeqstrdata.py的输出结果。对于qstrdefs.preprocessed.h中的每个Q(Foo)(加上一些额外的硬编码),它都会输出QDEF(MP_QSTR_Foo,(const byte*) "hash" "Foo")

然后,在主编译程序中,qstrdefs.generated.h会发生两件事:

  1. qstr.h中,每个QDEF都成为枚举中的一个条目,这使得MP_QSTR_Foo可用于编写代码,并等于该字符串在QSTR表中的索引。
  2. qstr.c中,实际的QSTR数据表是作为mp_qstr_const_pool->qstrs的元素生成的。

运行时生成QSTR

可以在运行时创建额外的QSTR池,以便向其中添加字符串。例如,代码:

foo[x] = 3

需要为x的值创建一个QSTR,以便"load attr"字节码可以使用它。

此外,在编译Python代码时,标识符和字面量需要创建QSTR。注意:只有短于10个字符的字面量才会成为QSTR。这是因为堆上的普通字符串总是至少占用16个字节(一个 GC 块),而QSTR可以更有效地将它们打包到堆中。

QSTR池(以及存储字符串数据的底层 “块”)在堆上按需分配,并有最小大小限制。

相关推荐

  1. MicroPython核心字符串驻留(String interning)

    2024-01-31 17:58:03       30 阅读
  2. MicroPython核心:编译器

    2024-01-31 17:58:03       44 阅读
  3. MicroPython核心:用C扩展MicroPython

    2024-01-31 17:58:03       35 阅读
  4. MicroPython核心:实现一个模块

    2024-01-31 17:58:03       35 阅读
  5. MicroPython核心(1):源码获取、编译构建

    2024-01-31 17:58:03       31 阅读
  6. Micropython的包管理

    2024-01-31 17:58:03       45 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-31 17:58:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-31 17:58:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-31 17:58:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-31 17:58:03       20 阅读

热门阅读

  1. 32.GitHub基础学习

    2024-01-31 17:58:03       35 阅读
  2. Kotlin开发中有关时间的具体使用

    2024-01-31 17:58:03       33 阅读
  3. Golang中的方法链

    2024-01-31 17:58:03       28 阅读
  4. 本周黄金价格将面临重大风险事件

    2024-01-31 17:58:03       42 阅读
  5. LRU(Least Recently Used)

    2024-01-31 17:58:03       33 阅读
  6. MySQL 索引 create index 详解

    2024-01-31 17:58:03       42 阅读
  7. linux nodejs无法安装canvas模块

    2024-01-31 17:58:03       32 阅读
  8. c#常用的修饰符

    2024-01-31 17:58:03       36 阅读
  9. C代码,控制亮灯的代码

    2024-01-31 17:58:03       32 阅读