這篇文章主要介紹了PHP7中字符串處理邏輯的優(yōu)化方法,具有一定借鑒價(jià)值,需要的朋友可以參考下。希望大家閱讀完這篇文章后大有收獲。下面讓小編帶著大家一起了解一下。
為沙縣等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及沙縣網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為做網(wǎng)站、網(wǎng)站設(shè)計(jì)、沙縣網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!
?? 先看如下示例代碼:
$a = 'foo'; $b = 'bar'; $c = "I like $a and $b";
?? 在 PHP 5.6 中執(zhí)行代碼,得到的 opcode 輸出如下圖:
?? 具體的執(zhí)行過程:
?? 在 PHP 7 中執(zhí)行代碼,得到的 opcode 輸出如下入:
?? PHP 7 中對(duì)字符串的處理過程相對(duì)簡(jiǎn)單,首先創(chuàng)建一個(gè)堆棧 stack,然后將要連接的字符串片段存入棧空間,最后只需要分配一次內(nèi)存空間,然后將結(jié)果從棧中移動(dòng)到所分配的內(nèi)存空間中。相較于 PHP 5,PHP 7 在處理過程中避免了反復(fù)申請(qǐng)內(nèi)存的過程。而 PHP 7 在字符串處理方面性能的提升得益于數(shù)據(jù)結(jié)構(gòu) rope 的使用。
?? Rope 是一棵二叉樹,其中每個(gè)葉子節(jié)點(diǎn)存儲(chǔ)的是字符串的子串,每個(gè)非葉子節(jié)點(diǎn)存儲(chǔ)的是位于該節(jié)點(diǎn)左側(cè)的每個(gè)葉子節(jié)點(diǎn)所存儲(chǔ)的子串中所包含的字符總數(shù)(方便了根據(jù)索引查找指定字符)。
?? 優(yōu)勢(shì):
?? 不足:
?? Rope 的本質(zhì)是一棵二叉樹,其基本的插入、刪除、搜索操作與二叉樹相同。這里只對(duì)字符串的聯(lián)接(concat)和拆分(split)進(jìn)行介紹。
另外,為了盡量減少操作的時(shí)間復(fù)雜度,可以將 Rope 構(gòu)造成一棵 AVL 樹,這樣在每次操作完成后實(shí)現(xiàn)自平衡。但這樣可能會(huì)增加一些撤銷操作的復(fù)雜度。例如,將兩棵高度不等的 Rope 進(jìn)行 concat 操作后形成的新的 Rope,通過節(jié)點(diǎn)旋轉(zhuǎn)實(shí)現(xiàn)自平衡后可能會(huì)破壞原先兩棵樹的結(jié)構(gòu),這樣如果要撤銷之前的 concat 操作會(huì)變得非常復(fù)雜。
?? concat 操作相對(duì)簡(jiǎn)單,只需要將兩棵 Rope 樹聯(lián)接成一棵新的 Rope 樹即可
?? 對(duì) Rope 樹進(jìn)行拆分的時(shí)候,會(huì)遇到兩種情況:拆分的位置正好位于某個(gè)節(jié)點(diǎn)的末尾或者拆分的位置在某個(gè)葉子節(jié)點(diǎn)的中間。對(duì)于第二種情況,我們可以把相應(yīng)的葉子節(jié)點(diǎn)進(jìn)行拆分,從而轉(zhuǎn)化成第一種情況。而對(duì)于第一種情況,根據(jù)節(jié)點(diǎn)所處的位置又可以分為兩種情況:


