2009-08-06 4 views
2

ich eine Methode zu summieren, die eine Sammlung von Dezimalzahlen fasst ein Verfahren ähnlich wie dies mit ....Net - schnellste Weg, um eine Sammlung von numerischen Werten

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 

Im eigentlichen Code gibt es in der Regel mehr Mitglieder in der Betrags-Sammlung, und es gibt mindestens 50 individuelle Betrags-Sammlungen, die in einer Gesamtoperation summiert werden müssen.

Diese Neuberechnung der Gesamtmenge wird oft aufgerufen (mindestens einmal und dann erneut für jede Inhaltsänderung der Sammlung) und wird in Profilkurven als konsumierend zwischen 2-5% der Gesamtbetriebszeit angezeigt. Ich schaue, ob irgendjemand eine Idee hat, wie man diese Summenoperation beschleunigen kann, oder ob dies nur die schnellste ist, die es bekommen wird.

Caching ist in diesem Fall nicht realistisch, da die Werte neu berechnet werden müssen.

**** EDIT ** Für Ravadre und Joel - Der Gesamtbetrag auf Klassenebene gespeichert (jede Menge Sammlung und Summe werden in einer Klasseninstanz enthalten)

Irgendwelche Ideen?

Antwort

3

Probieren Sie die Unterklasse List (Of Decimal) aus, setzen Sie eine TotalSum-Eigenschaft frei. Jedesmal, wenn Add() aufgerufen wird, wird um den eingegebenen Wert erhöht. Das wird Sie davon abhalten, durch die Liste zu iterieren, um die Summe zu erhalten.

Edit: Nach dem Versuch wäre es besser, IList (Of Decimal) zu implementieren, da Lists Methoden nicht virtuell sind. Etwas nach dem Vorbild dieser (Ja, ich weiß, es ist 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; } 
    } 
} 
+0

Implementiert etwas ähnliches in dieser Richtung und sah einige Verbesserungen. Vielen Dank! – StingyJack

0

Das ist ungefähr so ​​schnell, wie es die Tatsache erhalten wird, dass Sie eine verwenden.

Andere haben vorgeschlagen, dass Sie die Sum Erweiterungsmethode verwenden - dies wird nicht Leistung zu verbessern, da ihre Umsetzung Ihrer aktuellen Lösung für das Problem ist identisch:

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

Das Beispiel ist vereinfacht, es ist eigentlich ein Dictionary (Integer, Dezimal) und die Dezimalzahlen werden summiert. Würde das einen Unterschied machen? Warum? – StingyJack

1

Was totalAmount = amountCollection.Sum()?

+0

Überprüfen Sie jetzt, ob a) es schneller ist, und b) was die IL-Unterschiede sind. Vielen Dank! – StingyJack

+1

-1 Dies verbessert die Performance nicht, da die Implementierung von 'Sum' fast identisch ist mit dem, was der OP bereits geschrieben hat. Bitte sehen Sie meine Antwort. –

+2

Unabhängig davon, ob es schneller ist, ist die Verwendung von Sum() sauberer Code. –

0

Ihre andere Alternative ist die Verwendung von for-Schleife, Sie müssten keinen Enumerator erstellen, aber praktisch gibt es keinen signifikanten Boost (soweit ich weiß).

Warum können Sie das Ergebnis nicht zwischenspeichern? Wenn Sie den Gesamtbetrag zwischenspeichern und für jede Änderung eines Ihrer Elemente die Menge mit demselben Wert ändern (und schließlich alle 10 Änderungen für die Synchronisation berechnen). Wenn also eines der Elemente von 2.5 auf 3.6 geändert wird, addieren Sie einfach 1.1 zu Ihrer Gesamtmenge. Warum wird das in deinem Fall nicht funktionieren?

+0

aktualisiert OP mit mehr Info – StingyJack

+1

Ich sehe kein Problem um ehrlich zu sein. Sie könnten Ihre Liste umbrechen, so dass Sie jedes Mal ein Ereignis auslösen, wenn Sie versuchen, etwas zu ändern, oder es jedes Mal erhöhen, wenn jemand feststellt, dass jemand es verändert, dann registrieren Sie sich zu diesem Ereignis (wo es Ihnen passt) und modifizieren Sie es Gesamtmenge. Dies ist nur eine Erklärung, oder ich sehe (noch) nicht das ganze Problem? –

