千家信息网

python字符串驻留技术实例分析

发表于:2025-01-18 作者:千家信息网编辑
千家信息网最后更新 2025年01月18日,这篇文章主要讲解了"python字符串驻留技术实例分析",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"python字符串驻留技术实例分析"吧!1、什么是
千家信息网最后更新 2025年01月18日python字符串驻留技术实例分析

这篇文章主要讲解了"python字符串驻留技术实例分析",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"python字符串驻留技术实例分析"吧!

1、什么是"字符串驻留"?

字符串驻留是一种编译器/解释器的优化方法,它通过缓存一般性的字符串,从而节省字符串处理任务的空间和时间。

这种优化方法不会每次都创建一个新的字符串副本,而是仅为每个适当的不可变值保留一个字符串副本,并使用指针引用之。每个字符串的唯一拷贝被称为它的intern,并因此而得名 String Interning。

String Interning 一般被译为"字符串驻留"或"字符串留用",在某些语言中可能习惯用 String Pool(字符串常量池)的概念,其实是对同一种机制的不同表述。intern 作为名词时,是"实习生、实习医生"的意思,在此可以理解成"驻留物、驻留值"。

查找字符串 intern 的方法可能作为公开接口公开,也可能不公开。现代编程语言如 Java、Python、PHP、Ruby、Julia 等等,都支持字符串驻留,以使其编译器和解释器做到高性能。

2、为什么要驻留字符串?

字符串驻留提升了字符串比较的速度。如果没有驻留,当我们要比较两个字符串是否相等时,它的时间复杂度将上升到 O(n),即需要检查两个字符串中的每个字符,才能判断出它们是否相等。

但是,如果字符串是固定的,由于相同的字符串将使用同一个对象引用,因此只需检查指针是否相同,就足以判断出两个字符串是否相等,不必再逐一检查每个字符。由于这是一个非常普遍的操作,因此,它被典型地实现为指针相等性校验,仅使用一条完全没有内存引用的机器指令。

字符串驻留减少了内存占用。Python 避免内存中充斥多余的字符串对象,通过享元设计模式共享和重用已经定义的对象,从而优化内存占用。

3、Python的字符串驻留

像大多数其它现代编程语言一样,Python 也使用字符串驻留来提高性能。在 Python 中,我们可以使用is运算符,检查两个对象是否引用了同一个内存对象。

因此,如果两个字符串对象引用了相同的内存对象,则is运算符将得出True,否则为False。

>>> 'python' is 'python'

True

我们可以使用这个特定的运算符,来判断哪些字符串是被驻留的。在 CPython 的,字符串驻留是通过以下函数实现的,声明在 unicodeobject.h 中,定义在 unicodeobject.c 中。

PyAPI_FUNC(void) PyUnicode_InternInPlace(PyObject **);

为了检查一个字符串是否被驻留,CPython 实现了一个名为PyUnicode_CHECK_INTERNED的宏,同样是定义在 unicodeobject.h 中。

这个宏表明了 Python 在PyASCIIObject结构中维护着一个名为interned的成员变量,它的值表示相应的字符串是否被驻留。

#define PyUnicode_CHECK_INTERNED(op)       (((PyASCIIObject *)(op))->state.interned)

4、字符串驻留的原理

在 CPython 中,字符串的引用被一个名为interned的 Python 字典所存储、访问和管理。 该字典在第一次调用字符串驻留时,被延迟地初始化,并持有全部已驻留字符串对象的引用。

4.1 如何驻留字符串?

负责驻留字符串的核心函数是PyUnicode_InternInPlace,它定义在 unicodeobject.c 中,当调用时,它会创建一个准备容纳所有驻留的字符串的字典interned,然后登记入参中的对象,令其键和值都使用相同的对象引用。

以下函数片段显示了 Python 实现字符串驻留的过程。

