2011-11-05 33 views
11

如何在大字符串中運行大量RegExes(查找匹配項)而不引起LOH碎片?RegEx,StringBuilder和大對象堆碎片

它是.NET Framework 4.0,因此我使用StringBuilder所以它不在LOH中,但只要我需要運行RegEx,我必須調用StringBuilder.ToString()這意味着它將在LOH中。

有沒有解決這個問題的方法?實際上不可能有一個長期運行的應用程序來處理像這樣的大字符串和RegEx。

構想來解決這個問題:雖然在思考這個問題

,我想我找到了一個骯髒的解決方案。

在給定的時間,我只有5個字符串,這5個字符串(大於85KB)將被傳遞給RegEx.Match

由於出現碎片,因爲新對象將不適合於蕙空的空間,這應該解決的問題:

  1. PadRight所有字符串到最大。被接受的大小,讓我們說1024KB(我可能需要StringBuider做到這一點)
  2. 這樣所有新的字符串作爲以前的字符串已經超出了範圍將適合於已經清空內存
  3. 不會有任何碎片,因爲對象大小始終是相同的,因此我將只在給定時間分配1024 * 5,並且這些空間在LOH中將在這些字符串之間共享。

我想這個設計的最大問題如果其他大對象在LOH中分配這個位置會導致應用程序分配大量的1024 KB字符串,可能會導致更多的碎片。 fixed聲明可能會有幫助,但是我怎樣才能發送一個固定的字符串到RegEx而不實際創建一個不位於固定內存地址的新字符串?

關於這個理論的任何想法? (遺憾的是我不能輕易地重現該問題,我通常試圖用一個內存分析器觀察其變化,不知道什麼樣的獨立的測試情況我可以爲此寫)

+2

你確定大對象堆變得零散嗎?我用很大的(幾百千字節)字符串做了很多工作,而且我從未遇到過LOH碎片問題。 –

+1

是的,我確定。應用程序需要耗費內存並長時間運行以查看其實際影響。如果你真的做內存分析,你可能會看到它影響你,但沒有足夠多的崩潰你的應用程序。 –

+1

是的,這很容易。一百塊錢可以買一個64位操作系統。沒有一種編程工作可以與之相匹配。 –

回答

6

OK,這是我嘗試在一個非常通用的方式,但一些明顯的侷限性解決這個問題。由於我沒有在任何地方看到這個建議,每個人都在抱怨LOH碎片,所以我想分享代碼以確認我的設計和假設是正確的。

理論:

  1. 創建共享大量的StringBuilder(這是存儲讀取我們從流讀取的大弦) - new StringBuilder(ChunkSize * 5);
  2. 創建一個龐大的字符串(必須比大最大可接受尺寸),應該使用空白空格進行初始化。 - 新字符串('',ChunkSize * 10);
  3. 將字符串對象存儲到內存中,以便GC不會混淆它。 GCHandle.Alloc(pinnedText, GCHandleType.Pinned)。即使通常固定LOH對象,這似乎也會提高性能。也許是因爲unsafe代碼
  4. 讀流到共享的StringBuilder,然後不安全使用索引
  5. 將其複製到pinnedText傳遞pinnedText以正則表達式

使用這種實現下面的代碼工作就像沒有LOH分配。如果我切換到new string(' ')分配,而不是使用一個靜態StringBuilder,或使用StringBuilder.ToString()代碼可以與outofmemory exception

崩潰之前分配較少300%存儲器我也證實用存儲器剖析的結果,存在在此沒有LOH碎片實現。我仍然不明白爲什麼RegEx不會導致任何意外問題。我還測試了不同的昂貴RegEx模式,結果相同,沒有碎片。

代碼:

http://pastebin.com/ZuuBUXk3

using System; 
using System.Collections.Generic; 
using System.Runtime.InteropServices; 
using System.Text; 
using System.Text.RegularExpressions; 

namespace LOH_RegEx 
{ 
    internal class Program 
    { 
     private static List<string> storage = new List<string>(); 
     private const int ChunkSize = 100000; 
     private static StringBuilder _sb = new StringBuilder(ChunkSize * 5); 


     private static void Main(string[] args) 
     { 
      var pinnedText = new string(' ', ChunkSize * 10); 
      var sourceCodePin = GCHandle.Alloc(pinnedText, GCHandleType.Pinned); 

      var rgx = new Regex("A", RegexOptions.CultureInvariant | RegexOptions.Compiled); 

      try 
      { 

       for (var i = 0; i < 30000; i++) 
       {     
        //Simulate that we read data from stream to SB 
        UpdateSB(i); 
        CopyInto(pinnedText);     
        var rgxMatch = rgx.Match(pinnedText); 

        if (!rgxMatch.Success) 
        { 
         Console.WriteLine("RegEx failed!"); 
         Console.ReadLine(); 
        } 

        //Extra buffer to fragment LoH 
        storage.Add(new string('z', 50000)); 
        if ((i%100) == 0) 
        { 
         Console.Write(i + ","); 
        } 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex.ToString()); 
       Console.WriteLine("OOM Crash!"); 
       Console.ReadLine(); 
      } 
     } 


     private static unsafe void CopyInto(string text) 
     { 
      fixed (char* pChar = text) 
      { 
       int i; 
       for (i = 0; i < _sb.Length; i++) 
       { 
        pChar[i] = _sb[i]; 
       } 

       pChar[i + 1] = '\0'; 
      } 
     } 

     private static void UpdateSB(int extraSize) 
     { 
      _sb.Remove(0,_sb.Length); 

      var rnd = new Random(); 
      for (var i = 0; i < ChunkSize + extraSize; i++) 
      { 
       _sb.Append((char)rnd.Next(60, 80)); 
      } 
     } 
    } 
} 
0

你可以做你的工作在某些時間點卸載的AppDomain?

+0

事情是你仍然需要共享結果,除非你使用共享存儲並直接從內存或文件中以流的形式讀取數據,你仍然有同樣的問題。因爲如果您以任何方式使用遠程處理,那麼您將再次創建大數組或字符串,這些數組或字符串將轉到LOH,現在在這兩個應用程序域中。共享內存,內存映射文件等確實是一個解決方案,但在大型應用程序中確實非常複雜,並且性能受到了很大影響。 –

0

一種選擇是找到執行REG-EX的一些方法在非基於陣列的數據結構相匹配。不幸的是,一個快速的谷歌沒有提供很多基於流的reg-ex庫。我猜想reg-ex算法需要做很多後向跟蹤,這不受流支持。

你絕對需要正則表達式的全部力量嗎?你能否實現你自己的更簡單的搜索功能,可以在85kb以下的字符串鏈表上工作?

另外,如果您長時間堅持大對象引用,則LOH碎片只會導致問題。如果你不斷創造和摧毀它們,那麼LOH就不應該成長。

FWIW,我發現RedGate ANTS memory profiler非常擅長跟蹤LOH中的物體和碎片水平。

+0

「如果長時間堅持大對象引用,LOH碎片只會引起問題」。AFAIK這是不正確的,任何超過85KB的文件都將位於LOH中,無論它們持有多久。我使用的是螞蟻探查器,確實很不錯。 –

+0

是的,需要RegEx的全部功能 –

+0

對不起,我的意思是,我只在長時間持續引用時發現LOH存在問題。你說的正確的是超過85K的任何東西都會進入LOH。爲了讓我明白,你的問題是你在LOH中的字符串之間是否有其他長期對象被分配,導致你的字符串分配被推到LOH中,直到內存用完? – SimonC