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 使这项工作有效。概括起来,这个过程分为三个部分:

  1. 查找``MP_QSTR_Foo`` 代码中的所有标记。

  2. 生成包含所有字符串数据(包括长度和哈希)的静态 QSTR 池。

  3. MP_QSTR_Foo 相应的索引替换所有(通过预处理器)。

MP_QSTR_Foo 在两个来源中搜索令牌:

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

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

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

一些无法使用 MP_QSTR_Foo 语法表示的附加字符串(例如,它们包含非字母数字字符)在变量中 qstrdefs.h 并``qstrdefsport.h`` 通过``$(QSTR_DEFS)`` 变量显式提供 。

处理发生在以下阶段:

  1. qstr.i.last 是将每个输入文件通过 C 预处理器的串联。这意味着将删除任何有条件禁用的代码,并扩展宏。这意味着我们不会向池中添加不会在最终固件中使用的字符串。因为在这个阶段(由于``NO_QSTR`` 添加了宏``QSTR_GEN_EXTRA_CFLAGS`` )没有定义``MP_QSTR_Foo`` 它通过这个阶段不受影响。该文件还包括来自预处理器的注释,其中包括行号信息。请注意,此步骤仅使用已更改的文件,这意味着``qstr.i.last`` 将仅包含自上次编译以来已更改的文件中的数据。

  2. qstr.split``是 qstr.i.last 上运行后创建的空文件``makeqstrdefs.py split 。它只是用作依赖项来指示该步骤已运行。此脚本为每个输入 C 文件输出一个文件 ,仅包含匹配的 QSTR。每个 QSTR 都打印为 . 此步骤是将现有文件与. genhdr/qstr/...file.c.qstr , Q(Foo) . qstr.i.last

  3. qstrdefs.collected.hgenhdr/qstr/* 使用``makeqstrdefs.py cat`` 连接的输出。现在这是在代码中找到的全套``MP_QSTR_Foo`` ,现在格式化为``Q(Foo)`` ,每行一个,并带有重复项。仅当 qstrs 集已更改时,才会更新此文件。QSTR 数据的散列被写入另一个文件 (qstrdefs.collected.h.hash ),这允许它跨构建跟踪更改。

  4. qstrdefs.preprocessed.h 生成一个枚举,其中的``qstrdefs.collected.h`` qstrdefs*.h 每个条目``Q(Foo)`` 都将 映射``”Q(Foo)”`` 到它对应的索引。它qstrdefs.collected.h与连接 ,然后将每一行从 转换为”Q(Foo)”以便它们不变地通过预处理器。然后预处理器用于处理``qstrdefs*.h`` . 然后将转换还原为Q(Foo),并保存为``qstrdefs.preprocessed.h`` 。

  5. qstrdefs.generated.h 是 的输出``makeqstrdata.py`` 。对于 Q(Foo) qstrdefs.preprocessed.h 中的每个 (加上一些额外的硬编码),它输出 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

需要为 的值创建一个 QSTR,x 以便“加载属性”字节码可以使用它。

此外,在编译 Python 代码时,标识符和文字需要创建 QSTR。注意:只有少于 10 个字符的文字才能成为 QSTR。这是因为堆上的常规字符串始终占用最少 16 个字节(一个 GC 块),而 QSTR 允许将它们更有效地打包到池中。

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