项目优化经验——垃圾回收导致的性能问题
談?wù)勛罱鼉?yōu)化一個(gè)網(wǎng)站項(xiàng)目的經(jīng)驗(yàn),首先說一下背景情況:
1) 在頁(yè)面后臺(tái)代碼中我們把頁(yè)面上大部分的HTML都使用字符串來拼接生成然后直接賦值給LiteralControl。
2) 網(wǎng)站CPU很高,基本都在80%左右,即使使用了StringBuilder來拼接字符串性能也不理想。
3) 為了改善性能,把整個(gè)字符串保存在memcached中,性能還是不理想。
在比較了這個(gè)網(wǎng)站和其它網(wǎng)站服務(wù)器上相關(guān)性能監(jiān)視器指標(biāo)后發(fā)現(xiàn)有一個(gè)參數(shù)特別顯眼:
就是其中的每秒分配字節(jié)數(shù),這個(gè)性能比較差的網(wǎng)站每秒分配2GB的內(nèi)存(而且需要注意由于性能監(jiān)視器是每秒更新一下,對(duì)于一個(gè)非常健康的網(wǎng)站這個(gè)值應(yīng)該經(jīng)常看到是0才對(duì))!而其它一些網(wǎng)站只分配200M左右的內(nèi)存。服務(wù)器配備4G內(nèi)存,而每秒分配2G內(nèi)存,我想垃圾回收器一定需要不斷運(yùn)行來回收這些內(nèi)存。觀察%Time in GC可以發(fā)現(xiàn),這個(gè)值一直在10%左右,也就是說上次回收到這次回收間隔10秒的話,這次垃圾回收1秒,由于回收的時(shí)間相對(duì)固定,那么這個(gè)值可以反映回收的頻繁度。
知道了這個(gè)要點(diǎn)就知道了方向,在項(xiàng)目中找可能的問題點(diǎn):
1) 是否分配了大量臨時(shí)的小對(duì)象
2) 是否分配了數(shù)量不多但比較大的大對(duì)象
在經(jīng)歷了一番查找之后,發(fā)現(xiàn)一個(gè)比較大的問題,雖然使用了memcached來緩存整個(gè)頁(yè)面的HTML,但是在輸出之前居然進(jìn)行了幾次string的Replace操作,這樣就產(chǎn)生了幾個(gè)大的字符串,我們來做一個(gè)實(shí)驗(yàn)?zāi)M這種場(chǎng)景:
public partial class _Default : System.Web.UI.Page {static string template;protected void Page_Load(object sender, EventArgs e){if (template == null){StringBuilder sb = new StringBuilder();for (int i = 0; i < 10000; i++)sb.Append("1234567890");template = sb.ToString(); }Stopwatch sw = Stopwatch.StartNew();for (int i = 0; i < 1; i++){long mem1 = GC.GetTotalMemory(false);string s = template + i;long mem2 = GC.GetTotalMemory(false);Response.Write((mem2 - mem1).ToString("N0"));Response.Write("<br/>");GC.KeepAlive(s);}for (int i = 0; i < 100000; i++){double d = Math.Sqrt(i);}Thread.Sleep(30);Response.Write(sw.ElapsedMilliseconds);} }在這段代碼中:
1) 我們首先使用一個(gè)靜態(tài)變量模擬緩存中的待輸出的HTML
2) 我們中間的一段代碼測(cè)算一下這個(gè)字符串占用的內(nèi)存空間
3) 隨后我們做了一些消耗CPU的運(yùn)算操作來模擬頁(yè)面的一些計(jì)算
4) 然后休眠一段時(shí)間
4) 最后我們輸出了頁(yè)面執(zhí)行時(shí)間
我們這么做的目的是模擬一個(gè)比較“正常的”ASP.NET頁(yè)面需要做的一些工作:
1) 內(nèi)存上的分配
2) 一些計(jì)算
3) 涉及到IO訪問的一些等待
來看看輸出結(jié)果:
這里可以看到,我們這個(gè)字符串占用差不多200K的字節(jié),字符串是字符數(shù)組,CLR中字符采用Unicode雙字節(jié)存儲(chǔ),因此10萬長(zhǎng)度的字符串占用200千字節(jié),并且也可以看到這個(gè)頁(yè)面執(zhí)行時(shí)間30毫秒,差不多是一個(gè)正常aspx頁(yè)面的時(shí)間,而200K不到的字符串也差不多相當(dāng)于這個(gè)頁(yè)面的HTML片段,現(xiàn)在我們來改一下其中的一段代碼模擬優(yōu)化前進(jìn)行的Replace操作帶來的幾個(gè)大字符串:
for (int i = 0; i < 10; i++) {//long mem1 = GC.GetTotalMemory(false);string s = template + i;//long mem2 = GC.GetTotalMemory(false);//Response.Write((mem2 - mem1).ToString("N0"));//Response.Write("<br/>");//GC.KeepAlive(s); }然后使用IDE自帶壓力測(cè)試1000常量用戶來測(cè)試這個(gè)頁(yè)面:
可以看到每秒分配了超過400M字節(jié)(這和我們線上環(huán)境比還差點(diǎn)畢竟請(qǐng)求少),CPU占用基本在120-160左右(雙核),我們?nèi)サ裘棵敕峙鋬?nèi)存這個(gè)數(shù)值,來看看垃圾回收頻率和CPU占用兩個(gè)值的圖表:
可以看到紅色的CPU波動(dòng)基本和藍(lán)色的垃圾回收波動(dòng)保持一致(這里不太準(zhǔn)確的另外一個(gè)原因是壓力測(cè)試客戶端運(yùn)行于本機(jī),而為w3wp關(guān)聯(lián)2個(gè)處理器)!為什么說垃圾回收會(huì)帶來CPU的波動(dòng),從理論上來說有以下原因:
1) 垃圾回收的時(shí)候會(huì)暫時(shí)掛起所有線程,然后GC會(huì)檢測(cè)掃描每一個(gè)線程棧上可回收對(duì)象,然后會(huì)移動(dòng)對(duì)象,并且重新設(shè)置對(duì)象指針,這整個(gè)過程首先是消耗CPU的
2) 而且在這個(gè)過程之后恢復(fù)線程執(zhí)行,這個(gè)時(shí)候CPU往往會(huì)引起一個(gè)高峰因?yàn)橐呀?jīng)有更多的請(qǐng)求等待了
我們把Math.Sqrt這段代碼注釋掉并且把w3wp和VSTestHost關(guān)聯(lián)到不同的處理器來看看對(duì)于CPU計(jì)算很少的頁(yè)面,上圖更明顯的對(duì)比:
?
這說明垃圾回收的確會(huì)占用很多CPU資源,但這只是一部分,其實(shí)我覺得網(wǎng)站的CPU壓力來自于幾個(gè)地方:
1) 就是大量的內(nèi)存分配帶來的垃圾回收所占用的CPU,對(duì)于ASP.NET框架內(nèi)部的很多行為無法控制,但是可以在代碼中盡量避免在堆上產(chǎn)生很多不必要的對(duì)象
2) 是實(shí)際的CPU運(yùn)算,不涉及IO的運(yùn)算,這些可以通過改良算法來優(yōu)化,但是優(yōu)化比較有限
3) 是IO操作這塊,數(shù)據(jù)量的多少很關(guān)鍵,還有要考慮memcached等外部緩存對(duì)象序列化反序列化的消耗
4) 雖然很多IO操作不占用CPU資源,線程處于休眠狀態(tài),但是很多時(shí)候其實(shí)是依托新線程進(jìn)行的,帶來的就是線程切換和線程創(chuàng)建消耗的消耗,這一塊可以通過合理使用多線程來優(yōu)化
發(fā)現(xiàn)了這個(gè)問題之后優(yōu)化就很簡(jiǎn)單了,把Replace操作放到memcached的Set操作之前,取出之后不產(chǎn)生過多大字符串,把for循環(huán)改為一次,再來看一下:
?
這次內(nèi)存分配明顯少了很多,CPU降下來了,降的不多,但從壓力測(cè)試監(jiān)視器中看到頁(yè)面執(zhí)行平均時(shí)間從5秒變?yōu)?秒了,每秒平均請(qǐng)求數(shù)從170到了200(最高從200到了300)。在這里要說明一點(diǎn)很多時(shí)候網(wǎng)站的性能優(yōu)化不能光看CPU還要對(duì)比優(yōu)化前后網(wǎng)站的負(fù)載,因?yàn)樵趦?yōu)化之后頁(yè)面執(zhí)行時(shí)間降低了,負(fù)載量就增大了CPU消耗也隨之增大。并且可以看到垃圾回收頻率的縮短很明顯,從長(zhǎng)期在30%到幾十秒一次30%。
最后想補(bǔ)充幾點(diǎn):
1) 有的時(shí)候我們會(huì)使用GC.GetTotalMemory(true); 來得到垃圾回收之后內(nèi)存分配數(shù),類似這樣涉及到垃圾回收的代碼在項(xiàng)目上線后千萬不能出現(xiàn),否則很可能會(huì)% Time in GC達(dá)到80%以上大量占用CPU。
2) 對(duì)于放在緩存中的對(duì)象我們往往會(huì)覺得性能得到保障大量去使用,其實(shí)緩存實(shí)現(xiàn)的只是把創(chuàng)造這個(gè)對(duì)象過程的時(shí)間轉(zhuǎn)化為空間,而在拿到這個(gè)對(duì)象之后再進(jìn)行很多運(yùn)算帶來的大量空間始終會(huì)進(jìn)行垃圾回收。做網(wǎng)站和做應(yīng)用程序不一樣,一個(gè)操作如果申請(qǐng)200K堆內(nèi)存,一個(gè)頁(yè)面執(zhí)行這個(gè)操作10次,一秒200多個(gè)請(qǐng)求,大家可以自己算一下平均每秒需要分配多少內(nèi)存,這個(gè)數(shù)值是相當(dāng)可怕的,網(wǎng)站是一個(gè)多線程的環(huán)境,我們對(duì)內(nèi)存的使用要考慮更多。
總結(jié)
以上是生活随笔為你收集整理的项目优化经验——垃圾回收导致的性能问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Google电子地图基础及应用
- 下一篇: squid代理服务器详解