2017-09-29 7 views
2

非常に慣れていない生データフォーマットを圧縮する必要があるのは、特定の部分を抽出してLUTとはるかに短いデータライン。regex/linqの2GBメモリフットプリントを減らすか、そうでなければdistinct group値を取得する

私のテストデータは、約400k行を含むファイルから読み込まれます。私はデータを作成するためのプログラム的な方法で書きました。

今のところ、私は主に出力するグループの異なる値に興味があります。 'ref'と 'ref2'も必要ですが、それ自体はほとんどユニークです。

regexを介した 'naive' aproachは動作しますが、2GB以上のプロセスメモリと多くの時間を要します。正規表現を最適化する方法とは別の方法で別の値を取得する方法はありますか?

編集:Wiktor Stribiżewヒントのおかげで700MBまで編集できます。

using System; 
using System.Linq; 
using System.Text.RegularExpressions; 

TESTDATA /制作:

static string[] NAMES1 = "cat,dog,deer,buffalo,lion,mouse,hedgehog".Split(','); 
static string[] NAMES2 = "lily,rose,thyme,salt".Split(','); 
static string[] TYPES = "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2".Split(','); 

static string MakeText (int n, string[] names) 
{ 
    if (n % 15 == 0) 
    return $"{names[n % names.Length]}${n}"; 
    else if (n % 5 == 0) 
    return $"{names[n % names.Length]}${n}${TYPES[n % TYPES.Length]}"; 
    else 
    return $"{names[n % names.Length]}$EGAL${n}${TYPES[n % TYPES.Length]}$MORE"; 
} 

static string CreateData (int rows) 
{ 
    var ids = Enumerable.Range(0,8).Select(n => $"{Guid.NewGuid()}-1234").ToList(); 
    var id2 = $"{Guid.NewGuid()}-9876"; 

    Console.WriteLine ($"\nIDs: {string.Join ("\n ", ids)}"); 
    Console.WriteLine ($"\nID 2: {id2}"); 
    Console.WriteLine ($"\nNAMES1: {string.Join (", ", NAMES1)}"); 
    Console.WriteLine ($"\nNAMES2: {string.Join (", ", NAMES2)}"); 

    var inOrder = 
    Enumerable 
    .Range(0, rows/2) 
    .Select(n => $"{MakeText(n,NAMES1)}," + 
       $"{ids[ Math.Min(ids.Count-1,n/(rows/2/ids.Count))]}" + 
       $"={MakeText(Math.Min(int.MaxValue,rows*10)-n,NAMES2)},{id2}"); 

    var outOfOrder = 
    Enumerable 
    .Range(rows/2, rows) 
    .Select(n => $"{MakeText(n,NAMES1)},{ids[n % ids.Count]}" + 
       $"={MakeText(int.MaxValue-n,NAMES2)},{id2}"); 

    return string.Join (Environment.NewLine, inOrder.Concat (outOfOrder)) 
    + Environment.NewLine; 
} 

正規表現の使用:

static void Main (string[] args) 
{ 
    var content= CreateData (400000); 

    var r = new Regex( 
    @"^(?<ar>.+?)(\$EGAL)?(\$(?<ref>[0-9]+))(\$(?<typ>[123]))?" + 
    @"(\$MORE)?(,(?<id>.+))"+ 
    @"=(?<ar2>.+?)(\$EGAL)?(\$(?<ref2>[0-9]+))(\$(?<typ2>[123]))?" + 
    @"(\$MORE)?(,(?<id2>.+))(\r)?$", 
    RegexOptions.Compiled|RegexOptions.Multiline|RegexOptions.ExplicitCapture); 

    var matches = r.Matches(content).OfType<Match>().ToList(); 

    var ids1 = matches 
    .Select(
     m => m 
     .Groups["id"] 
     .Captures 
     .OfType<Capture>() 
     .Select(c=> c.Value) 
    ) 
    .SelectMany(i=>i) 
    .Distinct() 
    .ToList(); 

    Console.WriteLine ($"\nids: {string.Join ("\n ", ids1)}"); 

    var ids2 = matches 
    .Select(
     m => m 
     .Groups["id2"] 
     .Captures 
     .OfType<Capture>() 
     .Select(c=> c.Value) 
    ) 
    .SelectMany(i=>i) 
    .Distinct() 
    .ToList(); 

    Console.WriteLine ($"\nid 2: {string.Join ("\n ", ids2)}"); 

    var n1 = matches 
    .Select(
     m => m 
     .Groups["ar"] 
     .Captures 
     .OfType<Capture>() 
     .Select(c=> c.Value) 
    ) 
    .SelectMany(i=>i) 
    .Distinct() 
    .ToList(); 

    Console.WriteLine ($"\nnames1: {string.Join (", ", n1)}"); 

    var n2 = matches 
    .Select(
     m => m 
     .Groups["ar2"] 
     .Captures 
     .OfType<Capture>() 
     .Select(c=> c.Value) 
    ) 
    .SelectMany(i=>i) 
    .Distinct() 
    .ToList(); 

    Console.WriteLine ($"\nnames2: {string.Join (", ", n2)}"); 

    // need the type's and refs as well to recreate the substituted 
    // datalines after creating the LUT and put them together in some 
    // new file - that's easy. 
} 
+1

