Reputation: 23113
I am developing a part of a software, where I have a list (List<Sample>
at the moment) of samples like the following:
public class Sample
{
//...
public double ValueChannel1 { get; set; }
public double ValueChannel2 { get; set; }
//...
}
These lists have between ~100 and several thousands of these samples and there are around 100k samples per second.
Now I need to find the maximum and minimum value out of each of these lists, which I do the following way at the moment:
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);
To no surprise this is not very fast, so I was asking myself if there is something faster to do that, but I could not think of or find one?
Does someone know a faster way to do this? Maybe there is a way to find min and max with "one loop" instead of one for min and one for max?
Edit:
I profiled the current code with the following results:
731 tasks each containing one of these lists needed 845ms to process and 95% of that where the min/max searching.
I have no specific "target time", but as this runs all the time in my app (as it is capturing measurement data) it should cause as little CPU utlization as possible, to keep the hardware requirements as low as possible...
Best solution found:
In the end I choose the soltuion from Tim, as it was even a little faster then Konrad ones:
Tim's solution caused a speed-up of ~53% and Konrads's "only" ~43%.
Final solution (for now):
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;
}
This sums up to a speed up of ~70% compared to my initial solution...
Upvotes: 3
Views: 3908
Reputation: 726
That's an old one, but i'll share my implementation as it generic and (i think) efficient :
public static (T min, T max) GetMinMax<T>(this IEnumerable<T> collection) where T : IComparable, new()
{
var min = collection.First();
var max = collection.First();
int counter = 0;
foreach (var item in collection)
{
if (counter == 0)
{
counter++;
continue;
}
if (item.CompareTo(max) > 0) // item > max
max = item;
if (item.CompareTo(min) < 0) // item < min
min = item;
}
return (min, max);
}
This makes use of linq only for the initialization, but it would be easy to make it "stand alone". (If any one has any remarks, i'll be glad to hear it)
Upvotes: 0
Reputation: 265605
You can always implement the min/max logic yourself. It should be easy using the Aggregate
extension method:
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);
Might be nicer to read (might be not, depending on whether you like functional style thinking), but it will be definitely slower than using a simple foreach loop with 4 variables (due to lots of function calls). Nice thing about this approach is that it does not use any external variables (but then you can encapsulate the foreach loop in a method).
Upvotes: 1
Reputation: 460238
You can use a single loop:
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;
}
Upvotes: 7
Reputation: 8394
If you have control over your List<Sample>
object (I mean you don't get it from a third party code etc.), you could enwrap it in your own class, which would keep track of maximum and minimum values on the fly, as you're adding elements into it.
All it would take is just looking up whether the new Sample
does not "set the new record" and accordingly adjust your cached maximum / minimum values if so.
That approach would be very efficient if the list is forward-only, eg. you're not removing elements from the list.
EDIT:
Here's a sample implementation (it's bit of a "proof of concept", there's surely lots of space for improvement):
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;
}
}
Upvotes: 8