2006年 11 月發(fā)布了 PHP V5.2,它包括許多新增功能和錯(cuò)誤修正。它廢止了 5.1 版并被推薦給所有 PHP V5用戶進(jìn)行升級(jí)。我最喜歡的實(shí)驗(yàn)室環(huán)境 —— Windows®、Apache、MySQL、PHP (WAMP) —— 已經(jīng)被引入了 V5.2的新軟件包中(請(qǐng)參閱 參考資料)。您將在那里找到在 Windows® XP 或 2003 計(jì)算機(jī)上安裝 PHP V5.2、MySQL 和 Apache 的應(yīng)用程序。您可以十分輕松地進(jìn)行安裝,它有很多不錯(cuò)的小的管理優(yōu)點(diǎn),并且我十分誠懇地推薦使用它。
雖然對(duì)于 Windows 用戶來說,這是最簡單的軟件包,但是在 Linux 上配置 PHP 時(shí)您需要添加以下代碼:--memory-limit-enabled
(適用于您服務(wù)器的任何其他選項(xiàng)除外)。不過,在 Windows 下,提供了一個(gè)解決此問題的函數(shù)。
PHP V5.2 中有很多改進(jìn)之處,并且一個(gè)至關(guān)重要的領(lǐng)域是內(nèi)存管理。從 README.ZEND_MM 中準(zhǔn)確地引述就是:“新內(nèi)存管理器(PHP5.2 以及更高版本)的目標(biāo)是減少內(nèi)存分配開銷并加速內(nèi)存管理。”
下面是 V5.2 發(fā)行說明中的一些關(guān)鍵內(nèi)容:
--disable-zend-memory-manager
配置選項(xiàng)--enable-malloc-mm
配置選項(xiàng),調(diào)試構(gòu)建時(shí)此配置選項(xiàng)將被默認(rèn)啟用以允許使用內(nèi)部和外部內(nèi)存調(diào)試程序ZEND_MM_MEM_TYPE
和 ZEND_MM_SEG_SIZE
環(huán)境變量調(diào)整內(nèi)存管理器為了理解這些新增功能的含義,我們需要深入研究內(nèi)存管理中的藝術(shù),并考慮為什么分配開銷和運(yùn)行速度是大問題。
![]() ![]() |
![]()
|
計(jì)算中開發(fā)最快速的一項(xiàng)技術(shù)是內(nèi)存和數(shù)據(jù)存儲(chǔ),它們是受不斷增加速度和存儲(chǔ)大小這樣持續(xù)的需求而驅(qū)動(dòng)的。早期的計(jì)算機(jī)使用卡作為內(nèi)存,然后轉(zhuǎn)向了芯片技術(shù)。您能想象在只有 1 KB RAM內(nèi)存的計(jì)算機(jī)工作的情景嗎?很多早期的計(jì)算機(jī)程序員就曾使用過。這些先驅(qū)者很快就意識(shí)到,要在技術(shù)限制下工作,他們將必須細(xì)心地用瑣碎的命令避免系統(tǒng)過載。
身為 PHP 開發(fā)人員,與使用 C++或其他更嚴(yán)格的語言編碼的同事相比,我們所在的環(huán)境更方便進(jìn)行編碼。在我們的世界里,我們自己不必?fù)?dān)心如何處理系統(tǒng)內(nèi)存,因?yàn)?PHP將為我們處理這個(gè)問題。但是,在其他編程領(lǐng)域里,負(fù)責(zé)任的編碼人員將使用各種函數(shù)確保執(zhí)行的命令不會(huì)覆蓋其他一些程序數(shù)據(jù) ——因而,破壞了程序的運(yùn)行。
內(nèi)存管理通常是由來自編碼人員的請(qǐng)求處理的,以分配和釋放內(nèi)存塊。分配塊 可以保存任何類型的數(shù)據(jù),并且此過程將為該數(shù)據(jù)隔開一定量的內(nèi)存,并當(dāng)操作需要訪問數(shù)據(jù)時(shí)為應(yīng)用程序提供訪問方法。人們期望程序在完成任何操作后釋放分配的內(nèi)存,并允許系統(tǒng)和其他程序員使用該內(nèi)存。如果程序沒有把內(nèi)存釋放回系統(tǒng),則稱為內(nèi)存泄露。
泄露是任何運(yùn)行程序都存在的普遍問題,并且某種程度內(nèi)通常是可以接受的,尤其是當(dāng)我們知道運(yùn)行程序?qū)⒘⒓唇K止并釋放默認(rèn)分配給程序的所有內(nèi)存。
由于隨機(jī)運(yùn)行和終止程序,像幾乎所有客戶機(jī)應(yīng)用程序一樣,這是個(gè)問題。期望服務(wù)器應(yīng)用程序不確定地運(yùn)行而不終止或重新啟動(dòng),這使得內(nèi)存管理對(duì)于服務(wù)器守護(hù)程序編程絕對(duì)的至關(guān)重要。在長時(shí)間運(yùn)行的程序中,即使一個(gè)小的泄露最后都將發(fā)展為系統(tǒng)衰弱問題,因?yàn)閮?nèi)存塊已被使用并且永遠(yuǎn)不被釋放。
![]() ![]() |
![]()
|
正如使用任何語言編寫一樣,用 PHP 編寫的永久性服務(wù)器守護(hù)程序有很多可能的用途。但是當(dāng)我們出于這些目的開始使用 PHP 時(shí),我們也必須考慮內(nèi)存使用情況。
解析大量數(shù)據(jù)或可能隱藏?zé)o限次循環(huán)的腳本都趨于消耗大量內(nèi)存。很明顯,一旦內(nèi)存被耗盡,服務(wù)器的性能就降低,因此在執(zhí)行腳本時(shí)我們還必須注意內(nèi)存的使用情況。雖然我們可以通過啟用系統(tǒng)監(jiān)視器來簡單觀察內(nèi)存的使用量,但是它不會(huì)告訴我們比整個(gè)系統(tǒng)內(nèi)存狀態(tài)更有用的任何內(nèi)容。有時(shí)我們不止需要幫助進(jìn)行故障檢修或優(yōu)化的內(nèi)容,而有時(shí)我們只是需要更多詳細(xì)信息。
獲得腳本執(zhí)行內(nèi)容的透明性的一種方法是使用內(nèi)部或外部調(diào)試器。內(nèi)部調(diào)試器 是呈現(xiàn)為執(zhí)行腳本的相同的進(jìn)程。從操作系統(tǒng)的角度考慮的獨(dú)立進(jìn)程是外部調(diào)試器。使用調(diào)試器進(jìn)行內(nèi)存分析類似于任何一種情況,但是使用了不同的方法訪問內(nèi)存。內(nèi)部調(diào)試器對(duì)運(yùn)行進(jìn)程所在的內(nèi)存空間具有直接訪問權(quán),而外部調(diào)試器將通過套接字訪問內(nèi)存。
有許多方法和可用的調(diào)試服務(wù)器(外部)和庫(內(nèi)部)可用于輔助開發(fā)。為了準(zhǔn)備好對(duì) PHP 安裝進(jìn)行調(diào)試,可以使用新提供的 --enable-malloc-mm
,它在 DEBUG
構(gòu)建中默認(rèn)被啟用。這使環(huán)境變量 USE_ZEND_ALLOC
可用于允許在運(yùn)行時(shí)選擇 malloc 或 emalloc 內(nèi)存分配。使用 malloc-type 內(nèi)存分配將允許外部調(diào)試器觀察內(nèi)存使用情況,而 emalloc 分配將使用 Zend 內(nèi)存管理器抽象,要求進(jìn)行內(nèi)部調(diào)試。
![]() ![]() |
![]()
|
除了使內(nèi)存管理器更靈活更透明之外,PHP V5.2 還為 memory_get_usage()
和 memory_get_peak_usage()
提供了一個(gè)新參數(shù),這兩個(gè)函數(shù)允許查看內(nèi)存使用量。說明中提及的新布爾值是 real_size
。通過調(diào)用函數(shù) memory_get_usage($real);
(其中 $real = true
),結(jié)果將為調(diào)用時(shí)系統(tǒng)中實(shí)際分配的內(nèi)存大小,包括內(nèi)存管理器開銷。如果不使用標(biāo)記組,則返回的數(shù)據(jù)將只包括在運(yùn)行腳本內(nèi)使用的內(nèi)存,減去內(nèi)存管理器開銷。
memory_get_usage()
和 memory_get_peak_usage()
的不同之處在于后者將返回到目前為止調(diào)用它的運(yùn)行進(jìn)程的最高內(nèi)存量,而前者只返回執(zhí)行時(shí)的使用量。
對(duì)于 memory_get_usage()
,php.net 提供了清單 1 中的代碼片段。
|
在這個(gè)簡單示例中,我們首先回轉(zhuǎn)了直接調(diào)用 memory_get_usage()
的結(jié)果,代碼注釋中顯示可能在作者的系統(tǒng)中有 36640 字節(jié)的常見結(jié)果。然后我們使用 4,242 個(gè) “Hello” 副本來裝載 $a
并再次運(yùn)行函數(shù)。圖 1 中可以看到此簡單應(yīng)用的輸出。
沒有 memory_get_peak_usage()
的示例,因?yàn)閮烧呤窒嗨?,語法是相同的。但是,對(duì)于清單 1 中的示例代碼,將只有一個(gè)結(jié)果,即當(dāng)時(shí)的最高內(nèi)存使用量。讓我們看一看清單 2。
|
清單 2 中的代碼跟圖 1 一樣,但是 memory_get_usage()
已經(jīng)替換為 memory_get_peak_usage()
。在我們用 4242 個(gè) “Hello” 副本填充 $a
之前,輸出都不會(huì)有多大更改。內(nèi)存跳升至 57960,表示到目前為止的峰值。當(dāng)檢查內(nèi)存使用量峰值時(shí),得到了目前為止的最高值,因此所有進(jìn)一步調(diào)用都將得到 57960,直至我們處理的操作比處理 $a
使用的內(nèi)存更多(參見圖 2)。
![]() ![]() |
![]()
|
確保托管應(yīng)用程序的服務(wù)器不過載的一種方法是限制 PHP 執(zhí)行的任何腳本使用的內(nèi)存量。這根本不是我們應(yīng)當(dāng)執(zhí)行的操作,但由于 PHP是一種松散類型的語言,并且是在運(yùn)行時(shí)解析的,因此我們有時(shí)會(huì)獲得在釋放到生產(chǎn)應(yīng)用程序中后編寫得很差的腳本。這些腳本可能執(zhí)行循環(huán),也可能打開一張長的文件列表,忘記在打開新文件之前先關(guān)閉當(dāng)前文件。無論在哪一種情況下,編寫很差的腳本可能在您知道之前以消耗大量內(nèi)存告終。
在 PHP.INI 中,您可以使用配制參數(shù) memory_limit
來指定任何腳本能夠在系統(tǒng)中運(yùn)行的最大內(nèi)存使用量。這不是對(duì)于 V5.2 的特定更改,但是內(nèi)存管理器及其使用的任何討論都值得至少快速查看一次這個(gè)特性。它還精心地引導(dǎo)我使用內(nèi)存管理器的最后幾個(gè)新功能:環(huán)境變量。
![]() ![]() |
![]()
|
最后,在不能做完美主義者但是又完全符合自己目的的情況下怎樣編程?新環(huán)境變量 ZEND_MM_MEM_TYPE
和 ZEND_MM_SEG_SIZE
正好可以滿足您的需求。
當(dāng)內(nèi)存管理器分配大型內(nèi)存塊時(shí),它是安裝 ZEND_MM_SEG_SIZE
變量中列出的預(yù)定大小執(zhí)行操作的。這些內(nèi)存塊的默認(rèn)分區(qū)大小為每塊 256KB,但是您可以調(diào)整這些分區(qū)大小以滿足特殊需求。例如,如果您注意到最常用的一個(gè)腳本中的操作導(dǎo)致大量的內(nèi)存浪費(fèi),則可以將此大小調(diào)整為更接近匹配腳本需求的值,減少分配的內(nèi)存量但剩下的內(nèi)存量仍然為零。在正確的條件下,此類謹(jǐn)慎的配制調(diào)整可能造成巨大差別。
![]() ![]() |
![]()
|
如果具有預(yù)構(gòu)建的 PHP Windows 二進(jìn)制代碼,而沒有在構(gòu)建時(shí)使用 --enable-memory-limit
選項(xiàng),則需要先瀏覽此部分然后再繼續(xù)。對(duì)于 Linux®,配置 PHP 構(gòu)建時(shí)用 --enable-memory-limit
選項(xiàng)構(gòu)建 PHP。
要使用 Windows 二進(jìn)制代碼檢索內(nèi)存使用情況,請(qǐng)創(chuàng)建以下函數(shù)。
|
將結(jié)果保存到名為 function.php 的文件?,F(xiàn)在您只能將此文件包含在需要使用它的腳本中。
![]() ![]() |
![]()
|
讓我們來看一看使用這些設(shè)置的實(shí)際示例給我們帶來的好處??赡苡泻芏啻文枷胫罏槭裁丛谀_本的末尾沒有正確分配內(nèi)存。原因是因?yàn)橐恍┖瘮?shù)本身導(dǎo)致了內(nèi)存泄露,尤其是在僅使用內(nèi)置 PHP 函數(shù)的情況下。在這里,您將了解如何發(fā)現(xiàn)此類問題。并且為了開始進(jìn)行內(nèi)存泄露查找的征戰(zhàn),您將創(chuàng)建一個(gè)測(cè)試MySQL 數(shù)據(jù)庫,如清單 4 所示。
|
這將創(chuàng)建一個(gè)帶有 ID 字段和數(shù)據(jù)字段的簡單表。
在下一張清單中,想象我們堅(jiān)韌不拔的程序員正在執(zhí)行一些 MySQL 函數(shù),特別是使用 mysql_query()
將結(jié)果應(yīng)用到變量。當(dāng)他這樣做時(shí),他將注意到即使調(diào)用 mysql_free_result()
,一些內(nèi)存也不會(huì)被釋放,導(dǎo)致內(nèi)存使用量隨著 Apache 進(jìn)程不斷增長(參見清單 5)。
|
清單 5 是在任何位置都可能使用的簡單 MySQL 數(shù)據(jù)庫操作。在運(yùn)行腳本時(shí),我們注意到一些與內(nèi)存使用量相關(guān)的奇怪行為并需要將其檢查出來。為了使用內(nèi)存管理函數(shù)以使我們可以檢驗(yàn)發(fā)生錯(cuò)誤的位置,我們將使用以下代碼。
|
注:按照定義的時(shí)間間隔檢查當(dāng)前內(nèi)存使用量。在下面的輸出中,通過顯示我們的腳本一直在為函數(shù)分配內(nèi)存,并且在應(yīng)當(dāng)釋放的時(shí)候沒有釋放內(nèi)存,從而提供對(duì)內(nèi)存泄露的實(shí)際測(cè)試,您可以看到每次調(diào)用時(shí)內(nèi)存使用量如何增長。
|
我們所做的操作是發(fā)現(xiàn)了執(zhí)行腳本時(shí)出現(xiàn)的一些可疑操作,然后調(diào)整腳本使其給我們提供一些可理解的反饋。我們?cè)俅芜\(yùn)行了腳本,在每次迭代期間使用 memory_get_usage()
查看內(nèi)存使用量的變化。根據(jù)分配的內(nèi)存值的增長情況,暗示了我們用腳本在某個(gè)位置建立了一個(gè)漏洞。由于 mysql_free_result()
函數(shù)不釋放內(nèi)存,因此我們可以認(rèn)為 mysql_query()
并未正確分配內(nèi)存。
![]() ![]() |
![]()
|
PHP V5.2 版包括一些優(yōu)秀的新工具,可以幫助您更好地洞察腳本的系統(tǒng)內(nèi)存分配情況,以及重新全面控制內(nèi)存管理的精確調(diào)整。當(dāng)?shù)玫接行褂脮r(shí),新內(nèi)存管理工具將支持您的調(diào)試工作,從而重新獲得一些系統(tǒng)資源。
聯(lián)系客服