MicroPython字符串练习¶
MicroPython 使用 string interning 来保存 RAM 和 ROM。这避免了必须存储相同字符串的重复副本。首先,这适用于代码中的标识符,因为函数或变量名称之类的东西很可能出现在代码的多个位置。在 MicroPython 中,内部字符串称为 QSTR(uniQue STRing)。
QSTR 值(类型为``qstr`` )是 QSTR 池链表的索引。QSTR 存储它们的长度和其内容的散列,以便在重复数据删除过程中进行快速比较。所有处理字符串的字节码操作都使用 QSTR 参数。
编译时 QSTR 生成¶
在 MicroPython C 代码中,应该在最终固件中驻留的任何字符串都写为``MP_QSTR_Foo`` . 在编译时,这将评估为``qstr`` 指向 "Foo"
QSTR 池中索引的值。
在多步骤过程 Makefile
使这项工作有效。概括起来,这个过程分为三个部分:
查找``MP_QSTR_Foo`` 代码中的所有标记。
生成包含所有字符串数据(包括长度和哈希)的静态 QSTR 池。
用
MP_QSTR_Foo
相应的索引替换所有(通过预处理器)。
MP_QSTR_Foo
在两个来源中搜索令牌:
中引用的所有文件
$(SRC_QSTR)
。这是所有 C 代码(即``py``,extmod
,ports/stm32
),但不包括第三方代码,例如lib
.附加``$(QSTR_GLOBAL_DEPENDENCIES)``(包括``mpconfig*.h`` )。
注意: (frozen_mpy.c
由mpy-tool.py生成)有自己的QSTR生成和池。
一些无法使用 MP_QSTR_Foo
语法表示的附加字符串(例如,它们包含非字母数字字符)在变量中 qstrdefs.h
并``qstrdefsport.h`` 通过``$(QSTR_DEFS)`` 变量显式提供 。
处理发生在以下阶段:
qstr.i.last
是将每个输入文件通过 C 预处理器的串联。这意味着将删除任何有条件禁用的代码,并扩展宏。这意味着我们不会向池中添加不会在最终固件中使用的字符串。因为在这个阶段(由于``NO_QSTR`` 添加了宏``QSTR_GEN_EXTRA_CFLAGS`` )没有定义``MP_QSTR_Foo`` 它通过这个阶段不受影响。该文件还包括来自预处理器的注释,其中包括行号信息。请注意,此步骤仅使用已更改的文件,这意味着``qstr.i.last`` 将仅包含自上次编译以来已更改的文件中的数据。qstr.split``是 在 qstr.i.last 上运行后创建的空文件``makeqstrdefs.py split
。它只是用作依赖项来指示该步骤已运行。此脚本为每个输入 C 文件输出一个文件 ,仅包含匹配的 QSTR。每个 QSTR 都打印为 . 此步骤是将现有文件与.genhdr/qstr/...file.c.qstr
,Q(Foo)
.qstr.i.last
qstrdefs.collected.h
是genhdr/qstr/*
使用``makeqstrdefs.py cat`` 连接的输出。现在这是在代码中找到的全套``MP_QSTR_Foo`` ,现在格式化为``Q(Foo)`` ,每行一个,并带有重复项。仅当 qstrs 集已更改时,才会更新此文件。QSTR 数据的散列被写入另一个文件 (qstrdefs.collected.h.hash
),这允许它跨构建跟踪更改。qstrdefs.preprocessed.h
生成一个枚举,其中的``qstrdefs.collected.h``qstrdefs*.h
每个条目``Q(Foo)`` 都将 映射``”Q(Foo)”`` 到它对应的索引。它qstrdefs.collected.h与连接 ,然后将每一行从 转换为”Q(Foo)”以便它们不变地通过预处理器。然后预处理器用于处理``qstrdefs*.h`` . 然后将转换还原为Q(Foo),并保存为``qstrdefs.preprocessed.h`` 。qstrdefs.generated.h
是 的输出``makeqstrdata.py`` 。对于Q(Foo)
qstrdefs.preprocessed.h 中的每个 (加上一些额外的硬编码),它输出QDEF(MP_QSTR_Foo, (const byte*)"hash" "Foo")
.
然后在主编译中,会发生两件事``qstrdefs.generated.h`` :
在 qstr.h 中,每个 QDEF 成为枚举中的一个条目,它
MP_QSTR_Foo
可供代码使用并等于 QSTR 表中该字符串的索引。在 qstr.c 中,实际的 QSTR 数据表是作为
mp_qstr_const_pool->qstrs
.
运行时 QSTR 生成¶
可以在运行时创建额外的 QSTR 池,以便向其中添加字符串。例如,代码:
foo[x] = 3
需要为 的值创建一个 QSTR,x
以便“加载属性”字节码可以使用它。
此外,在编译 Python 代码时,标识符和文字需要创建 QSTR。注意:只有少于 10 个字符的文字才能成为 QSTR。这是因为堆上的常规字符串始终占用最少 16 个字节(一个 GC 块),而 QSTR 允许将它们更有效地打包到池中。
QSTR 池(以及存储字符串数据的底层“块”)在堆上按需分配,具有最小大小。