Python 源码剖析(三)【字符串对象】
三、字符串对象
1、PyStringObject与PyString_Type
2、创建PyStringObject对象
3、Intern 机制
4、字符缓冲池
5、PyStringObject 效率相关问题
6、Hack PyStringObject
1、PyStringObject与PyString_Type
PyStringObject 对象的声明:
[stringobject.h]
typedef struct {
PyObject_VAR_HEAD
long ob_shash;
int ob_sstate;
char ob_sval[];
} PyStringObject;
PyObject_VAR_HEAD 中含有字符串长度ob_size,ob_sval指向字符串存储内存,大小为ob_size+1,且ob_sval[ob_size]='\0'; ob_shash保存字符串哈希值,没计算过的默认为0,计算方法为:
[stringobject.c]
static long string_hash(PyStringObject *a)
{
register int len;
register unsigned char *p;
register long x;
if (a->ob_shash != -)
return a->ob_shash;
len = a->ob_size;
p = (unsigned char *) a->ob_sval;
x = *p << ;
while (--len >= )
x = (*x) ^ *p++;
x ^= a->ob_size;
if (x == -)
x = -;
a->ob_shash = x;
return x;
}
ob_sstate表示该对象是否被Intern。
PyStringObject对应的类型对象:
[stringobject.c]
PyTypeObject PyString_Type = {
PyObject_HEAD_INIT(&PyType_Type)
,
"str",
sizeof(PyStringObject),
sizeof(char),
……
(reprfunc)string_repr, /* tp_repr */
&string_as_number, /* tp_as_number */
&string_as_sequence, /* tp_as_sequence */
&string_as_mapping, /* tp_as_mapping */
(hashfunc)string_hash, /* tp_hash */
, /* tp_call */
……
string_new, /* tp_new */
PyObject_Del, /* tp_free */
};
2、创建PyStringObject对象
最一般的方法PyString_FromString:
[stringobject.c]
PyObject *
PyString_FromString(const char *str)
{
register size_t size;
register PyStringObject *op;
assert(str != NULL);
/*判断字符串长度*/
size = strlen(str);
if (size > INT_MAX) {
PyErr_SetString(PyExc_OverflowError,
"string is too long for a Python string");
return NULL;
}
/*处理null string*/
if (size == && (op = nullstring) != NULL) {
#ifdef COUNT_ALLOCS
null_strings++;
#endif
Py_INCREF(op);
return (PyObject *)op;
}
if (size == && (op = characters[*str & UCHAR_MAX]) != NULL) {
#ifdef COUNT_ALLOCS
one_strings++;
#endif
Py_INCREF(op);
return (PyObject *)op;
}
/* 创建新的PyStringObject对象,并初始化 */
/* Inline PyObject_NewVar */
op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);
if (op == NULL)
return PyErr_NoMemory();
PyObject_INIT_VAR(op, &PyString_Type, size);
op->ob_shash = -;
op->ob_sstate = SSTATE_NOT_INTERNED;
memcpy(op->ob_sval, str, size+);
/* Itern(共享)长度较短的PyStringObject对象 */
if (size == ) {
PyObject *t = (PyObject *)op;
PyString_InternInPlace(&t);
op = (PyStringObject *)t;
nullstring = op;
Py_INCREF(op);
} else if (size == ) {
PyObject *t = (PyObject *)op;
PyString_InternInPlace(&t);
op = (PyStringObject *)t;
characters[*str & UCHAR_MAX] = op;
Py_INCREF(op);
}
return (PyObject *) op;
}
首先判断有没有超过长度限制(INT_MAX 2147483647 ),然后判断是不是为空串,如果是并且之前创建过空串对象就直接返回,接着就判断其是否为一个字符,是而且在Intern机制中创建过就直接返回,否则,就开始申请内存(PyStringObject + size),创建对象并返回。
另一个创建PyStringObject对象的途径:
[stringobject.c]
PyObject* PyString_FromStringAndSize(const char *str, int size)
{
register PyStringObject *op;
/*处理null string*/
if (size == && (op = nullstring) != NULL) {
#ifdef COUNT_ALLOCS
null_strings++;
#endif
Py_INCREF(op);
return (PyObject *)op;
}
if (size == && str != NULL &&
(op = characters[*str & UCHAR_MAX]) != NULL)
{
#ifdef COUNT_ALLOCS
one_strings++;
#endif
Py_INCREF(op);
return (PyObject *)op;
}
/* Inline PyObject_NewVar */
op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);
if (op == NULL)
return PyErr_NoMemory();
PyObject_INIT_VAR(op, &PyString_Type, size);
op->ob_shash = -;
op->ob_sstate = SSTATE_NOT_INTERNED;
if (str != NULL)
memcpy(op->ob_sval, str, size);
op->ob_sval[size] = '\0';
/* share short strings */
if (size == ) {
PyObject *t = (PyObject *)op;
PyString_InternInPlace(&t);
op = (PyStringObject *)t;
nullstring = op;
Py_INCREF(op);
} else if (size == && str != NULL) {
PyObject *t = (PyObject *)op;
PyString_InternInPlace(&t);
op = (PyStringObject *)t;
characters[*str & UCHAR_MAX] = op;
Py_INCREF(op);
}
return (PyObject *) op;
}
和前一个差不多。
3、Intern 机制
现在看下前面提到的Intern机制,即创建PyStringObject对象函数里的PyString_InternInPlace:
[stringobjec.c]
void PyString_InternInPlace(PyObject **p)
{
register PyStringObject *s = (PyStringObject *)(*p);
PyObject *t;
if (s == NULL || !PyString_Check(s))
Py_FatalError("PyString_InternInPlace: strings only please!");
/* If it's a string subclass, we don't really know what putting
it in the interned dict might do. */
if (!PyString_CheckExact(s))
return;
if (PyString_CHECK_INTERNED(s))
return;
if (interned == NULL) {
interned = PyDict_New();
if (interned == NULL) {
PyErr_Clear(); /* Don't leave an exception */
return;
}
}
t = PyDict_GetItem(interned, (PyObject *)s);
if (t) {
Py_INCREF(t);
Py_DECREF(*p);
*p = t;
return;
}
if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < ) {
PyErr_Clear();
return;
}
/* The two references in interned are not counted by refcnt.
The string deallocator will take care of this */
s->ob_refcnt -= ;
PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL;
}
首先进行一系列检测,而核心在于 interned(PyDictObject对象),如果创建对象在interned中,增加interned中对象的引用计数,减少传入对象的引用计数(其为临时变量);如果创建对象不在interned中,将创建对象加入interned,并将其引用计数减2(interned中对象的引用应当无效,否则无法删除)。对象引用计数为0时会被销毁:
[stringobject.c]
static void string_dealloc(PyObject *op)
{
switch (PyString_CHECK_INTERNED(op)) {
case SSTATE_NOT_INTERNED:
break;
case SSTATE_INTERNED_MORTAL:
/* revive dead object temporarily for DelItem */
op->ob_refcnt = ;
if (PyDict_DelItem(interned, op) != )
Py_FatalError(
"deletion of interned string failed");
break;
case SSTATE_INTERNED_IMMORTAL:
Py_FatalError("Immortal interned string died.");
default:
Py_FatalError("Inconsistent interned string state.");
}
op->ob_type->tp_free(op);
}
4、字符缓冲池
PyStringObject 为一个字节的字符对象设计了一个类似PyIntObject小整数对象池一样的对象池characters:
static PyStringObject *characters[UCHAR_MAX + 1];
#define UCHAR_MAX 0xff /* maximum unsigned char value */
由字符串创建的代码可看出characters缓冲池在字符串生成时才开始生成。
虽然在创建PyStringObject时Intern机制只作用于 空串和字符(对应nullstring和characters),但Intern机制会作用到其他地方。
5、PyStringObject 效率相关问题
字符串连接问题,用 '+' 效率低下,连接N个PyStringObject对象将进行N-1次内存申请及搬运,推荐使用join(只分配一次内存)。
'+' 调用string_concat:
[stringobject.c]
static PyObject* string_concat(register PyStringObject *a, register PyObject *bb)
{
register unsigned int size;
register PyStringObject *op;
#define b ((PyStringObject *)bb)
……
size = a->ob_size + b->ob_size;
/* Inline PyObject_NewVar */
op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);
if (op == NULL)
return PyErr_NoMemory();
PyObject_INIT_VAR(op, &PyString_Type, size);
op->ob_shash = -;
op->ob_sstate = SSTATE_NOT_INTERNED;
memcpy(op->ob_sval, a->ob_sval, (int) a->ob_size);
memcpy(op->ob_sval + a->ob_size, b->ob_sval, (int) b->ob_size);
op->ob_sval[size] = '\0';
return (PyObject *) op;
#undef b
}
而join则这样(先计算处list或tuple中PyStringObject的总大小再申请一次内存):
[stringobject.c]
static PyObject* string_join(PyStringObject *self, PyObject *orig)
{
char *sep = PyString_AS_STRING(self);
const int seplen = PyString_GET_SIZE(self);
PyObject *res = NULL;
char *p;
int seqlen = ;
size_t sz = ;
int i;
PyObject *seq, *item;
。。。。。。//获得list中PyStringObject对象的个数,保存在seqlen中
for (i = ; i < seqlen; i++)
{
const size_t old_sz = sz;
item = PySequence_Fast_GET_ITEM(seq, i);
sz += PyString_GET_SIZE(item);
if (i != )
sz += seplen;
}
/* 申请内存空间 */
res = PyString_FromStringAndSize((char*)NULL, (int)sz);
/* 连接list中的每一个PyStringObject对象*/
p = PyString_AS_STRING(res);
for (i = ; i < seqlen; ++i)
{
size_t n;
/* 获得list中的一个PyStringObject对象*/
item = PySequence_Fast_GET_ITEM(seq, i);
n = PyString_GET_SIZE(item);
memcpy(p, PyString_AS_STRING(item), n);
p += n;
if (i < seqlen - )
{
memcpy(p, sep, seplen);
p += seplen;
}
}
Py_DECREF(seq);
return res;
}
6、Hack PyStringObject
可在string_length中添加打印地址代码,运行len()时可观察到相同的字符串地址一样,这就是Intern机制的作用;可在len()中添加代码:
static void ShowCharater()
{
char a = 'a';
PyStringObject** posA = characters+(unsigned short)a;
int i;
for(i = ; i < ; ++i)
{
PyStringObject* strObj = posA[i];
printf("%s, %d\n", strObj->ob_sval, strObj->ob_refcnt);
}
}
观察是否使用缓冲池对象。
Python 源码剖析(三)【字符串对象】的更多相关文章
- 《Python 源码剖析》之对象
py一切皆对象的实现 Python中对象分为两类: 定长(int等), 非定长(list/dict等) 所有对象都有一些相同的东西, 源码中定义为PyObject和PyVarObject, 两个定义都 ...
- Python源码剖析——01内建对象
<Python源码剖析>笔记 第一章:对象初识 对象是Python中的核心概念,面向对象中的"类"和"对象"在Python中的概念都为对象,具体分为 ...
- Python 源码剖析(一)【python对象】
处于研究python内存释放问题,在阅读部分python源码,顺便记录下所得.(基于<python源码剖析>(v2.4.1)与 python源码(v2.7.6)) 先列下总结: ...
- Python开发【源码剖析】 List对象
前言 本文探讨的Python版本为2.7.16,可从官网上下载,把压缩包Python-2.7.16.tgz解压到本地即可 需要基本C语言的知识(要看的懂) PyListObject对象 PyListO ...
- Python源码剖析——02虚拟机
<Python源码剖析>笔记 第七章:编译结果 1.大概过程 运行一个Python程序会经历以下几个步骤: 由解释器对源文件(.py)进行编译,得到字节码(.pyc文件) 然后由虚拟机按照 ...
- python源码剖析学习记录-01
学习<Python源码剖析-深度探索动态语言核心技术>教程 Python总体架构,运行流程 File Group: 1.Core Modules 内部模块,例如:imp ...
- Python源码剖析|百度网盘免费下载|Python新手入门|Python新手学习资料
百度网盘免费下载:Python源码剖析|新手免费领取下载 提取码:g78z 目录 · · · · · · 第0章 Python源码剖析——编译Python0.1 Python总体架构0.2 Pyth ...
- Python 源码剖析 目录
Python 源码剖析 作者: 陈儒 阅读者:春生 版本:python2.5 版本 本博客园的博客记录我会适当改成Python3版本 阅读 Python 源码剖析 对读者知识储备 1.C语言基础知识, ...
- Python 源码剖析(六)【内存管理机制】
六.内存管理机制 1.内存管理架构 2.小块空间的内存池 3.循环引用的垃圾收集 4.python中的垃圾收集 1.内存管理架构 Python内存管理机制有两套实现,由编译符号PYMALLOC_DEB ...
- Django Rest Framework源码剖析(三)-----频率控制
一.简介 承接上篇文章Django Rest Framework源码剖析(二)-----权限,当服务的接口被频繁调用,导致资源紧张怎么办呢?当然或许有很多解决办法,比如:负载均衡.提高服务器配置.通过 ...
随机推荐
- Prism for WPF 搭建一个简单的模块化开发框架(一)
原文:Prism for WPF 搭建一个简单的模块化开发框架(一) 最近闲来无事又想搞搞WPF..... 做个框架吧,可能又是半途而废....总是坚持不下来 不废话了, 先看一下工程结构 布局大概是 ...
- 北京Uber优步司机奖励政策(11月16日~11月22日)
用户组:人民优步“关羽组”(适用于11月16日-11月22日)奖励政策: 滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/ ...
- java 编码二进制写法、十六进制用源代码表示
二进制: int a = 0b10; a其实=2 八进制: int a = 01; a其实=8 十六进制: int a = 0x1; a其实=16
- vim中project多标签和多窗口的使用
1.打开多个窗口 打开多个窗口的命令以下几个: 横向切割窗口 :new+窗口名(保存后就是文件名) :split+窗口名,也可以简写为:sp+窗口名 纵向切割窗口名 :vsplit+窗口名,也可以简写 ...
- libevent学习三(Getting an event_base)
1.一个event_base持有了一系列的事件,并监控和决定哪些事件需要激活, 2.每一个event_base背后都有一个支持其工作的方法(诸如select,poll,epoll,kquene...) ...
- Windows运行机理——消息与消息队列
Windows运行机理这系列文章都是来至于<零基础学Qt4编程>——吴迪,个人觉得写得很好,所以进行了搬运和个人加工 Windows程序设计时一种基于消息的时机驱动方式的设计模式,完全不同 ...
- MySQL☞sign函数
sign( )函数:判断数值的正负性,如果数值是正数,返回值是1,如果该数值是负数,返回值是-1,如果该数值是 0,返回值也是0. 格式: select sign(数值) from 表名 例子: 1. ...
- TW实习日记:第24-25天
项目的交付期是真的赶...一直在不断地修改一些小bug,然后消息推送功能出了一个问题,就是不知道为什么PC端会发送两次消息到移动端后台.其中第一条正常第二条会有乱码不正常,可以说是很奇怪了,一开始都认 ...
- Ducci序列 (Ducci Sequence,ACM/ICPC Seoul 2009,UVa1594)
题目描述: 题目思路: 直接模拟 #include<stdio.h> #include<string.h> #define maxn 105 int less(const ch ...
- [Clr via C#读书笔记]Cp9参数
Cp9参数 可选参数和命名参数 参数设置了默认值(设置要从右到左,有默认值的参数必须放在没有默认值的参数的后面,默认值必须是常量),就可以使用可选参数和命名参数了.向方法传递实参的时候,编译器按照从左 ...