パターンを正確にすると、[適切なサブパターンで '.'を置き換えます](https://regex101.com/r/knSux2/1 )、あなたは大幅に正規表現のパフォーマンスを向上させることができます。また、 'Regex.Matches'を一度実行し、結果を変数に格納し、キャプチャコレクションにアクセスするときに再利用します。 –

+0

Regex.Matchesを2GBから700MBに減らしてくれてありがとう、Wiktorに感謝します。私はサブパターンを調べます。 –

+0

パターンの最適化では、テストの内容には可能なすべてのバリエーションの種類が含まれていないようです。主なポイントは、このようなシナリオでの主要なパフォーマンス・キラーである '。+? 'よりも制限されたパターンを探しています。 –

答えて

2

内容全体が一度に解析された場合は、試合は/キャプチャ・コレクションは、おそらく常に一定を占めることになりますこれまでにしか最適化できなかった空間の量です。メモリを使用すると、一度に1行ずつ処理し、一意の値(たとえば、HashSet)だけを保持する方が良いでしょう。個人的に私はIOの名前空間からの読者を使用したい(この例StringReaderでは、そのファイルを他の読者の既存のために使用することができる)

var r = new Regex( 
    @"^(?<ar>.+?)(\$EGAL)?(\$(?<ref>[0-9]+))(\$(?<typ>[123]))?" + 
    @"(\$MORE)?(,(?<id>.+))"+ 
    @"=(?<ar2>.+?)(\$EGAL)?(\$(?<ref2>[0-9]+))(\$(?<typ2>[123]))?" + 
    @"(\$MORE)?(,(?<id2>.+))?$", 
    RegexOptions.Compiled|RegexOptions.ExplicitCapture); 

var values= new[]{"ar", "ref", "typ","id", "ar2", "ref2", "typ2", "id2"}.Select((s,i)=> new {Group=s, Index = i + 1, Values = new HashSet<string>()}).ToArray();  
values= values.Where(v=> !v.Group.StartsWith("ref")).ToArray(); //this line is only an addition for testing to not include the ref values 
using(var sr = new StringReader(content)){ 
    string line; 
    while((line = sr.ReadLine()) != null){ 
     var gr = r.Match(line).Groups; 
     foreach(var gv in values) 
      gv.Values.Add(gr[gv.Index].Value); 
    } 
} 

//output results 
foreach(var v in values) 
    Console.WriteLine ($"\n{v.Group}s: {string.Join ("\n ", v.Values)}"); 

本当に正規表現自体に触れていませんが、上記のすべての解析すべきですメモリ占有量の少ない一意の値。それは、ラインのためのカスタムパーサーを使用することによってさらに最適化することができます。 (別々の変数の代わりに、すべてのコレクションはvaluesオブジェクト内にあります)

+0

私はユースケースを鋭く見た後、ユーザーが必要とするときはいつでも、(ローカルにキャッシュされ、圧縮された)10MBファイルに正規表現を適用するだけで、この "解析とメモリへの格納"を忘れてしまいます。正規表現が行単位でファイルを読み込み、内部で一致するものを見つけるのに1秒かかる場合でも、これはアプリケーションの存続期間中に多くのメモリを消費するよりも優れています。 –

+0

いいですね。正確な使用状況はわかりませんが、ファイルがすでにメモリに保存されている場合は、結果が同じデータを参照している限り、検索結果をメモリに保存する必要はありません。 たとえば、ファイルは行の文字列配列として格納され、バッファされた結果はそれらの行への参照を格納します(正規表現の一致ではありません)。それはちょうど推測です。(ファイルがバッファリングされていない場合は、StringReaderの代わりにFileReaderを使用して、メモリ消費量を最小限に抑えることができます)。 (psでは、最適なパフォーマンスのために正規表現の代わりに直接解析することは常に可能かもしれません) –

関連する問題