這一章, 就一張圖, 程式碼比較多
PyStringObject
原始碼位置 Include/stringobject.h |
Objects/stringobject.c
定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
typedef struct { PyObject_VAR_HEAD long ob_shash; int ob_sstate; char ob_sval[1]; /* Invariants: * ob_sval contains space for 'ob_size+1' elements. * ob_sval[ob_size] == 0. * ob_shash is the hash of the string or -1 if not computed yet. * ob_sstate != 0 iff the string object is in stringobject.c's * 'interned' dictionary; in this case the two references * from 'interned' to this object are *not counted* in ob_refcnt. */ } PyStringObject; |
說明
1 2 3 4 5 6 7 8 9 10 11 12 13 |
1. PyObject_VAR_HEAD PyStringObject是變長物件, 比定長物件多了一個ob_size欄位 2. ob_shash 儲存字串的hash值, 如果還沒計算等於-1 當string_hash被呼叫, 計算結果會被儲存到這個欄位一份, 後續不再進行計算 3. ob_sstate 如果是interned, !=0, 否則=0 interned後面說 4. char ob_sval[1] 字元指標指向一段記憶體, char陣列指標, 指向一個ob_size+1大小陣列(c中字串最後要多一個字元``表字串結束) |
結構
構造方法
1 2 3 |
PyAPI_FUNC(PyObject *) PyString_FromString(const char *); PyAPI_FUNC(PyObject *) PyString_FromStringAndSize(const char *, Py_ssize_t); |
兩個構造方法其實區別不大,
1 2 3 |
PyString_FromStringAndSize 引數可以為`NULL`, 無論是否為`NULL`, 都會分配`size+1`個位元組空間.(不為NULL的話字元陣列會進行拷貝) PyString_FromString, 引數不能為`NULL`, 且必須是``結束的字元陣列, 會呼叫c 語言string.h模組的strlen()函式計算字串長度, 分配空間, 並將整個字元陣列拷貝到ob_sval指向的記憶體 |
我們關注PyString_FromString
就行
建立過程 PyString_FromString
定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
//預設未初始化, 均為NULL static PyStringObject *characters[UCHAR_MAX + 1]; static PyStringObject *nullstring; PyObject * PyString_FromString(const char *str) { register size_t size; register PyStringObject *op; assert(str != NULL); // 計算引數字元陣列長度 size = strlen(str); // 超長, 報錯(PY_SSIZE_T_MAX平臺相關,32位2GB) if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) { PyErr_SetString(PyExc_OverflowError, "string is too long for a Python string"); return NULL; } // 長度=0, 且nullstring已定義, 返回nullstring if (size == 0 & (op = nullstring) != NULL) { #ifdef COUNT_ALLOCS null_strings++; #endif Py_INCREF(op); return (PyObject *)op; } // 字元緩衝池邏輯 // 長度=1, 且characters[*str & UCHAR_MAX]字元已定義 if (size == 1 && (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(PyStringObject_SIZE + size); if (op == NULL) return PyErr_NoMemory(); // 初始化 PyObject_INIT_VAR(op, &PyString_Type, size); op->ob_shash = -1; //未計算hash, -1 op->ob_sstate = SSTATE_NOT_INTERNED; //未intern, 0 // 將字元陣列拷貝到PyStringObject Py_MEMCPY(op->ob_sval, str, size+1); // 在nullstring和字元緩衝池對應位置未初始化時, 會走到這個邏輯 /* share short strings */ if (size == 0) { PyObject *t = (PyObject *)op; // 走intern, 後面說 PyString_InternInPlace(&t); op = (PyStringObject *)t; // 初始化nullstring nullstring = op; Py_INCREF(op); } else if (size == 1) { PyObject *t = (PyObject *)op; // 走intern, 後面說 PyString_InternInPlace(&t); op = (PyStringObject *)t; // 初始化字元緩衝池對應位置 characters[*str & UCHAR_MAX] = op; Py_INCREF(op); } // 返回 return (PyObject *) op; } |
步驟簡化
1 2 3 4 |
1. 計算長度 2. 長度0, 空字串, 是返回已定義好的nullstring 3. 長度1, 字元, 返回字元緩衝池裡面的 4. 都不是, 分配記憶體, 初始化 |
結論
1 2 |
長度0/長度1, 會用到intern機制 注意, intern機制對長度>1的字串也適用 |
interned機制
interned
1 2 3 4 5 6 7 8 9 |
/* This dictionary holds all interned strings. Note that references to strings in this dictionary are *not* counted in the string's ob_refcnt. When the interned string reaches a refcnt of 0 the string deallocation function will delete the reference from this dictionary. Another way to look at this is that to say that the actual reference count of a string is: s->ob_refcnt + (s->ob_sstate?2:0) */ static PyObject *interned; //指標, 指向PyDictObject |
interned定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
void PyString_InternInPlace(PyObject **p) { register PyStringObject *s = (PyStringObject *)(*p); PyObject *t; // 檢查值使用在PyStringObject上, 派生類不適用 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; // 本身已經intern了(標誌位ob_sstate), 不重複進行, 返回 if (PyString_CHECK_INTERNED(s)) return; // 未初始化字典, 初始化之 if (interned == NULL) { // 注意這裡 interned = PyDict_New(); if (interned == NULL) { PyErr_Clear(); /* Don't leave an exception */ return; } } // 在interned字典中已存在, 修改, 返回intern獨享 t = PyDict_GetItem(interned, (PyObject *)s); if (t) { Py_INCREF(t); Py_DECREF(*p); *p = t; return; } // 在interned字典中不存在, 放進去 if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) 0) { PyErr_Clear(); return; } // 加入interned字典(key-value), 會導致refcnt+2, 去掉, 保證當外部沒有引用時, refcnt=0, 可以進行回收處理, (不-2, refcnt永遠>=2) /* The two references in interned are not counted by refcnt. The string deallocator will take care of this */ Py_REFCNT(s) -= 2; // 修改字串物件的ob_sstate標誌位, SSTATE_INTERNED_MORTAL PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL; } |
使用的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// 構造方法 PyAPI_FUNC(PyObject *) PyString_FromString(const char *); PyAPI_FUNC(PyObject *) PyString_FromStringAndSize(const char *, Py_ssize_t); // SSTATE_INTERNED_MORTAL, 計數0會被回收 PyObject * PyString_InternFromString(const char *cp) { PyObject *s = PyString_FromString(cp); if (s == NULL) return NULL; PyString_InternInPlace(&s); return s; } // SSTATE_INTERNED_IMMORTAL, 永遠不會被銷燬 void PyString_InternImmortal(PyObject **p) |
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
>>> a = '' >>> b = '' >>> id(a) == id(b) True >>> a = 'x' >>> b = 'x' >>> id(a) == id(b) True >>> a = "abc" >>> b = "abc" >>> id(a) == id(b) True |
python原始碼自己也大量使用
1 2 3 4 |
dict_str = PyString_InternFromString("__dict__") lenstr = PyString_InternFromString("__len__") s_true = PyString_InternFromString("true") empty_array = PyString_InternFromString("[]") |
好處
1 |
一旦字串被intern, 會被python儲存到字典中, 整個python執行期間, 系統中有且僅有一個物件. 下次相同字串再進入, 不會重複建立物件. |
字元緩衝池
定義
1 2 3 |
UCHAR_MAX 平臺相關 static PyStringObject *characters[UCHAR_MAX + 1]; |
在上面PyString_FromString
可以看到, 字元緩衝池在使用中初始化(存在直接返回, 不存在建一個, 放interned字典中, 初始化字元緩衝池對應位置)
1 2 3 4 5 6 7 |
PyObject *t = (PyObject *)op; // 走intern, 後面說 PyString_InternInPlace(&t); op = (PyStringObject *)t; // 初始化字元緩衝池對應位置 characters[*str & UCHAR_MAX] = op; |
字串銷燬過程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
static void string_dealloc(PyObject *op) { // 是否intern switch (PyString_CHECK_INTERNED(op)) { // 非, 跳出 -> 回收記憶體 case SSTATE_NOT_INTERNED: break; // 普通, 從interned字典中刪除, 跳出 -> 回收記憶體 case SSTATE_INTERNED_MORTAL: /* revive dead object temporarily for DelItem */ Py_REFCNT(op) = 3; if (PyDict_DelItem(interned, op) != 0) 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."); } // 回收記憶體 Py_TYPE(op)->tp_free(op); } |
效能相關
+
與 join
1 2 3 4 5 |
'a' + 'b' + 'c' or ''.join(['a', 'b', 'c']) |
可以檢視string_concat
方法和string_join
方法的原始碼
1 2 |
string_concat, 一次加=分配一次記憶體空間, 拷貝兩次. N次連結, 需要N-1次記憶體分配. string_join, 計算序列所有元素字串總長度, 用PyString_FromStringAndSize((char*)NULL, sz)分配記憶體空間, 然後逐一拷貝. 一次記憶體操作. |
changelog
1 |
2014-08-08 first version |
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式