2017-08-29 71 views
1

的我遇到這個奇怪的性能問題:C#應用程序CPU性能顯着減慢時,程序會創建大量的對象

  1. 我有創造數以百萬計的C#對象的C#應用​​程序。

  2. 在代碼的部分無關,應用程序確實不依賴於在步驟分配的數據1.

的CPU時間似乎被關聯到的對象的數目的特定的工作在步驟1創建。

我寫了一個簡單的C#案例,它再現了我的問題。 slowdown使用在調用DoMyWork()方法之前創建的數百萬字符串對象調用該命令。 正如你所看到的,如果200M的字符串被實例化,相同的DoMyWork()方法最多可能需要3秒。

  • 我錯過了語言的東西嗎?
  • 假設沒有達到物理內存限制,是否有最大數量的對象不應達到,否則CLR會減慢?

我跑我的測試的Windows 10下的英特爾酷睿i7-6700和我的計劃是建立在32位模式下的控制檯版本(VS 2017年 - FW 4.6.1):

slowdown 0 
Allocating 40000 hashtables: 2 ms 
Allocating 40000 hashtables: 4 ms 
Allocating 40000 hashtables: 15 ms 
Allocating 40000 hashtables: 2 ms 
Allocating 40000 hashtables: 5 ms 
Allocating 40000 hashtables: 5 ms 
Allocating 40000 hashtables: 2 ms 
Allocating 40000 hashtables: 18 ms 
Allocating 40000 hashtables: 10 ms 
Allocating 40000 hashtables: 19 ms

放緩0使用〜30M

slowdown 200 
Allocating 40000 hashtables: 392 ms 
Allocating 40000 hashtables: 1120 ms 
Allocating 40000 hashtables: 3067 ms 
Allocating 40000 hashtables: 2 ms 
Allocating 40000 hashtables: 31 ms 
Allocating 40000 hashtables: 418 ms 
Allocating 40000 hashtables: 15 ms 
Allocating 40000 hashtables: 2 ms 
Allocating 40000 hashtables: 18 ms 
Allocating 40000 hashtables: 416 ms

放緩200個使用800M〜


using System; 
using System.Diagnostics; 
using System.Collections; 

namespace SlowDown 
{ 
    class Program 
    { 
    static string[] arr; 

    static void CreateHugeStringArray(long size) 
    { 
     arr = new string[size * 1000000]; 
     for (int i = 0; i < arr.Length; i++) arr[i] = ""; 
    } 


    static void DoMyWork() 
    { 
     int n = 40000; 
     Console.Write("Allocating " + n + " hashtables: "); 
     Hashtable[] aht = new Hashtable[n]; 

     for (int i = 0; i < n; i++) 
     { 
     aht[i] = new Hashtable(); 
     } 
    } 


    static void Main(string[] args) 
    { 
     if (0 == args.Length) return; 
     CreateHugeStringArray(Convert.ToInt64(args[0])); 

     for (int i = 0; i < 10 ; i++) 
     { 
     Stopwatch sw = Stopwatch.StartNew(); 
     DoMyWork(); 
     sw.Stop(); 
     Console.Write(sw.ElapsedMilliseconds + " ms\n"); 
     } 
    } 
    } 
} 
+2

在您的示例中,您不會創建「數百萬個C#對象」。您創建了一個巨大的字符串數組,每個數組元素將實際指向相同的字符串對象(請參閱string interning)。 – dymanoid

+0

如果您嘗試在'DoMyWork'內添加一個整數爲整數,它會如何執行,因爲現有邏輯中涉及到一個裝箱操作。你也可以考慮在重複操作相同的字符串時使用'StringBuilder'。 –

+1

在'CreateHugeStringArray'之後加上'GC.Collect()',看看你是否仍然得到相同的時間。 –

回答

1

可能的垃圾收集討厭的東西,它可以凍結你的主線程,即使它的工作原理主要是在後臺線程,如mentionned這裏:Garbage Collector Thread

如果您收集它,時間遺體(對我來說)左右無論「不相關」陣列的大小如何,均爲90毫秒。

+0

事實上,在真實情況下,我的應用程序正在加載大量以後使用的數據,因此不能收集數據。我通過添加一個案例2來更新我的帖子,其中數據引用保存在靜態引用中。這樣做,數組永遠不會被收集。然而,執行性能差兩倍,而GC不應該對數據做任何事情。這使我認爲在整個數據保持分配時發生緩慢的問題。 GC線程可能會觀察到所有降低全局應用程序性能的活動對象。 – guista

1

該問題是由與您的DoMyWork同時運行的垃圾收集器引起的。它需要清理的數組的龐大規模「打斷」了真正的工作。

要看到GC的影響,您StartNew調用之前添加這些行 - 這樣的GC工作時以前的時機:

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; 
GC.Collect(); 
+0

我剛剛修改了代碼,因此arr是類的靜態字段,因此一旦由CreateHugeStringArray創建,它就不會被收集 - 然後將DoMyWork部分放入for循環中。第一次通過for循環通常是350ms,通過循環的所有其他時間都是大約50ms - 這種情況下需要額外時間的任何想法。 – PaulF

+1

這可能是JIT @PaulF。 – mjwills

+0

應該想到這一點。 – PaulF

0

下面的代碼創建10000個新的字符串對象,迫使垃圾收集運行:

string str = ""; 

for (int i = 0; i < 10000; i++) str += i; 

垃圾收集器的性能是成正比

  • 已分配
  • 的內存總量在使用

你CreateHugeStringArray()分配非常大的對象,在使用增加的內存總量的對象的數量。在極端情況下,這部分內存可能在磁盤上(換頁),進一步降低系統性能。

你故事的寓意是 - 除非你需要,否則不要分配內存。

0

還沒有找到原因,但似乎在LOH中有一個巨大的陣列顯着減緩垃圾收集。然而,如果我們創建許多較小的陣列來保存相同數量的數據(這將轉到第2代而不是LOH),GC不會太慢。看起來帶有1kk字符串指針的數組佔用大約400萬字節的內存。所以爲了避免進入LOH,陣列必須佔用少於85千字節。這是約50倍。您可以使用舊招大數組分割成許多小數組

private static string[][] arrayTwoDimentional; 

    private static int _arrayLength = 1000000; 

    private static int _sizeFromExample = 200; 

    static void CreateHugeStringArrayTwoDimentional() 
    { 
     // Make 50 times more smaller arrays 
     arrayTwoDimentional = new string[_sizeFromExample * 50][]; 

     for (long i = 0; i < arrayTwoDimentional.Length; i++) 
     { 
      // Make array smaller 50 times 
      arrayTwoDimentional[i] = new string[_arrayLength/50]; 
      for (var index = 0; index < arrayTwoDimentional[i].Length; index++) 
      { 
       arrayTwoDimentional[i][index] = ""; 
      } 
     } 
    } 

    static string GetByIndex(long index) 
    { 
     var arrayLenght = _arrayLength/50; 
     var firstIndex = index/arrayLenght; 
     var secondIndex = index % arrayLenght; 

     return arrayTwoDimentional[firstIndex][secondIndex]; 
    } 

證明GC是瓶頸這裏

Inside DoMyWork 更換陣列布局

After moving

在這個例子中,數組後大小是硬編碼。在Codeproject上有一個很好的例子,您可以如何計算存儲對象類型的大小,這將有助於調整數組大小: https://www.codeproject.com/Articles/129541/NET-memory-problem-with-uncontrolled-LOH-size-and

相關問題