class Node:
def __init__(self, data):
self.parent = None
self.left = None
self.right = None
self.data = data
self.weight = 0
def __repr__(self):
return str(self.__dict__)
def __str__(self):
return str(self.__dict__)
class Rope:
# 每個(gè)葉子節(jié)點(diǎn)最多存儲(chǔ) 5 個(gè)字符,包括空字符在內(nèi)
LEAF_DATA_LEN = 5
def __init__(self):
self.root = None
def create_rope(self, parent, data, left_index, right_index):
"""
創(chuàng)建 rope 數(shù)據(jù)結(jié)構(gòu)
:param parent: 父節(jié)點(diǎn)(根節(jié)點(diǎn)的父節(jié)點(diǎn)為空)
:param data: 用于創(chuàng)建 rope 數(shù)據(jù)結(jié)構(gòu)的原始數(shù)據(jù),這里只接受 list 和 str 兩種類型
:param left_index: 起始位置索引
:param right_index: 結(jié)束位置索引
:return: Node
"""
if isinstance(data, str):
data = list(data)
elif not isinstance(data, list):
return
if right_index - left_index > self.LEAF_DATA_LEN:
node = Node("")
node.parent = parent
middle_index = (left_index + right_index) // 2
node.weight = middle_index - left_index
node.left = self.create_rope(node, data, left_index, middle_index)
node.right = self.create_rope(node, data, middle_index, right_index)
else:
node = Node(data[left_index: right_index])
node.parent = parent
node.weight = right_index - left_index
if node.parent is None:
self.root = node
return node
@staticmethod
def calc_weight(node):
"""
計(jì)算節(jié)點(diǎn) weight 值
:param node:
:return:
"""
if node is None:
return 0
init_weight = node.weight
while node.right is not None:
node = node.right
init_weight += node.weight
return init_weight
def concat_rope(self, data1, data2):
"""
字符串連接
:param data1:
:param data2:
:return:
"""
r1 = Rope()
r1.create_rope(None, data1, 0, len(data1))
r2 = Rope()
r2.create_rope(None, data2, 0, len(data2))
node = Node("")
node.left = r1.root
node.right = r2.root
r1.root.parent = node
r2.root.parent = node
node.weight = self.calc_weight(r1)
self.root = node
def split_rope(self, data, index):
"""
字符串拆分
:param data: 要拆分的字符串
:param index: 拆分的位置(字符串索引從 0 開始計(jì)算)
:return: Rope
"""
if index < 0 or index > len(data) - 1:
return
node = self.create_rope(None, data, 0, len(data))
original_index = index
if index == self.root.weight - 1:
# 在根節(jié)點(diǎn)拆分
rope_left = node.left
rope_left.parent = None
rope_right = node.right
rope_right.parent = None
return rope_left, rope_right
elif index < self.root.weight - 1:
while index < node.weight - 1 and node.data == "":
node = node.left
else:
while index > node.weight - 1 and node.data == "":
index -= node.weight
node = node.right
if node.data != "":
# index 落在了最左側(cè)和最右側(cè)的兩個(gè)葉子節(jié)點(diǎn)
if original_index < self.root.weight - 1:
# index 落在了最左側(cè)的葉子節(jié)點(diǎn)
rope_left = self.create_rope(None, node.data[0:index + 1], 0, index + 1)
rope_right = self.root
# 更新 rope_right 的 weight
node.data = node.data[index + 1:]
while node is not None:
node.weight -= (index + 1)
node = node.parent
else:
# index 落在了最右側(cè)的葉子節(jié)點(diǎn)
rope_left = self.root
rope_right = self.create_rope(None, node.data[index + 1:], 0, len(node.data[index + 1:]))
node.data = node.data[0:index + 1]
elif index == node.weight - 1:
# index 正好落在了節(jié)點(diǎn)的末尾
if original_index < self.root.weight:
# index 落在了最左側(cè)分支中的非葉子節(jié)點(diǎn)的末尾
weight_sub = node.weight
rope_left = node.left
rope_left.parent = None
node.left = None
rope_right = self.root
# 更新節(jié)點(diǎn) weight
while node is not None:
node.weight -= weight_sub
node = node.parent
else:
# index 落在了最右側(cè)分支中的非葉子節(jié)點(diǎn)的末尾
rope_left = self.root
rope_right = node.right
rope_right.parent = None
node.right = None
else:
stack = []
if original_index < self.root.weight:
# index 落在了左子樹中的節(jié)點(diǎn)
index -= node.weight
rope_left = node
rope_right = self.root
node.parent.left = None
node.parent = None
node = node.right
else:
# index 落在了右子樹中的節(jié)點(diǎn)
rope_left = self.root
stack.append(node.right)
rope_right = None
node.right = None
node = node.left
while node.data == "" and index >= 0:
if index < node.weight - 1:
stack.append(node.right)
node.right = None
node = node.left
elif index > node.weight - 1:
node = node.right
index -= node.weight
else:
stack.append(node.right)
node.right = None
break
if node.data != "":
# 需要拆分葉子節(jié)點(diǎn)
new_node = Node(node.data[index + 1:])
new_node.weight = node.weight - index - 1
stack.append(new_node)
node.data = node.data[0:index + 1]
# 更新節(jié)點(diǎn)的 weight 信息
while node is not None:
if node.data != "":
node.weight = len(node.data)
else:
node.weight = self.calc_weight(node.left)
node = node.parent
# 組裝 rope_right并更新節(jié)點(diǎn)的 weight 值
left_node = None
while len(stack) > 0:
root_node = Node("")
if left_node is None:
left_node = stack.pop()
root_node = left_node
else:
root_node.left = left_node
left_node.parent = root_node
right_node = stack.pop()
root_node.right = right_node
right_node.parent = root_node
root_node.weight = self.calc_weight(root_node.left)
left_node = root_node
if rope_right is None:
# index > self.root.weight - 1
rope_right = root_node
else:
# index < self.root.weight - 1
tmp = rope_right
while tmp.left is not None:
tmp = tmp.left
tmp.left = root_node
root_node.parent = tmp
while tmp.parent is not None:
tmp.weight = self.calc_weight(tmp.left)
tmp = tmp.parent
rope_right = tmp
rope_right.weight = self.calc_weight(rope_right.left)
return rope_left, rope_right
rope = Rope()
data = "php is a script language"
index = 18
left, right = rope.split_rope(data, index)
print(left)
print(right)?? PHP 5 中 zval 中定義的字符串的長度為 int(有符號(hào)整型) 類型,這就導(dǎo)致即使是在 64 為機(jī)器上字符串的長度也不能超過 231 (LP64 數(shù)據(jù)模型中,int 永遠(yuǎn)只有 32 位)。
// zval 中對(duì)字符串的定義,長度為 int 類型
typedef union _zvalue_value {
long lval;
double dval;
struct {
char *val; /* C string buffer, NULL terminated */
int len; /* String length : num of ASCII chars */
} str; /* string structure */
HashTable *ht;
zend_object_value obj;
zend_ast *ast;
} zvalue_value;
// 類實(shí)例結(jié)構(gòu)體中對(duì)字符串的定義,此時(shí)字符串的長度為 zend_uint 類型,相較于 int 類型,字符串長度提升了一倍
struct _zend_class_entry {
char type;
const char *name; /* C string buffer, NULL terminated */
zend_uint name_length; /* String length : num of ASCII chars */
struct _zend_class_entry *parent;
int refcount;
zend_uint ce_flags;
/*……*/
}
// hashtable 的 key 中對(duì)字符串的定義,長度為 uint 類型
typedef struct _zend_hash_key {
const char *arKey; /* C string buffer, NULL terminated */
uint nKeyLength; /* String length : num of ASCII chars */
ulong h;
} zend_hash_key;?? PHP 5 中很多地方都支持二進(jìn)制字符串,由于字符串沒有一個(gè)固定的結(jié)構(gòu)體,這就導(dǎo)致很多地方都有對(duì)字符串的定義。同時(shí),由于各處對(duì)字符串長度的定義不同,導(dǎo)致各處支持的字符串長度也不同。
?? 在較老版本的 PHP 中,由于沒有引入 interned string,同一個(gè)字符串如果在多處被使用,為了互不影響,就需要復(fù)制多份出來,這就造成了對(duì)內(nèi)存空間大量消耗。
static PHP_FUNCTION(session_id)
{
char *name = NULL;
int name_len, argc = ZEND_NUM_ARGS();
if (zend_parse_parameters(argc TSRMLS_CC, "|s", &name, &name_len) == FAILURE) {
return;
}
/* …… */
if (name) {
if (PS(id)) {
efree(PS(id));
}
PS(id) = estrndup(name, name_len);
}
}?? 以 PHP 函數(shù) session_id 為例,該函數(shù)最終將 name 完全復(fù)制一份然后存入 PS(id)。這樣,后續(xù)代碼對(duì) name 進(jìn)行的任何操作都不會(huì)影響 PS(id) 中存儲(chǔ)的值。
?? 從 PHP 5.4 起,為了解決上述字符串復(fù)制導(dǎo)致內(nèi)存消耗大的問題,PHP 引入了 interned string 。另外,由于 interned string 需要共享內(nèi)存空間,所以在線程安全的 PHP 版本中并不被支持。
?? 所謂 interned string,即一個(gè)字符串在一個(gè)進(jìn)程中只存儲(chǔ)一次。當(dāng)一個(gè) php-fpm 進(jìn)程啟動(dòng)時(shí),會(huì)申請(qǐng)一塊 size 為 1MB 的 buffer,持久化的字符串(字符串常量、函數(shù)名、變量名、類名、方法名……)會(huì)被存儲(chǔ)到該緩沖并添加到一個(gè) hashtable 中。由于 PHP 處理的 request 之間是相互獨(dú)立的,所以一個(gè) request 處理完成后,其間在內(nèi)存中產(chǎn)生的數(shù)據(jù)都會(huì)被銷毀。為了在處理 request 的過程中也能使用 interned string,在 request 到來時(shí) PHP 會(huì)記錄當(dāng)前 interned string buffer 的 top 位置,在該 request 請(qǐng)求的處理完成后,top 位置之后消耗的空間又會(huì)被恢復(fù)。