+0

Oh, und wenn es möglich ist, versuchen Sie es mit doppelten anstelle von Dezimalstellen (möglicherweise nicht möglich für das, was Sie tun), sie sind deutlich schneller. –

2

Es ist mir nicht klar, wie Sie eine Liste von Zahlen schneller addieren, als jede Reihe nacheinander hinzuzufügen!

jedoch mit decimal s Arithmetik zu tun, ist wahrscheinlich viel, viel, viel langsamer als ein Gleitkommatyp verwenden, wenn Sie also die Präzision nicht wirklich tun, schlage ich vor Umschaltung.

+0

seine Währung Beträge, so etwa 5 Plätze ist alles, was ich brauche, aber die eingehenden Werte und die Vergleichswerte sind alle Dezimalzahlen, so gibt es eine wahrscheinlich eine deutliche Boxen Strafe für die Verwendung von Float nur für diese Implementierung. – StingyJack

+1

@StingyJack - Bitte denken Sie nicht einmal daran, den Dezimalwert in eine binäre Fließkommazahl zu ändern, wenn Sie mit der Währung arbeiten! –

+0

@StingyJack - was für 'signifikante Boxstrafe'? – Marc

1

Es gibt mindestens 50 individuelle Mengensammlungen, die in einem Gesamtvorgang summiert werden müssen.

Wie wäre es mit der Zwischenspeicherung der Summen aus jeder einzelnen Sammlung? Wenn sich eine Sammlung ändert, müssen Sie nur diese Sammlung und die zwischengespeicherten Summen zusammenfassen.

+0

aktualisiert OP mit mehr Info – StingyJack

+0

Das ist im Grunde, was ich tue. – StingyJack

1

2-5% des gesamten Profilspur Es ist einfach nicht so viel.

Auch wenn Sie die Ausführungszeit dieser Methode halbieren, haben Sie höchstens ein paar Prozent Ihrer gesamten Laufzeit gespeichert.

Ihre beste Wette ist wahrscheinlich, zuerst woanders zu suchen.

+1

Den Leistungskrieg 5% auf einmal gewinnen ...http://blogs.msdn.com/ricom/archive/2005/10/17/481999.aspx – StingyJack

0

Wenn Sie nicht über eine Version von IList/IDictionary implementieren möchten als statenjason vorgeschlagen, dass Sie einen Wrapper erstellen konnte:

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 
2

Wenn diese Währungswerte sind, sollten skaliert ganze Zahlen verwenden. Für 2 Dezimalstellen speichern Sie die Anzahl der Pennies anstelle der Anzahl der Dollar und teilen Sie dann durch 100, wenn Sie das Ergebnis ausgeben müssen. Für 5 Dezimalstellen verwenden Ihre Variablen intern die ursprüngliche Zahl * 100000. Dies sollte sowohl die Leistung als auch die Genauigkeit verbessern, besonders wenn Sie eine Division durchführen.

+0

Wir haben das als eine Idee herumwerfen, und meine Kohorte versucht es, um die Division/Rundung zu vereinfachen.
In dem Beispiel habe ich oben, dass mindestens 3 Multiplikationen hinzugefügt (um die addierten Dezimalstellen in Ints konvertieren) und eine Division (um das Ergebnis zurück in eine Dezimalzahl konvertieren). Ich denke nicht, dass das in diesem Fall eine Verbesserung der Geschwindigkeit wäre, aber wir versuchen es. – StingyJack

+1

Nein, der Punkt ist nicht, sie vor und zurück zu konvertieren, sondern sie als skalierte ganze Zahlen zu speichern und niemals Dezimalzahlen zu berühren! – mquander

+1

Mquander hat es - Sie speichern, sagen wir, ganze Pfennige. während Ihrer Berechnungen runden Sie den nächsten Cent ab, wenn Sie teilen müssen. Wenn Sie Bericht erstatten, teilen Sie sich durch 100 und runden den nächsten Cent ab. – Beth