void  PyUnicode_InternInPlace(PyObject **p)  {      PyObject *s = *p;        .........        // Lazily build the dictionary to hold interned Strings      if (interned == NULL) {          interned = PyDict_New();          if (interned == NULL) {              PyErr_Clear();              return;          }      }        PyObject *t;        // Make an entry to the interned dictionary for the      // given object      t = PyDict_SetDefault(interned, s, s);        .........       // The two references in interned dict (key and value) are      // not counted by refcnt.      // unicode_dealloc() and _PyUnicode_ClearInterned() take      // care of this.      Py_SET_REFCNT(s, Py_REFCNT(s) - 2);        // Set the state of the string to be INTERNED      _PyUnicode_STATE(s).interned = SSTATE_INTERNED_MORTAL;  }

4.2 如何清理驻留的字符串?

清理函数从interned字典中遍历所有的字符串,调整这些对象的引用计数,并把它们标记为NOT_INTERNED,使其被垃圾回收。一旦所有的字符串都被标记为NOT_INTERNED,则interned字典会被清空并删除。

这个清理函数就是_PyUnicode_ClearInterned,在unicodeobject.c 中定义。

void  _PyUnicode_ClearInterned(PyThreadState *tstate)  {      .........        // Get all the keys to the interned dictionary      PyObject *keys = PyDict_Keys(interned);        .........        // Interned Unicode strings are not forcibly deallocated;      // rather, we give them their stolen references back      // and then clear and DECREF the interned dict.        for (Py_ssize_t i = 0; i < n; i++) {          PyObject *s = PyList_GET_ITEM(keys, i);            .........            switch (PyUnicode_CHECK_INTERNED(s)) {          case SSTATE_INTERNED_IMMORTAL:              Py_SET_REFCNT(s, Py_REFCNT(s) + 1);              break;          case SSTATE_INTERNED_MORTAL:              // Restore the two references (key and value) ignored              // by PyUnicode_InternInPlace().              Py_SET_REFCNT(s, Py_REFCNT(s) + 2);              break;          case SSTATE_NOT_INTERNED:              /* fall through */          default:              Py_UNREACHABLE();          }            // marking the string to be NOT_INTERNED          _PyUnicode_STATE(s).interned = SSTATE_NOT_INTERNED;      }        // decreasing the reference to the initialized and      // access keys object.      Py_DECREF(keys);        // clearing the dictionary      PyDict_Clear(interned);        // clearing the object interned      Py_CLEAR(interned);  }

5、字符串驻留的实现

既然了解了字符串驻留及清理的内部原理,我们就可以找出 Python 中所有会被驻留的字符串。

为了做到这点,我们要做的就是在 CPython 源代码中查找PyUnicode_InternInPlace 函数的调用,并查看其附近的代码。下面是在 Python 中关于字符串驻留的一些有趣的发现。

5.1 变量、常量与函数名

CPython 对常量(例如函数名、变量名、字符串字面量等)执行字符串驻留。

以下代码出自codeobject.c,它表明在创建新的PyCode对象时,解释器将对所有编译期的常量、名称和字面量进行驻留。

PyCodeObject *  PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount,                            int nlocals, int stacksize, int flags,                            PyObject *code, PyObject *consts, PyObject *names,                            PyObject *varnames, PyObject *freevars, PyObject *cellvars,                            PyObject *filename, PyObject *name, int firstlineno,                            PyObject *linetable)  {        ........        if (intern_strings(names) < 0) {          return NULL;      }        if (intern_strings(varnames) < 0) {          return NULL;      }        if (intern_strings(freevars) < 0) {          return NULL;      }        if (intern_strings(cellvars) < 0) {          return NULL;      }        if (intern_string_constants(consts, NULL) < 0) {          return NULL;      }        ........    }

5.2 字典的键

CPython 还会驻留任何字典对象的字符串键。

当在字典中插入元素时,解释器会对该元素的键作字符串驻留。以下代码出自dictobject.c,展示了实际的行为。

有趣的地方:在PyUnicode_InternInPlace函数被调用处有一条注释,它问道,我们是否真的需要对所有字典中的全部键进行驻留?

int  PyDict_SetItemString(PyObject *v, const char *key, PyObject *item)  {      PyObject *kv;      int err;      kv = PyUnicode_FromString(key);      if (kv == NULL)          return -1;        // Invoking String Interning on the key      PyUnicode_InternInPlace(&kv); /* XXX Should we really? */        err = PyDict_SetItem(v, kv, item);      Py_DECREF(kv);      return err;  }

5.3 任何对象的属性

Python 中对象的属性可以通过setattr函数显式地设置,也可以作为类成员的一部分而隐式地设置,或者在其数据类型中预定义。

CPython 会驻留所有这些属性名,以便实现快速查找。以下是函数PyObject_SetAttr的代码片段,该函数定义在文件object.c中,负责为 Python 对象设置新属性。

int  PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)  {        ........        PyUnicode_InternInPlace(&name);        ........  }

5.4 显式地驻留

Python 还支持通过sys模块中的intern函数进行显式地字符串驻留。

当使用任何字符串对象调用此函数时,该字符串对象将被驻留。以下是sysmodule.c文件的代码片段,它展示了在sys_intern_impl函数中的字符串驻留过程。

static PyObject *  sys_intern_impl(PyObject *module, PyObject *s)  {        ........        if (PyUnicode_CheckExact(s)) {          Py_INCREF(s);          PyUnicode_InternInPlace(&s);          return s;      }        ........  }

6、字符串驻留的其它发现

只有编译期的字符串会被驻留。在解释时或编译时指定的字符串会被驻留,而动态创建的字符串则不会。

Python猫注:这一条规则值得展开思考,我曾经在上面踩过坑……有两个知识点,我相信 99% 的人都不知道:字符串的 join() 方法是动态创建字符串,因此其创建的字符串不会被驻留;常量折叠机制也发生在编译期,因此有时候容易把它跟字符串驻留搞混淆。

包含 ASCII 字符和下划线的字符串会被驻留。在编译期间,当对字符串字面量进行驻留时,CPython确保仅对匹配正则表达式[a-zA-Z0-9_]*的常量进行驻留,因为它们非常贴近于 Python 的标识符。

注:关于 Python 中标识符的命名规则,在 Python2 版本只有"字母、数字和下划线",但在 Python 3.x 版本中,已经支持 Unicode 编码。

感谢各位的阅读,以上就是"python字符串驻留技术实例分析"的内容了,经过本文的学习后,相信大家对python字符串驻留技术实例分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

字符 字符串 对象 函数 字典 编译 两个 内存 常量 代码 检查 解释 实例 实例分析 技术 分析 相同 属性 方法 解释器 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 网络安全大会乌镇 本服务器设置于美利坚合众国 什么是网络安全接入方案 2018年国际网络安全 游戏服务器接口 舟山电子网络技术咨询热线 湖北教育云网络安全测试在哪里 数据库数据的批量修改操作 校园网络安全规划用到的工具 武汉市健康码系统用的什么数据库 2021网络安全宣传标语 数据库信息算不算知识产权 如何完善网络安全管理 咸宁软件开发制作 杀手3连不上ps5服务器 无锡技术管理软件开发平台 素材站下载都是自己的服务器吗 山东省网络安全大赛 获奖 gplp网络安全 要建立统一高效网络安全什么机制 广州市淘库网络技术有限公司盖章 数据库是什么专业学的课 高性能服务器开箱 保障网络安全的根本目的 巨杉数据库技术架构 风暴英雄 数据库 陕西电脑服务器机箱加工厂家 网盘服务器核心数和内存大小 网络安全配置电脑 svn服务器 删除
0