- interned string 的初始化,只會(huì)發(fā)生在 PHP 啟動(dòng)以及 PHP 腳本的編譯階段
- interned string 是只讀的,既不能修改,也不能銷毀
- 由于 interned string buffer 只有 1MB 的空間,當(dāng) 1MB 的空間存滿后,后續(xù)的字符串處理還會(huì)回到引入 interned string 之前的狀態(tài)
?? interned string 的優(yōu)勢(shì)
?? 由于需要操作 hashtable,interned string 的初始化和創(chuàng)建會(huì)比較復(fù)雜,也正因?yàn)槿绱耍⒉皇撬械淖址歼m合存入 interned string。
struct _zend_string {
zend_refcounted_h gc;
zend_ulong h; /* hash value */
size_t len;
char val[1];
};
typedef struct _zend_refcounted_h {
uint32_t refcount; /* reference counter 32-bit */
union {
uint32_t type_info;
} u;
} zend_refcounted_h;?? PHP 7 中字符串有了固定的結(jié)構(gòu) zend_string
?? PHP 7 中字符串的長度類型為 size_t,字符串的長度不再局限于 PHP 5 中的 231 ,尤其在 64 位的機(jī)器上,字符串的長度取決于平臺(tái)支持的最大長度。
??PHP 7 中字符串內(nèi)容的存儲(chǔ)不再像之前使用 char * ,而是使用了 struct hack,使得字符串內(nèi)容和 zend_string 一起存儲(chǔ)在一塊連續(xù)的內(nèi)存空間。同時(shí),zend_string 還內(nèi)嵌了字符串的 hash 值,這樣,對(duì)于一個(gè)指定的字符串只需要進(jìn)行一次 hash 計(jì)算。
??PHP 7 中字符串引入了引用計(jì)數(shù),這樣,只要字符串還在被使用,就不用擔(dān)心會(huì)被銷毀。
static PHP_FUNCTION(session_id)
{
zend_string *name = NULL;
int argc = ZEND_NUM_ARGS();
/* …… */
if (name) {
if (PS(id)) {
zend_string_release(PS(id));
}
PS(id) = zend_string_copy(name);
}
}
static zend_always_inline zend_string *zend_string_copy(zend_string *s)
{
if (!ZSTR_IS_INTERNED(s)) {
GC_REFCOUNT(s)++;
}
return s;
}
static zend_always_inline void zend_string_release(zend_string *s)
{
if (!ZSTR_IS_INTERNED(s)) {
if (--GC_REFCOUNT(s) == 0) {
pefree(s, GC_FLAGS(s) & IS_STR_PERSISTENT);
}
}
}??仍以函數(shù) session_id 為例,設(shè)置 session_id 時(shí)不再需要將 name 完全復(fù)制,而只是將 name 的引用計(jì)數(shù)加 1。在刪除字符串時(shí)也是同樣,將字符串的引用計(jì)數(shù)減 1,只有引用計(jì)數(shù)為 0 時(shí)才會(huì)真正銷毀字符串。
??PHP 7 中的 interned string 不再需要單獨(dú)申請(qǐng) interned string buffer 來存儲(chǔ),而是在 zend_string 的 gc 中將 type_info 標(biāo)記為 IS_STR_INTERNED。這樣,在銷毀字符串時(shí),zend_string_release 會(huì)檢查字符串是否為 interned string,如果是則不會(huì)進(jìn)行任何操作。
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享PHP7中字符串處理邏輯的優(yōu)化方法內(nèi)容對(duì)大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,遇到問題就找創(chuàng)新互聯(lián),詳細(xì)的解決方法等著你來學(xué)習(xí)!
當(dāng)前文章:PHP7中字符串處理邏輯的優(yōu)化方法
文章路徑:http://www.chinadenli.net/article28/igjijp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號(hào)、網(wǎng)站導(dǎo)航、商城網(wǎng)站、虛擬主機(jī)、標(biāo)簽優(yōu)化、云服務(wù)器
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)