2016-12-03 68 views
0

我的應用程序出現問題,希望有人能給我建議如何解決它。長時間運行後的高CPU使用率

我有多線程應用程序。它調整10-20個線程,並在每個線程中執行一些複雜的任務。

Thread thread = new Thread(ProcessThread); 
thread.Start(); 

private void ProcessThread() 
{ 
    while(IsRunning) 
    { 
     // do some very complex operations: grab HTTP pages. Save to files. Read from files. Run another threads etc. 
    } 
} 

在開始應用使用了約10%的CPU和140MB內存。但1000後執行CPU使用率爲25%-30%,內存爲1200Mb。我知道我的代碼中可能存在內存泄漏,我會嘗試修復它。但是,CPU發生了什麼?它爲什麼會增長?每次執行都會執行與開始和稍後相同的操作(例如,打開網頁,獲取一些信息並將其保存到文件中)。

我認爲這個問題可以與GC。更多的內存應用程序需要更多的CPU需要清理內存?

另一個問題,能否請你指教一個很好的工具如何衡量什麼佔用CPU在我的應用程序?

也許你可以推薦一個很好的工具來分析內存,並檢查它泄漏?我試過JetBrains dotMemory,但是不太瞭解。也許你可以幫助我。 以下是統計: http://prntscr.com/dev067 http://prntscr.com/dev7a2 正如我所看到的,我沒有太多非託管內存。但同時我看到字符串的問題,但不能理解GC應該清除它的問題?

欣賞任何意見和建議,有什麼我可以提高。

回答

0

除非你是包裝原生類型,我懷疑你有內存泄漏。內存使用情況很可能是由於GC的工作原理。

GC將不會收集在一個週期內都死了的項目。它將使用世代。這意味着只有在需要空間時纔會收集舊對象。所以內存使用量會增加,直到第一次收集。然後,只收集一小部分的內容,這會降低內存使用量,但並不完全。 GC也不一定會將釋放的內存返回給操作系統。

在Visual Studio中的更高版本,你會發現一個內存分析器。

0

這是非常不清楚什麼是「一些複雜的任務」。 這尤其讓我擔心:

// do some very complex operations: grab HTTP pages. Save to files. Read from files. Run another threads etc. 

好吧,如果你確實是從你的進程,線程開始新的線程,那麼它完全是有道理的,CPU使用率提高。從性能角度來看,創建新線程代價高昂,這也是我們使用線程池(回收線程)的原因。

內存使用情況也是如此。更多的線程=爲每個線程需要更多內存(每個線程都有它自己的堆棧需要額外的內存)...

另外,我不認爲GC是罪魁禍首這裏。

有很多問題需要TE回答之前,我可以幫你找到這樣的行爲的原因問題,我們可以責怪GC之前:):你啓動所有 1)做,比方說,20個線程在你的節目開始時? 2)你是否已經從正在運行的創建新線程? 3)這些線程的終止條件是什麼?他們真的終止了嗎?

我會建議使用dotTrace確定CPU使用率。 不幸的是,我沒有使用任何工具來分析內存使用情況,所以我不能推薦任何。

0

我看着你的截圖,我看到你有很多對象,這些對象正在倖存的第0代集合中,所以它們被升級到第1代,然後升級到第2代。這可能是內存泄漏的標誌,但不一定。沒有看到你的代碼很難說。我能夠告訴你的是,你正在保留你的物品很長一段時間。這可能是需要的,但再次看不到代碼我不知道。

約GC

有點當GC醒來清理,分析了託管堆,看看哪些對象沒有紮根,標誌着他們都可以進行收集。這被稱爲標記階段。然後它開始釋放內存。這叫做掃描階段。如果無法清除任何東西,這些對象可以代1.一段時間後的GC再次喚醒並重覆上面的,但是這一次,由於在第1代的項目,如果他們無法收集他們會去代2.本可能是一個不好的跡象。這些物體是否真的需要在那裏長時間存在?

那麼你能做什麼?

你說GC必須清理東西。那麼是的,GC會清理的東西,但只有當你不參考它們。如果一個對象被植入,GC將無法清除它。記住

與GC

編寫代碼開始調查,你需要調查你的代碼,並確保你的代碼是在考慮GC寫的。瀏覽你的代碼並列出你正在使用的所有對象。如果任何對象是實現IDisposable一類的,然後將它們包裝在using語句:

using (Font font1 = new Font("Arial", 10.0f)) 
{ 
    // work here depends on font1 
} 

如果你在你的類的類級變量的一個需要font1,那麼你就必須做出descision時調用Dispose就可以了:至少,這個類必須實現IDisposable並調用Dispose font1。這個類的用戶(任何在這個類上調用new的代碼)應該使用這個類,並使用using語句或者調用它的Dispose。

不要讓物體的時間比你需要他們。

還需要進一步調查嗎?

一旦您調查了您的代碼並確保您的代碼對GC更友善,並且仍然存在問題,請使用工具進行調查。 Here是一篇很好的文章,可以幫助您理解這部分內容。

有些人有誤解認爲調用GC.Collect()將解決內存泄漏問題。這根本不是真的。強制垃圾回收仍然會遵循相同的規則,如果對象是根深蒂固,你可以無限撥打GC.Collect(),對象仍然不會被清理。

這裏是一個示例應用程序,它會顯示這樣的:

public class Writer : IDisposable 
{ 
    public void Dispose() 
    { 

    } 

    public void Write(string s) 
    { 
     Console.WriteLine(s); 
    } 
} 

class Program 
{ 

    static void Main(string[] args) 
    { 
     Writer writer = new Writer(); 
     writer.Write("1"); 
     writer.Dispose(); 

     // writer will still be around because I am referencing it (rooted) 
     writer.Write("2"); 

     GC.Collect(); 

     // calling GC.Collect() has no impact since writer is still rooted 
     writer.Write("3"); 
     Console.ReadKey(); 

    } 
} 
0

我只能對你爲什麼有一個CPU問題,而不是GC評論。

  1. 我建議您驗證您是否只創建您期望的數字線程。如果你碰巧有線程啓動線程,很容易滑落。
  2. 正如另一條評論所述,線程創建和銷燬代價高昂。如果您一直在創建和銷燬線程,這將對您的性能產​​生顯着影響。
  3. 我懷疑你的放緩的原因是內存抖動,這意味着每個線程使用的內存量足夠大,和/或所有線程使用的總量足夠大,導致內存頁被換入和換出每時每刻。在某些情況下,處理器花費更多的時間交換內存到磁盤上,而不是花在執行線程上。

這裏是我的建議:

  1. 使用一個線程池。這可以最大限度地減少創建和銷燬線程所需的時間。這也會限制你正在使用的線程數量。
  2. 確保每個線程在被換出之前執行一段合理的時間 - 即你沒有線程上下文抖動。
  3. 確保每個線程正在使用的內存量並不比預期的大得多,以防止內存抖動。
  4. 如果您必須爲每個線程使用大量的內存,請確保您的使用模式是這樣的,以至於沒有太多的內存分頁。