我正在开发一个软件的一部分,我有一个列表(目前List<Sample>
),如下所示:
public class Sample
{
//...
public double ValueChannel1 { get; set; }
public double ValueChannel2 { get; set; }
//...
}
这些列表有大约100到几千个这样的样本,每秒大约有10万个样本 现在我需要找出每个列表中的最大值和最小值,我现在按照以下方式执行:
var Ch1Max = task.Samples.Max<Sample>(s => s.ValueChannel1);
var Ch1Min = task.Samples.Min<Sample>(s => s.ValueChannel1);
var Ch2Max = task.Samples.Max<Sample>(s => s.ValueChannel2);
var Ch2Min = task.Samples.Min<Sample>(s => s.ValueChannel2);
毫不奇怪,这不是很快,所以我问自己是否有更快的事情要做,但是我想不到或找不到?
有人知道更快的方法吗? 也许有一种方法可以找到min和max的“一个循环”而不是一个用于min和一个用于max?
修改
我用当前的代码描述了以下结果:
每个包含其中一个列表的731个任务需要845毫秒来处理,95%用于最小/最大搜索
我没有特定的“目标时间”,但由于这在我的应用程序中一直运行(因为它捕获测量数据),它应该尽可能少地使用CPU,以尽可能降低硬件要求... < / p>
找到最佳解决方案:
最后,我选择蒂姆的解决方案,因为它甚至比康拉德更快一点:
蒂姆的解决方案导致了约53%的加速,康拉德斯的“仅”加速了43%。
最终解决方案(暂时):
double Ch1Max = Double.MinValue, Ch1Min = Double.MaxValue;
double Ch2Max = Double.MinValue, Ch2Min = Double.MaxValue;
var samples = task.Samples.ToArray();
int count = samples.Length;
for (int i = 0; i < count; ++i)
{
var valueChannel1 = samples[i].ValueChannel1; // get only once => faster
if (valueChannel1 > Ch1Max) Ch1Max = valueChannel1;
if (valueChannel1 < Ch1Min) Ch1Min = valueChannel1;
var valueChannel2 = samples[i].ValueChannel2;
if (valueChannel2 > Ch2Max) Ch2Max = valueChannel2;
if (valueChannel2 < Ch2Min) Ch2Min = valueChannel2;
}
与我最初的解决方案相比,这总计加速了~70%......
答案 0 :(得分:8)
如果您可以控制List<Sample>
对象(我的意思是您没有从第三方代码中获取它等),您可以将它包装在您自己的类中,这将跟踪最大值和最小值当你在里面添加元素时,它会立即运行。
只需查看新Sample
是否“设置新记录”并相应调整缓存的最大/最小值(如果是这样)。
如果列表仅向前,那么该方法将非常有效,例如。你没有从列表中删除元素。
修改强>
这是一个示例实现(它有点“概念证明”,肯定有很多改进空间):
public class Sample
{
public double ValueChannel1
{
get;
set;
}
public double ValueChannel2
{
get;
set;
}
// etc.
}
public class SampleList
{
/* that's the list we're enwrapping.
* SampleList could also be inherited from List<Sample>, but in general this approach is less recommended -
* read up on "composition over inheritance". */
private List<Sample> _samples = new List<Sample>();
/// <summary>
/// Caches the lowest known value of ValueChannel1 property
/// </summary>
public double? ValueChannel1Minimum // it's a nullable double, because while the list is still empty, minimums and maximums have no value yet
{
get;
private set;
}
public double? ValueChannel1Maximum { get; private set; }
public double? ValueChannel2Minimum { get; private set; }
public double? ValueChannel2Maximum { get; private set; }
public void Add(Sample sample)
{
if (sample == null)
{
throw new ArgumentNullException("sample");
}
// have you beat the record?
if (sample.ValueChannel1 <= (ValueChannel1Minimum ?? double.MaxValue))
{
// note: the essence of the trick with ?? operator is: if there's no minimum set yet, pretend the minimum to be the biggest value there is.
// practically speaking, it ensures that the first element added to the list
// sets the new minimum, whatever value that element had.
ValueChannel1Minimum = sample.ValueChannel1;
}
if (sample.ValueChannel1 >= (ValueChannel1Maximum ?? double.MinValue))
{
ValueChannel1Maximum = sample.ValueChannel1;
}
// etc. for other properties
_samples.Add(sample);
}
public List<Sample> ToList()
{
return _samples;
}
}
答案 1 :(得分:7)
您可以使用单个循环:
double Ch1Max = double.MinValue;
double Ch1Min = double.MaxValue;
double Ch2Max = double.MinValue;
double Ch2Min = double.MaxValue;
foreach(Sample s in samples)
{
if(s.ValueChannel1 > Ch1Max) Ch1Max = s.ValueChannel1;
if(s.ValueChannel1 < Ch1Min) Ch1Min = s.ValueChannel1;
if(s.ValueChannel2 > Ch2Max) Ch2Max = s.ValueChannel2;
if(s.ValueChannel2 < Ch2Min) Ch2Min = s.ValueChannel2;
}
答案 2 :(得分:1)
您始终可以自己实施最小/最大逻辑。使用Aggregate
扩展方法应该很容易:
Pair<Sample, Sample> minMax = task.Samples.Aggregate(
new Pair<Sample, Sample> {
First = new Sample { ValueChannel1 = double.MaxValue, ValueChannel2 = double.MaxValue },
Second = new Sample { ValueChannel1 = double.MinValue, ValueChannel2 = double.MinValue }
},
(minmax, sample) => {
minmax.First.ValueChannel1 = Math.Min(minmax.First.ValueChannel1, sample.ValueChannel1);
minmax.First.ValueChannel2 = Math.Min(minmax.First.ValueChannel2, sample.ValueChannel2);
minmax.Second.ValueChannel1 = Math.Max(minmax.Second.ValueChannel1, sample.ValueChannel1);
minmax.Second.ValueChannel2 = Math.Max(minmax.Second.ValueChannel2, sample.ValueChannel2);
});
Console.Out.WriteLine("Channel 1 Min = {0}, Channel 1 Max = {1}, Channel 2 Min = {2}, Channel 2 Max = {3}",
minMax.First.ValueChannel1,
minMax.Second.ValueChannel1,
minMax.First.ValueChannel2,
minMax.Second.ValueChannel2);
可能更好阅读(可能不是,取决于你是否喜欢功能风格思考),但它肯定比使用带有4个变量的简单foreach循环(由于大量函数调用)慢。关于这种方法的好处是它不使用任何外部变量(但是你可以在方法中封装foreach循环)。