.Net - Самый быстрый способ суммировать коллекцию числовых значений - PullRequest
2 голосов
/ 06 августа 2009

У меня есть метод, который суммирует коллекцию десятичных значений, используя метод, подобный этому ...

Dim _amountCollection as New List(Of Decimal)
_amountCollection.Add(145.12D)
_amountCollection.Add(11.32D)
_amountCollection.Add(6547.07D)

Dim _totalAmount as Decimal

For Each _individualAmount as Decimal in _amountCollection
  _totalAmount += _individualAmount
Next 

В фактическом коде обычно есть больше членов в наборе сумм, и есть по крайней мере 50 отдельных собраний сумм, которые необходимо суммировать в общей операции.

Этот пересчет общей суммы вызывается часто (по крайней мере, один раз, а затем снова для каждого изменения содержимого коллекции) и отображается в трассировках профиля как от 2 до 5% общего времени работы. Я смотрю, чтобы узнать, есть ли у кого-нибудь представление о том, как ускорить эту операцию суммирования, или это будет самая быстрая операция.

Кэширование в этом случае нереально, потому что суммы должны пересчитываться.

**** EDIT ** Для Равадре и Джоэла - общая сумма хранится на уровне класса (каждая коллекция сумм и сумма содержатся в экземпляре класса)

Есть идеи?

Ответы [ 9 ]

3 голосов
/ 06 августа 2009

Попробуйте создать подклассы List (Of Decimal), выставьте свойство TotalSum. Каждый раз, когда вызывается Add (), увеличивается на значение, указанное в поле. Это избавит вас от необходимости перебирать список, чтобы получить сумму.

Edit: После попытки было бы лучше реализовать IList (Of Decimal), потому что методы List не являются виртуальными. Что-то вроде этого (Да, я знаю, что это C # ...)

public class DecimalSumList : IList<decimal>
{
    readonly IList<decimal> _allValues = new List<decimal>();
    decimal _totalSum;
    public decimal TotalSum { get { return _totalSum; } }
    public void Add(decimal item)
    {
        _totalSum += item;
        _allValues.Add(item);
    }

    public void CopyTo(decimal[] array, int arrayIndex)
    {
        _allValues.CopyTo(array, arrayIndex);
    }

    bool ICollection<decimal>.Remove(decimal item)
    {
        _totalSum -= item;
        return _allValues.Remove(item);
    }

    public int Count
    {
        get { return _allValues.Count; }
    }

    public bool IsReadOnly
    {
        get { throw new NotImplementedException(); }
    }

    public void Remove(decimal item)
    {
        _totalSum -= item;
        _allValues.Remove(item);
    }
    public void Clear()
    {
        _totalSum = 0;
        _allValues.Clear();
    }

    public bool Contains(decimal item)
    {
        return _allValues.Contains(item);
    }

    public IEnumerator<decimal> GetEnumerator()
    {
        return _allValues.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public int IndexOf(decimal item)
    {
        return _allValues.IndexOf(item);
    }

    public void Insert(int index, decimal item)
    {
        _totalSum += item;
        _allValues.Insert(index,item);
    }

    public void RemoveAt(int index)
    {
        _totalSum -= _allValues[index];
        _allValues.RemoveAt(index);
    }

    public decimal this[int index]
    {
        get { return _allValues[index]; }
        set { _allValues[index] = value; }
    }
}
2 голосов
/ 06 августа 2009

Если это валютные значения, рассмотрите возможность использования масштабированных целых чисел. Для 2 десятичных знаков вместо количества долларов вы сохраняете количество копеек, а затем делите на 100, когда вам нужно вывести результат. Для 5 десятичных знаков ваши переменные внутренне используют исходное число * 100000. Это должно улучшить как производительность, так и точность, особенно если вы выполняете какое-либо деление.

2 голосов
/ 06 августа 2009

Мне непонятно, как вы собираетесь добавлять список чисел быстрее, чем добавлять каждое по очереди!

Однако использование decimal s для выполнения арифметики, вероятно, намного, намного, намного медленнее, чем использование типа с плавающей запятой, поэтому, если вам на самом деле не нужна точность, я предлагаю переключиться.

1 голос
/ 06 августа 2009

2-5% от общего времени трассировки профиля не так уж много.

Даже если вы сократите время выполнения этого метода пополам, вы сэкономите не более нескольких процентов от общего времени выполнения.

Лучше всего сначала искать в другом месте.

1 голос
/ 06 августа 2009

существует не менее 50 отдельных коллекций сумм, которые необходимо суммировать в общей операции.

Как насчет кэширования сумм из каждой отдельной коллекции? Затем, когда коллекция изменяется, вам нужно только подвести итог этой коллекции и итоговых сумм.

1 голос
/ 06 августа 2009

А как же totalAmount = amountCollection.Sum()?

0 голосов
/ 06 августа 2009

Если вы не хотите реализовывать версию IList / IDictionary, как предложил statenjason, вы можете создать оболочку:

Public Class TotalDictionaryWrapper
        Private innerDictionary As IDictionary(Of Integer, Decimal)
        Private m_total As Decimal = 0D
        
        Public Sub New(ByVal dictionary As IDictionary(Of Integer, Decimal))
            Me.innerDictionary = dictionary
        End Sub
        
        Public ReadOnly Property Total() As [Decimal]
            Get
                Return Me.m_total
            End Get
        End Property
        
        Public Sub Add(ByVal key As Integer, ByVal value As Decimal)
            Me.innerDictionary.Add(key, value)
            m_total += value
        End Sub
        
        Public Sub Remove(ByVal key As String)
            Dim toRemove As Decimal = Me.innerDictionary(key)
            Me.innerDictionary.Remove(key)
            Me.m_total -= toRemove
        End Sub
        
        Public Sub Update(ByVal key As Integer, ByVal newValue As Decimal)
            Dim oldValue As Decimal = Me.innerDictionary(key)
            Me.innerDictionary(key) = newValue
            Me.m_total -= oldValue
            Me.m_total += newValue
        End Sub
        
        Other methods..

    End Class
0 голосов
/ 06 августа 2009

Ваша другая альтернатива использует цикл for, вам не нужно было бы создавать перечислитель, но в любом случае практически нет существенного повышения (насколько я знаю).

Почему вы не можете кэшировать результат? Если вы кешируете общее количество, и для каждого изменения одного из ваших элементов измените количество, используя то же значение (и в конечном итоге вычислите все 10 модификаций для синхронизации). Поэтому, когда один из элементов изменяется с 2,5 на 3,6, вы просто добавляете 1,1 к общей сумме. Почему это не сработает в вашем случае?

0 голосов
/ 06 августа 2009

Это примерно так же быстро, как это произойдет, учитывая тот факт, что вы используете List(Of T).

Другие предложили использовать метод расширения Sum - это не повысит производительность, поскольку его реализация идентична текущему решению проблемы:

<Extension> _
Public Shared Function Sum(ByVal source As IEnumerable(Of Decimal)) As Decimal
    If (source Is Nothing) Then
        Throw Error.ArgumentNull("source")
    End If
    Dim num As Decimal = 0
    Dim num2 As Decimal
    For Each num2 In source
        num = (num + num2)
    Next
    Return num
End Function
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...