2013-11-27 7 views
3

Я разрабатываю часть программного обеспечения, где у меня есть список (List<Sample> в данный момент) образцов, как следующее:Является ли IEnumerable.Max() самым быстрым способом?

public class Sample 
{ 
    //... 
    public double ValueChannel1 { get; set; } 
    public double ValueChannel2 { get; set; } 
    //... 
} 

Эти списки имеют между ~ 100 и несколько тысяч этих образцов и есть около 100 тыс. выборок в секунду.
Теперь мне нужно найти максимальное и минимальное значение из каждого из этих списков, которые я делаю следующим образом на данный момент:

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?

Edit:
Я профилированный текущий код со следующими результатами:
731 задач каждый из которых содержит один из этих списков, необходимых для обработки 845ms и 95% что там, где мин/макс поиска.
У меня нет специального «целевого времени», но поскольку это все время работает в моем приложении (поскольку оно захватывает данные измерений), это должно приводить к как можно меньшей загрузке процессора, чтобы максимально снизить требования к оборудованию. .

Лучшее решение найдено:
В конце концов я выбираю soltuion от Тима, как это было даже немного быстрее, чем Konrad из них:
решения Тима вызвало ускорение на ~ 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% по сравнению с моим исходным раствором ...

+2

Как медленно «не очень быстро»? Вы это оценили? – Arran

+0

Отсутствие каких-либо специфических особенностей по времени/целевому времени? – Voidpaw

+2

Есть способ найти их в одном цикле. Прокрутите их и отслеживайте минимальное и максимальное значение. – MAV

ответ

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; 
} 
+0

обычно 'for' быстрее, но все зависит от того, как все это скомпилировано – Grundy

+1

'обычно' не соответствует действительности. Это было верно в .NET 1.1. http://lj.rossia.org/users/steinkrauz/300537.html Итак, между тем 'foreach' скомпилирован как' for' (если возможно). –

+0

Спасибо за этот вклад - как глупо от меня, чтобы не думать об этом простом ускорении x4 - у меня просто не было идеи объединить все 4 в одном цикле ... – ChrFin

8

Если у вас есть контроль над List<Sample> объекта (я имею в виду вы не получите его от кода третьей стороны и т.д.), вы можете окутывать его в ваш собственный класс, который будет отслеживать максимальные и минимальные значения «на лету», поскольку вы добавляете в него элементы.

Все, что потребуется, - это просто посмотреть, не устанавливает ли новый Sample «установить новую запись» и соответственно корректирует ваши кешированные максимальные/минимальные значения.

Этот подход будет очень эффективным, если список доступен только для пересылки, например. вы не удаляете элементы из списка.

EDIT:

Вот пример реализации (это немного «доказательство концепции», там, конечно, много места для совершенствования):

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; 
    } 
} 
+0

То точно для меня - попробуем это и отчитаю ... – ChrFin

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 в методе).

+0

Спасибо за этот вклад - даже если это не лучшее решение здесь Возможно, я смогу использовать эту логику где-то еще в будущем, поэтому она сохраняется как «хорошо знать» ... – ChrFin

Смежные вопросы