MicroPython 字符串实习

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 代码(即py, extmod, ports/stm32),但不包括第三方代码,例如 lib.

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

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

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

处理发生在以下阶段:

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

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

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

  4. 生成一个枚举,其中的每个条目都将 a 映射MP_QSTR_Foo 到它对应的索引。它qstrdefs.collected.h与连接 qstrdefs*.h,然后将每一行从 转换为Q(Foo)"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 池(以及存储字符串数据的底层“块”)在堆上按需分配,具有最小大小。