php 5.2 記憶體管理器
?
PHP V5.2 中的新增功能,第 1 部分: 使用新的記憶體管理器
像跟蹤和監視 uber-nerd 一樣跟蹤和監視 PHP 記憶體
Tracy Peterson (tracy@tracypeterson.com), 自由撰稿人, Consultant
自 1997 年以來,Tracy Peterson 擔任過 IT 專案經理和 Web 開發人員,並且目前擔任 Microsoft 的 MSN Search 的運算程式主管。他目前的工作地點位於舊金山。
簡介:?瞭解如何使用 PHP V5.2 中引入的新記憶體管理器並開始精通於跟蹤和監視記憶體使用量情況。這將使您可以在 PHP V5.2 中更加有效地使用更多的記憶體。
查看本系列更多內容
標記本文!
發布日期:?2007 年 4 月 10 日
層級:?中級
訪問情況?1145 次瀏覽
建議:?0?(添加評論)
平均分 (共 2 個評分 )
PHP V5.2:開始
2006 年 11 月發布了 PHP V5.2,它包括許多新增功能和錯誤修正。它廢止了 5.1 版並被推薦給所有 PHP V5 使用者進行升級。我最喜歡的實驗室環境 ―― Windows?、Apache、MySQL、PHP (WAMP) ―― 已經被引入了 V5.2 的新軟體包中(請參閱 參考資料)。您將在那裡找到在 Windows? XP 或 2003 電腦上安裝 PHP V5.2、MySQL 和 Apache 的應用程式。您可以十分輕鬆地進行安裝,它有很多不錯的小的管理優點,並且我十分誠懇地推薦使用它。
雖然對於 Windows 使用者來說,這是最簡單的軟體包,但是在 Linux 上配置 PHP 時您需要添加以下代碼:--memory-limit-enabled
(適用於您伺服器的任何其他選項除外)。不過,在 Windows 下,提供了一個解決此問題的函數。
PHP V5.2 中有很多改進之處,並且一個至關重要的領域是記憶體管理。從 README.ZEND_MM 中準確地引述就是:“新記憶體管理器(PHP5.2 以及更高版本)的目標是減少記憶體配置開銷並加速記憶體管理。”
下面是 V5.2 版本資訊中的一些關鍵內容:
- 刪除了不必要的
--disable-zend-memory-manager
配置選項
- 添加了
--enable-malloc-mm
配置選項,調試構建時此配置選項將被預設啟用以允許使用內部和外部記憶體偵錯工具
- 允許使用
ZEND_MM_MEM_TYPE
和 ZEND_MM_SEG_SIZE
環境變數調整記憶體管理器
為了理解這些新增功能的含義,我們需要深入研究記憶體管理中的藝術,並考慮為什麼分配開銷和運行速度是大問題。
回頁首
為什麼進行記憶體管理?
計算中開發最快速的一項技術是記憶體和資料存放區,它們是受不斷增加速度和儲存大小這樣持續的需求而驅動的。早期的電腦使用卡作為記憶體,然後轉向了晶片技術。您能想象在只有 1 KB RAM 記憶體的電腦工作的情景嗎?很多早期的電腦程式員就曾使用過。這些先驅者很快就意識到,要在技術限制下工作,他們將必須細心地用瑣碎的命令避免系統過載。
身為 PHP 開發人員,與使用 C++ 或其他更嚴格的語言編碼的同事相比,我們所在的環境更方便進行編碼。在我們的世界裡,我們自己不必擔心如何處理系統記憶體,因為 PHP 將為我們處理這個問題。但是,在其他編程領域裡,負責任的編碼人員將使用各種函數確保執行的命令不會覆蓋其他一些程式資料 ―― 因而,破壞了程式的運行。
記憶體管理通常是由來自編碼人員的請求處理的,以分配和釋放記憶體塊。分配塊 可以儲存任何類型的資料,並且此過程將為該資料隔開一定量的記憶體,併當操作需要訪問資料時為應用程式提供存取方法。人們期望程式在完成任何操作後釋放分配的記憶體,並允許系統和其他程式員使用該記憶體。如果程式沒有把記憶體釋放回系統,則稱為記憶體泄露。
泄露是任何運行程式都存在的普遍問題,並且某種程度內通常是可以接受的,尤其是當我們知道運行程式將立即終止並釋放預設分配給程式的所有記憶體。
由於隨機運行和終止程式,像幾乎所有客戶機應用程式一樣,這是個問題。期望伺服器應用程式不確定地運行而不終止或重新啟動,這使得記憶體管理對於伺服器精靈編程絕對的至關重要。在長時間啟動並執行程式中,即使一個小的泄露最後都將發展為系統衰弱問題,因為記憶體塊已被使用並且永遠不被釋放。
回頁首
長期考慮
正如使用任何語言編寫一樣,用 PHP 編寫的永久性伺服器精靈有很多可能的用途。但是當我們出於這些目的開始使用 PHP 時,我們也必須考慮記憶體使用量情況。
解析大量資料或可能隱藏無限次迴圈的指令碼都趨於消耗大量記憶體。很明顯,一旦記憶體被耗盡,伺服器的效能就降低,因此在執行指令碼時我們還必須注意記憶體的使用方式。雖然我們可以通過啟用系統監視器來簡單觀察記憶體的使用量,但是它不會告訴我們比整個系統記憶體狀態更有用的任何內容。有時我們不止需要協助進行故障檢修或最佳化的內容,而有時我們只是需要更多詳細資料。
獲得指令碼執行內容的透明性的一種方法是使用內部或外部調試器。內部調試器 是呈現為執行指令碼的相同的進程。從作業系統的角度考慮的獨立進程是外部調試器。使用調試器進行記憶體分析類似於任何一種情況,但是使用了不同的方法訪問記憶體。內部調試器對運行進程所在的記憶體空間具有直接存取權,而外部調試器將通過通訊端訪問記憶體。
有許多方法和可用的調試伺服器(外部)和庫(內部)可用於輔助開發。為了準備好對 PHP 安裝進行調試,可以使用新提供的 --enable-malloc-mm
,它在 DEBUG
構建中預設被啟用。這使環境變數 USE_ZEND_ALLOC
可用於允許在運行時選擇 malloc 或 emalloc 記憶體配置。使用 malloc-type 記憶體配置將允許外部調試器觀察記憶體使用量情況,而 emalloc 分配將使用 Zend 記憶體管理器抽象,要求進行內部調試。
回頁首
PHP 中的記憶體管理函數
除了使記憶體管理器更靈活更透明之外,PHP V5.2 還為 memory_get_usage()
和 memory_get_peak_usage()
提供了一個新參數,這兩個函數允許查看記憶體使用量量。說明中提及的新布爾值是 real_size
。通過調用函數 memory_get_usage($real);
(其中 $real = true
),結果將為調用時系統中實際分配的記憶體大小,包括記憶體管理器開銷。如果不使用標記組,則返回的資料將只包括在運行指令碼內使用的記憶體,減去記憶體管理器開銷。
memory_get_usage()
和 memory_get_peak_usage()
的不同之處在於後者將返回到目前為止調用它的運行進程的最高記憶體量,而前者只返回執行時的使用量。
對於 memory_get_usage()
,php.net 提供了清單 1 中的程式碼片段。
清單 1. memory_get_usage()
樣本
在這個簡單樣本中,我們首先迴轉了直接調用 memory_get_usage()
的結果,代碼注釋中顯示可能在作者的系統中有 36640 位元組的常見結果。然後我們使用 4,242 個 “Hello” 副本來裝載 $a
並再次運行函數。圖 1 中可以看到此簡單應用的輸出。
圖 1. memory_get_usage()
的樣本輸出
沒有 memory_get_peak_usage()
的樣本,因為兩者十分相似,文法是相同的。但是,對於清單 1 中的範例程式碼,將只有一個結果,即當時的最高記憶體使用量量。讓我們看一看清單 2。
清單 2. memory_get_peak_usage()
樣本
清單 2 中的代碼跟圖 1 一樣,但是 memory_get_usage()
已經替換為 memory_get_peak_usage()
。在我們用 4242 個 “Hello” 副本填充 $a
之前,輸出都不會有多大更改。記憶體跳升至 57960,表示到目前為止的峰值。當檢查記憶體使用量量峰值時,得到了目前為止的最高值,因此所有進一步調用都將得到 57960,直至我們處理的操作比處理 $a
使用的記憶體更多(參見圖 2)。
圖 2. memory_get_peak_usage()
的樣本輸出
回頁首
限制記憶體使用量
確保託管應用程式的伺服器不過載的一種方法是限制 PHP 執行的任何指令碼使用的記憶體量。這根本不是我們應當執行的操作,但由於 PHP 是一種鬆散類型的語言,並且是在運行時解析的,因此我們有時會獲得在釋放到生產應用程式中後編寫得很差的指令碼。這些指令碼可能執行迴圈,也可能開啟一張長的檔案清單,忘記在開啟新檔案之前先關閉當前檔案。無論在哪一種情況下,編寫很差的指令碼可能在您知道之前以消耗大量記憶體告終。
在 PHP.INI 中,您可以使用配製參數 memory_limit
來指定任何指令碼能夠在系統中啟動並執行最大記憶體使用量量。這不是對於 V5.2 的特定更改,但是記憶體管理器及其使用的任何討論都值得至少快速查看一次這個特性。它還精心地引導我使用記憶體管理器的最後幾個新功能:環境變數。
回頁首
調整記憶體管理器
最後,在不能做完美主義者但是又完全符合自己目的的情況下怎樣編程?新環境變數 ZEND_MM_MEM_TYPE
和 ZEND_MM_SEG_SIZE
正好可以滿足您的需求。
當記憶體管理器分配大型記憶體塊時,它是安裝 ZEND_MM_SEG_SIZE
變數中列出的預定大小執行操作的。這些記憶體塊的預設分區大小為每塊 256 KB,但是您可以調整這些分區大小以滿足特殊需求。例如,如果您注意到最常用的一個指令碼中的操作導致大量的記憶體浪費,則可以將此大小調整為更接近匹配指令碼需求的值,減少分配的記憶體量但剩下的記憶體量仍然為零。在正確的條件下,此類謹慎的配製調整可能造成巨大差別。
回頁首
在 Windows 中檢索記憶體使用量情況
如果具有預構建的 PHP Windows 二進位代碼,而沒有在構建時使用 --enable-memory-limit
選項,則需要先瀏覽此部分然後再繼續。對於 Linux?,配置 PHP 構建時用 --enable-memory-limit
選項構建 PHP。
要使用 Windows 二進位代碼檢索記憶體使用量情況,請建立以下函數。
清單 3. 在 Windows 下獲得記憶體使用量情況
將結果儲存到名為 function.php 的檔案。現在您只能將此檔案包含在需要使用它的指令碼中。
回頁首
動手實踐
讓我們來看一看使用這些設定的實際樣本給我們帶來的好處。可能有很多次您都想知道為什麼在指令碼的末尾沒有正確分配記憶體。原因是因為一些函數本身導致了記憶體泄露,尤其是在僅使用內建 PHP 函數的情況下。在這裡,您將瞭解如何發現此類問題。並且為了開始進行記憶體泄露尋找的征戰,您將建立一個測試 MySQL 資料庫,如清單 4 所示。
清單 4. 建立測試資料庫
mysql> create database memory_test;mysql> use memory_test;mysql> create table leak_test ( id int not null primary key auto_increment, data varchar(255) not null default '');mysql> insert into leak_test (data) values ("data1"),("data 2"), ("data 3"),("data 4"),("data 5"),("data6"),("data 7"), ("data 8"),("data 9"),("data 10"); |
這將建立一個帶有 ID 欄位和資料欄位的簡單表。
在下一張清單中,想象我們堅韌不拔的程式員正在執行一些 MySQL 函數,特別是使用 mysql_query()
將結果應用到變數。當他這樣做時,他將注意到即使調用 mysql_free_result()
,一些記憶體也不會被釋放,導致記憶體使用量量隨著 Apache 進程不斷增長(參見清單 5)。
清單 5. 記憶體泄露檢測樣本
for ( $x=0; $x<300; $x++ ) { $db = mysql_connect("localhost", "root", "test"); mysql_select_db("test"); $sql = "SELECT data FROM test"; $result = mysql_query($sql); // The operation suspected of leaking mysql_free_result($result); mysql_close($db); } |
清單 5 是在任何位置都可能使用的簡單 MySQL 資料庫操作。在運行指令碼時,我們注意到一些與記憶體使用量量相關的奇怪行為並需要將其檢查出來。為了使用記憶體管理函數以使我們可以檢驗發生錯誤的位置,我們將使用以下代碼。
清單 6. 定標尋找錯誤的樣本
"; $db = mysql_connect("localhost", "user", "password");mysql_select_db("memory_test");echo "After connecting, we're using (in bytes): ", memory_get_usage(),"\n "; for ( $x=0; $x<10; $x++ ) { $sql = "SELECT data FROM leak_test WHERE id='".$x."'"; $result = mysql_query($sql); // The operation // suspected of leaking. echo "After query #$x, we're using (in bytes): ", memory_get_usage(), "\n "; mysql_free_result($result); echo "After freeing result $x, we're using (in bytes): ", memory_get_usage(), "\n ";} mysql_close($db);echo "After closing the connection, we're using (in bytes): ", memory_get_usage(), "\n ";echo "Peak memory usage for the script (in bytes):". memory_get_peak_usage();?> |
註:按照定義的時間間隔檢查當前記憶體使用量量。在下面的輸出中,通過顯示我們的指令碼一直在為函數分配記憶體,並且在應當釋放的時候沒有釋放記憶體,從而提供對記憶體泄露的實際測試,您可以看到每次調用時記憶體使用量量如何增長。
清單 7. 測試指令碼輸出
At the start we're using (in bytes): 63216After connecting, we're using (in bytes): 64436After query #0, we're using (in bytes): 64760After freeing result 0, we're using (in bytes): 64828After query #1, we're using (in bytes): 65004After freeing result 1, we're using (in bytes): 65080After query #2, we're using (in bytes): 65160After freeing result 2, we're using (in bytes): 65204After query #3, we're using (in bytes): 65284After freeing result 3, we're using (in bytes): 65328After query #4, we're using (in bytes): 65408After freeing result 4, we're using (in bytes): 65452After query #5, we're using (in bytes): 65532After freeing result 5, we're using (in bytes): 65576After query #6, we're using (in bytes): 65656After freeing result 6, we're using (in bytes): 65700After query #7, we're using (in bytes): 65780After freeing result 7, we're using (in bytes): 65824After query #8, we're using (in bytes): 65904After freeing result 8, we're using (in bytes): 65948After query #9, we're using (in bytes): 66028After freeing result 9, we're using (in bytes): 66072After closing the connection, we're using (in bytes): 65108Peak memory usage for the script (in bytes): 88748 |
我們所做的操作是發現了執行指令碼時出現的一些可疑操作,然後調整指令碼使其給我們提供一些可理解的反饋。我們再次運行了指令碼,在每次迭代期間使用 memory_get_usage()
查看記憶體使用量量的變化。根據分配的記憶體值的增長情況,暗示了我們用指令碼在某個位置建立了一個漏洞。由於 mysql_free_result()
函數不釋放記憶體,因此我們可以認為 mysql_query()
並未正確分配記憶體。
回頁首
結束語
PHP V5.2 版包括一些優秀的新工具,可以協助您更好地洞察指令碼的系統記憶體配置情況,以及重新全面控制記憶體管理的精確調整。當得到有效使用時,新記憶體管理工具將支援您的調試工作,從而重新獲得一些系統資源。
參考資料
學習
- 您可以參閱本文在 developerWorks 全球網站上的 英文原文 。
- 閱讀 PHP V5.2 版本資訊。
- “How to Manage Memory in PHP” 是一篇關於 PHP 記憶體管理編程實踐的優秀文章。
- Zend Developer Zone 中有很多針對記憶體管理器函數的文檔。
- 訪問 PHP.net 獲得 PHP 文檔。
- 文章 “A step-by-step how-to guide to install, configure, and test a Linux, Apache, Informix, and PHP server” 包含一個關於為 Linux 編譯 PHP 解析程式的部分。
- 查閱實際樣本產生的 錯誤報表。
- 在 “PHP V5 遷移指南” 中瞭解如何將在 PHP V4 中開發的代碼遷移到 V5。
- 要獲得學慣用 PHP 進行編程的教程,請查閱 developerWorks 的 “學習 PHP” 系列。
- Planet PHP 是 PHP 開發人員社區新聞資源。
- PHP.net 是 PHP 開發人員的資源。
- 查閱 “PHP 推薦讀物列表”。
- 瀏覽 developerWorks 上的所有 PHP 文章 和 PHP 教程。
- 查閱 IBM developerWorks 的 PHP 項目資源中心 擴充 PHP 技巧。
- 收聽針對軟體開發人員的有趣訪談和討論,一定要訪問 developerWorks podcast。
- 隨時關注 developerWorks 的 技術事件和網路廣播。
- 查閱最近將在全球舉辦的面向 IBM 開放源碼開發人員的研討會、交易展覽、網路廣播和其他 活動。
- 訪問 developerWorks 開源軟體技術專區,獲得豐富的 how-to 資訊、工具和項目更新,協助您用開放源碼技術進行開發,並與 IBM 產品結合使用。
- 訪問 Safari 線上書店 瀏覽開放源碼技術的各種參考資料。
獲得產品和技術
- 使用 IBM 試用軟體 構建您的下一個開發項目,這些軟體可以通過下載或從 DVD 中獲得。