C# 减少regex/linq的2 GB内存占用,否则解决获取不同的组值

C# 减少regex/linq的2 GB内存占用,否则解决获取不同的组值,c#,.net,regex,performance,linq,C#,.net,Regex,Performance,Linq,我需要通过提取某些部分来创建包含LUT和更短数据线的不同文件格式,从而将非常逐字逐句的原始数据格式压缩为更加精简的格式 我的Testdata是从一个文件中读取的,其中包含大约400k行—我以编程的方式创建数据 目前,我最感兴趣的是我输出的群体的不同价值观。”“ref”和“ref2”也是需要的,但它们本身几乎是唯一的 通过regex工作的“天真”方案。。。但是占用超过2GB的进程内存和大量的时间-如何优化正则表达式或以其他方式获得不同的值 编辑:由于提示,已降至700MB 测试数据/创建: sta

我需要通过提取某些部分来创建包含LUT和更短数据线的不同文件格式,从而将非常逐字逐句的原始数据格式压缩为更加精简的格式

我的Testdata是从一个文件中读取的,其中包含大约400k行—我以编程的方式创建数据

目前,我最感兴趣的是我输出的群体的不同价值观。”“ref”和“ref2”也是需要的,但它们本身几乎是唯一的

通过regex工作的“天真”方案。。。但是占用超过2GB的进程内存和大量的时间-如何优化正则表达式或以其他方式获得不同的值

编辑:由于提示,已降至700MB

测试数据/创建:

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.
}
static void Main(字符串[]args)
{
var内容=CreateData(400000);
var r=新的正则表达式(
@“^(?。+?)(\$EGAL)?(\$(?[0-9]+)(\$(?[123]))?”+
@“(\$MORE)?(,(.+)”+
@“=(?。+)(\$EGAL)?(\$(?[0-9]+)(\$(?[123]))?”+
@“(\$MORE)?(,(?。+)(\r)?$”,
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture);
var matches=r.matches(content).OfType().ToList();
var ids1=匹配项
.选择(
m=>m
.组[“id”]
.捕获
第()类
.选择(c=>c.Value)
)
.SelectMany(i=>i)
.Distinct()
.ToList();
Console.WriteLine($”\nids:{string.Join(“\n”,ids1)});
var ids2=匹配项
.选择(
m=>m
.组[“id2”]
.捕获
第()类
.选择(c=>c.Value)
)
.SelectMany(i=>i)
.Distinct()
.ToList();
Console.WriteLine($”\nid2:{string.Join(“\n”,ids2)});
var n1=匹配项
.选择(
m=>m
.组[“ar”]
.捕获
第()类
.选择(c=>c.Value)
)
.SelectMany(i=>i)
.Distinct()
.ToList();
Console.WriteLine($”\nnames1:{string.Join(“,”,n1)});
var n2=匹配项
.选择(
m=>m
.组[“ar2”]
.捕获
第()类
.选择(c=>c.Value)
)
.SelectMany(i=>i)
.Distinct()
.ToList();
Console.WriteLine($”\nnames2:{string.Join(“,”,n2)});
//还需要类型和引用来重新创建替换的
//创建LUT后的数据线,并将它们以某种方式组合在一起
//新文件-这很简单。
}

如果一次性解析整个内容,那么匹配/捕获集合可能总是会占用一定的空间,目前为止只能进行优化。内存方面,最好一次处理一行,只保留唯一的值(例如,使用
哈希集
)。就个人而言,我喜欢使用IO名称空间中的读卡器(在本例中为
StringReader
,但对于现有文件,可以使用其他读卡器)

var r=新正则表达式(
@“^(?。+?)(\$EGAL)?(\$(?[0-9]+)(\$(?[123]))?”+
@“(\$MORE)?(,(.+)”+
@“=(?。+)(\$EGAL)?(\$(?[0-9]+)(\$(?[123]))?”+
@“(\$MORE)?(,(?。+)?$”,
RegexOptions.Compiled | RegexOptions.ExplicitCapture);
var values=new[]{“ar”、“ref”、“typ”、“id”、“ar2”、“ref2”、“typ2”、“id2”}。选择((s,i)=>new{Group=s,Index=i+1,values=new HashSet())。ToArray();
values=values.Where(v=>!v.Group.StartsWith(“ref”)).ToArray()//该行仅用于添加测试,不包括参考值
使用(var sr=新的StringReader(内容)){
弦线;
而((line=sr.ReadLine())!=null){
var gr=r.Match(line).组;
foreach(价值中的var gv)
gv.Values.Add(gr[gv.Index].Value);
}
}
//输出结果
foreach(值中的var v)
Console.WriteLine($“\n{v.Group}s:{string.Join(“\n”,v.Values)}”);
还没有真正触及正则表达式本身,但上面的内容应该以较低的内存占用率解析所有唯一的值。可以通过为行使用自定义解析器对其进行进一步优化。
(所有集合都在
对象中,而不是单独的变量)

如果将模式精确一点,则可以大大提高正则表达式的性能。另外,运行
Regex.Matches
一次,将结果存储在某个变量中,并在访问捕获集合时重用它。感谢Wiktor,没有重复Regex。Matches将我的内存从2GB降至700MB。我将研究子模式。好吧,通过模式优化,看起来测试内容并没有真正包括所有可能的变化类型。主要的一点仍然是找到比
+?
更受限制的模式,这是此类场景中的主要性能杀手。有什么原因不能压缩数据吗?Matthew:数据包含不需要的死文本(如$more,$EGAL)。简单地压缩它仍然需要压缩这个超级数据,这样我就可以完全消除它。在仔细查看用例之后,我将放弃这种“解析并存储在内存中以进行查找”,只要在用户需要的任何时候对(本地缓存和压缩的)10MB文件应用正则表达式即可。即使正则表达式需要1s来逐行读取文件并在其中找到匹配项,这也比在应用程序的生命周期中占用大量内存要好。听起来不错。不知道确切的用例,但是如果文件已经存储在内存中,那么只要结果引用相同的数据,存储查找结果就不必占用内存。例如,该文件存储为一个字符串行数组,缓冲结果存储对这些行的引用(而不是正则表达式匹配项)。不过这只是猜测而已。(如果文件未缓冲,则可以直接在文件上使用FileReader而不是StringReader,以降低内存消耗)。(ps为了获得最佳性能,可能总是可以直接解析而不是正则表达式)
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.
}
 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)}");