Woj*_*ski 6 c# xml linq performance bigdata
假设我们有一个Foo班级:
public class Foo
{
public DateTime Timestamp { get; set; }
public double Value { get; set; }
// some other properties
public static Foo CreateFromXml(Stream str)
{
Foo f = new Foo();
// do the parsing
return f;
}
public static IEnumerable<Foo> GetAllTheFoos(DirectoryInfo dir)
{
foreach(FileInfo fi in dir.EnumerateFiles("foo*.xml", SearchOption.TopDirectoryOnly))
{
using(FileStream fs = fi.OpenRead())
yield return Foo.CreateFromXML(fs);
}
}
}
Run Code Online (Sandbox Code Playgroud)
为了获得观点,我可以说这些文件中的数据已经记录了大约2年,频率通常为每分钟几个Foo.
现在:我们有一个参数TimeSpan TrainingPeriod,例如大约15天.我想要完成的是致电:
var allTheData = GetAllTheFoos(myDirectory);
Run Code Online (Sandbox Code Playgroud)
并获得IEnumerable<Foo> TrainingSet, TestSet它,其中TrainingSet包括Foos记录的前15天,以及TestSet所有其余的.然后,TrainingSet我们想要计算一些常量内存数据(如平均值Value,一些线性回归等),然后使用计算值开始使用TestSet.换句话说,我的代码在语义上应该等价于:
TimeSpan TrainingPeriod = new TimeSpan(15, 0, 0); // hope it says 15 days
var allTheData = GetAllTheFoos(myDirectory);
List<Foo> allTheDataList = allTheData.ToList();
var threshold = allTheDataList[0].Timestamp + TrainingPeriod;
List<Foo> TrainingSet = allTheDataList.Where(foo => foo.Timestamp < threshold).ToList();
List<Foo> TestSet = allTheDataList.Where(foo => foo.Timestamp >= threshold).ToList();
Run Code Online (Sandbox Code Playgroud)
顺便说一句,XML文件命名约定确保了我,Foos将按时间顺序返回.当然,我不想将它全部存储在内存中,每次.ToList()都会调用它.所以我提出了另一个解决方案:
TimeSpan TrainingPeriod = new TimeSpan(15, 0, 0);
var allTheData = GetAllTheFoos(myDirectory);
var threshold = allTheDataList.First().Timestamp + TrainingPeriod; // a minor issue
var grouped = from foo in allTheData
group foo by foo.Timestamp < Training;
var TrainingSet = grouped.First(g => g.Key);
var TestSet = grouped.First(g => !g.Key); // the major one
Run Code Online (Sandbox Code Playgroud)
但是,关于这段代码存在一个小问题和一个主要问题.次要的是第一个文件至少被读取两次 - 实际上并不重要.但看起来TrainingSet和TestSet独立访问目录,每次读取每个文件并只选择那些持有特定时间戳约束的文件.我对此并不感到困惑 - 事实上如果它有效,我会感到困惑,并且必须再次重新考虑LINQ.但是这会引发文件访问问题,并且每个文件都会被解析两次,这完全浪费了CPU时间.
所以我的问题是:我可以仅使用简单的LINQ/C#工具来实现这种效果吗?我想我可以用一种好的蛮力方式做到这一点,超越一些GetEnumerator(),MoveNext()方法等等 - 请不要打扰它,我可以完全自己处理这个问题.
但是,如果有一些优雅,简短和甜蜜的解决方案,我们将非常感激.
谢谢!
另一个编辑:
我最终提出的代码如下:
public static void Handle(DirectoryInfo dir)
{
var allTheData = Foo.GetAllTheFoos(dir);
var it = allTheData.GetEnumerator();
it.MoveNext();
TimeSpan trainingRange = new TimeSpan(15, 0, 0, 0);
DateTime threshold = it.Current.Timestamp + trainingRange;
double sum = 0.0;
int count = 0;
while(it.Current.Timestamp <= threshold)
{
sum += it.Current.Value;
count++;
it.MoveNext();
}
double avg = sum / (double)count;
// now I can continue on with the 'it' IEnumerator
}
Run Code Online (Sandbox Code Playgroud)
当然还存在一些小问题,即非常复杂的MoveNext()的输出(它已经是IEnumerable的结尾吗?),但我希望一般的想法是明确的.但是在实际代码中,它不仅仅是我正在计算的平均值,而是不同类型的回归等.所以我想以某种方式提取第一部分,将其作为IEnumerable传递给从我的派生的类
public abstract class AbstractAverageCounter
{
public abstract void Accept(IEnumerable<Foo> theData);
public AverageCounterResult Result { get; protected set; }
}
Run Code Online (Sandbox Code Playgroud)
分离提取培训数据的责任及其处理.在我得到一个之前描述的过程之后IEnumerator<Foo>,我认为IEnumerable<Foo>首选将它传递给我的TheRestOfTheDataHandler实例.
您可以尝试在从初始 ienumerable 获得的 ienumerator 上实现有状态迭代器模式。
IEnumerable<T> StatefulTake(IEnumerator<T> source, Func<bool> getDone, Action setDone);
Run Code Online (Sandbox Code Playgroud)
此方法仅检查完成,调用 MoveNext,生成 Current 并更新完成(如果 movenext 返回 false)。
然后,您可以通过对此方法的后续调用来分割集合,并使用以下方法对其进行部分枚举,例如: TakeWhile Any First ...然后您可以在此基础上执行任何操作,但每个操作都必须枚举到最后。
var source = GetThemAll();
using (var e = source.GetEnumerator()){
bool done=!source.MoveNext();
foreach(var i in StatefulTake(e, ()=>done,()=>done=true).TakeWhile(i=>i.Time<...)){
//...
}
var theRestAverage = StatefulTake(e,()=>done,()=>done=true).Avg(i=>i.Score);
//...
}
Run Code Online (Sandbox Code Playgroud)
这是我在异步工具包中经常使用的模式。
更新:修复了 StatefulTake 方法的签名,它不能使用 ref 参数。此外,对 MoveNext 的初始调用也是必要的。这三种done变量引用和方法本身应该封装在一个上下文类中。