Richtige Collection für performante Abfrage

  • WPF

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Richtige Collection für performante Abfrage

    Hallo liebe Leute,
    ich hab folgende Ausgangssituation.
    Eine Klasse mit zwei String Propertys (Welche quasi den Key darstellen sollen) und 3 weitere Integerwerte.
    Zudem gibt es eine ObservableCollection der eigenen Klasse.
    Nach dem Anlegen eines Objektes vom Typ dieser Klasse, wird sie der ObservableCollection hinzugefügt.

    Eine Shared Funktion soll mir nun das Objekt mit den entsprechenden Keys wiedergeben.
    Quellcodetechnisch sieht das so aus:

    VB.NET-Quellcode

    1. Public Class Auftragslager
    2. Public Shared Auftragsliste as new ObservableCollection(of Auftragslager)
    3. Private _Auftragsnummer As String
    4. Public Property Auftragsnummer As String
    5. Get
    6. Return _Auftragsnummer
    7. End Get
    8. Set(ByVal value As String)
    9. _Auftragsnummer = value
    10. End Set
    11. End Property
    12. Private _Komponente As String
    13. Public Property Komponente As String
    14. Get
    15. Return _Komponente
    16. End Get
    17. Set(ByVal value As String)
    18. _Komponente = value
    19. End Set
    20. End Property
    21. Private _Ausgegeben As Integer
    22. Public Property Ausgegeben() As Integer
    23. Get
    24. Return _Ausgegeben
    25. End Get
    26. Set(ByVal value As Integer)
    27. _Ausgegeben = value
    28. End Set
    29. End Property
    30. Private _Auszugeben As Integer
    31. Public Property Auszugeben() As Integer
    32. Get
    33. Return _Auszugeben
    34. End Get
    35. Set(ByVal value As Integer)
    36. _Auszugeben = value
    37. End Set
    38. End Property
    39. Private _Verbraucht As Integer
    40. Public Property Verbraucht() As Integer
    41. Get
    42. Return _Verbraucht
    43. End Get
    44. Set(ByVal value As Integer)
    45. _Verbraucht = value
    46. End Set
    47. End Property
    48. Public Sub New(auftragsnummer As String, komponente As String, ausgegeben As Integer, auszugeben As Integer, verbraucht As Integer)
    49. _Auftragsnummer = auftragsnummer
    50. _Komponente = komponente
    51. _Ausgegeben = ausgegeben
    52. _Auszugeben = auszugeben
    53. _Verbraucht = verbraucht
    54. Auftragsliste .Add(Me)
    55. End Sub
    56. Public Shared Function GetAusgegeben(ByVal auftragsnummer As String, ByVal komponente As String) As Integer
    57. Dim val As integer = Nothing
    58. For i As Integer =0 To list.Count-1
    59. If list(i).Auftragsnummer=auftragsnummer And list(i).Komponente=komponente
    60. val=list(i).Ausgegeben
    61. End If
    62. Next
    63. ' val = (From p In list
    64. ' Where p.Komponente = komponente And p.Auftragsnummer = auftragsnummer
    65. ' Select p).FirstOrDefault()
    66. If Not val = Nothing
    67. Return val
    68. 'return val.Ausgegeben
    69. Else
    70. Return 0
    71. End If
    72. End Function
    73. End Class


    Das Problem ist, welche Collection (Anstelle der Observable) sollte ich nehmen und wie ermittle ich am besten meinen Datensatz? Ich hatte das zuvor mit LINQ gemacht, da habe ich aber leider eine nicht so gute Performance.

    Wie würdet ihr das lösen?
    Danke euch!
    Hallo

    Mal abgesehen davon das der Code vermutlich so nicht lauffähig ist. (was ist list in Zeile 64 ?) Ist nirgens deklariert.

    Da Observablecollection IEnumerable implementiert dürfte es doch gar nicht so langsam sein.
    Falls du aber keinerlei Benachrichtigungsmechanismus in deinen Views benötigst musst du ja keine Observable nehmen.

    Aber ist folgendes zu unperformant?

    VB.NET-Quellcode

    1. Public Shared Function GetAusgegeben(ByVal auftragsnummer As String, ByVal komponente As String) As Integer
    2. Return (From p In Auftragsliste Where p.Komponente = komponente AndAlso p.Auftragsnummer = auftragsnummer
    3. Select p.Ausgegeben).FirstOrDefault()
    4. End Function


    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Nofear23m“ ()

    Dieses Shared zeug ist erstmal Böse. Warum brauchst du das? Eigt. sollte es doch möglich sein, dass du ganz einfach hin gehst und die Liste da instanzierst wo du sie benötigst(und auch dort dein Hilfsfunktion erstellst...).
    Der nächste Punkt ist, dass Zeile 72-77 ganz einfach dem hier entspricht:

    VB.NET-Quellcode

    1. Return val

    Integer ist ein ValueType und in VB.Net wird bei Zuweisung von Nothing diesen ValueTypes einfach der Standardwert zugewiesen, im Falle von Integer ist das ganz einfach 0.
    D.h. auf diese Art kannst du auch nicht unterscheiden zwischen gefunden und nicht gefunden wenn die Anzahl 0 ist, auch wenn das für deinen Fall hier irrelevant ist.

    Zusätzlich gehst du die Liste jedesmal komplett durch, wobei es doch vollkommend ausreichend wäre beim ersten mal finden bereits das Ergebnis zurückzugeben:

    VB.NET-Quellcode

    1. //Zeile 66
    2. Return list(i).Ausgegeben
    3. //Zeile 69
    4. Return 0

    Dadurch ist die benötigte Suchzeit nicht mehr konstant, sondern braucht nur Halb so lang, wenn sich das Element in der Mitte befindet.

    Der nächste Punkt ist, brauchst du und benutzt die die ObservableCollection auch tatsächlich? Wenn ja, dann wäre das hier vlt. was:
    gist.github.com/kzu/cfe3cb6e4fe3efea6d24

    Und für Auftragsnummer und Komponente legst du dann eine struct an, welche AuftragslagerKey oder so heißt. Diese sollte GetHashCode implementieren und einen sinnvollen HashCode auf basis dieser zwei Properties implementieren. Diese struct wird in dem Dictionary als Key verwendet. Dadurch kannst du dann eben auch über TryGetValue über den Key nach der Value suchen und das ganze durch das Dictionary eben halbwegs performant...

    Edit: Link hat nicht funktioniert
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Hallo,
    Funktioniert das von der Logik so? Es wird bei dem ersten Item mit Index = 0 und bei nicht gefunden jeweils 0 zurückgegeben?

    Falls du bei Integer bleiben möchtest und unbedingt nothing zurückgeben möchtest musst du ein Nullable nutzen:
    Dim vall As Nullable(Of Integer) oder Dim val As Integer?. Hier ist der Initialwert nothing

    Ist es richtig, dass die Auftragsnummer ein String ist?
    Ich gehe davon aus, dass die Auftragsnummer einzigartig ist, deswegen würde sich uU. ein ObservableDictionary lohnen mit der Auftragsnummer als Key, welches man leider selbst bauen müsste wenn die Reihenfolge der Items egal ist. Hier ist es mal ein wenig dargestellt:
    stackoverflow.com/questions/5663395/net-observabledictionary

    Falls nicht, die If- Abfrage mit mehreren Argumenten mit AndAlso verknüpfen, dann wird immer nur das erste Argument geprüft und nur wenn das passt das zweite Argument ausgewertet. Das bringt wenigstens schon mal ein bischen was.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „SKeks“ ()

    Hallo Leute,
    erst mal vielen Dank das ihr euch meldet!
    Also in Zeile 64 dieses list war der alte Name der Observable, habe ich geändert, damit der Name auch dem Kontext entspricht.
    Auftragsnummer und Komponente bilden zusammen einen Key und beide Properties sind ein String, auch die Auftragsnummer.

    Ich habe jetzt mal den Ansatz von Sascha implementiert:

    VB.NET-Quellcode

    1. Return (From p In list Where p.Komponente=komponente AndAlso p.Auftragsnummer=auftragsnummer
    2. Select p).FirstOrDefault()


    Es muss grundsätzlich keine ObservableCollection sein, ich nutzte an der Stelle keine INotifyPropertyChanged Schnittstelle, in anderen Bereichen schon.
    Gibt es vielleicht ein zwei Beispiele für eine ObservableDictionary, dass ich mir das mal anschauen könnte?

    Schwakowiak schrieb:

    Ich habe jetzt mal den Ansatz von Sascha implementiert

    Das freut mich. Und? Schnell genug oder nicht? Ist dir das etwa zu langsam? Wieviele Einträge sind da bitte drinnen?

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.
    Hallo nochmal @Schwakowiak

    Da muss ich jetzt leider nochmals nachfragen. Ich habe gerade einen test gemacht, dabei ist es mit meiner Variante per Linq unerheblich ob 70000 oder 5 Millionen Einträge habe.
    Im Kaltstart dauert es ~4 ms und danach nur noch ~2 ms. Das ist dir zu unperformant?
    Ehrlich. 2 Millisekunden dauert dir zu lange. Was schreibst du da für ne App? Wo willst du hin, in den Nanosekundenbereich?

    Anbei ein Screenshot und mein Test:

    VB.NET-Quellcode

    1. Sub Main()
    2. Console.WriteLine($"Schreibe Werte")
    3. For i As Integer = 1 To 5000000
    4. Dim entry = New Auftragslager($"Auftrag {i}", $"Komponente {i}", i, i, i)
    5. Next
    6. Console.WriteLine($"Werte geschrieben - Taste drücken für start!")
    7. Console.ReadKey()
    8. For o As Integer = 0 To 2
    9. Dim sw As New Stopwatch()
    10. sw.Start()
    11. Dim result = Auftragslager.GetAusgegeben("Auftrag 50000", "Komponente 50000")
    12. sw.Stop()
    13. Console.WriteLine($"Dauer in ms: {sw.ElapsedMilliseconds}")
    14. Console.WriteLine($"Ergebnis: {result.ToString}")
    15. Next
    16. Console.ReadKey()
    17. End Sub


    Grüße
    Sascha
    Bilder
    • Console.PNG

      4,56 kB, 361×133, 4 mal angesehen
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.
    Du holst dir ja auch nur das 50.000te Element, da ist klar, das die größe der Liste unerheblich ist, und du auch 2^31 Elemente haben könntest und es wäre immernoch genauso schnell..

    Wenn es nicht Observable sein muss, dann kannst auch direkt ein normales Dictionary verwenden...
    codereview.stackexchange.com/q…tuple-myclass/37689#37689
    Hier ist eines der wenigen Codebeispiele die ich direkt für VB.Net gefunden habe, ansonsten musst nach C# gucken....

    Das GetHashCode würde ich anders machen und dabei den ReSharper Ansatz verfolgen:

    VB.NET-Quellcode

    1. Public Overrides Function GetHashCode() As Integer
    2. Return (Store_Name.GetHashCode() * 397) ^ Location.GetHashCode()
    3. End Function

    Natürlich musst du das Codebeispiel, ebenso wie meinen Part hier für dein Beispiel anpassen.
    Und anstelle von ContainsKey ist auch TryGetValue wesentlich sinnvoller. Das Internet ist voll davon wie man das benutzt am einfachsten bei MSDN gucken...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Ja, dem TE ging es um ca. 70.000 Elemente. Aber selbst wenn ich bei 5 Mio. Elementen das letzte Element hole sind wir bei der dauer eines Wimpernschlags von. Ich meine 200 ms bei 5 Mio Einträgen wo ich den letztn benötige, naja.

    Aber ja, wenn ihm das zu langsam ist, ist das ok für mich, ich denke mir nur das man sowas eher vernachlässigen kann.
    Bin gespannt auf welches Ergebniss Ihr hier im Endeffekt kommt. Ich werde es verfolgen.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.
    Was bei vollem Array auf ~85s hinausläuft. Abgesehen davon, dass man Geschwindigkeit von soetwas misst indem man es mehrmals macht und dann den Durchschnitt berechnet, denn 2ms ist zu kurz um eine genau Aussage darüber treffen zu können...
    Und alles was länger als 50ms dauert muss btw laut MSDN(wenn ich nur die Quelle wieder finden würde) in einen Thread, was "bereits" bei ~1,4M passiert.

    Aufjedenfall ist soetwas immer sinnvoll über ein Dictionary zu lösen, schließlich wurde das Dictionary genau für solche Fälle geschaffen. Und wenn man dadurch noch eine Zeit von O(log n) anstelle von O(n) hat umso besser...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Gebe ich dir vollkommen recht. Ich würde es auch in einen Thread packen. Bin aber gespannt was mit dem Dictionary rauskommt.
    Bin jetzt ehrlich gesagt zu faul das zu testen, bin aber gespannt. Gerne lerne ich hier was neues. Habe mich mit solchen Dingen bisher wenig beschäftigt da ich solch große Listen noch nie hatte weil ich meist mit DB arbeite und da rufe ich erst nur das ab was ich benötige. Bin aber gespannt 8o

    Grüße
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.
    Habs getestet komme bei 50000 auf ~0,4ms, mehr kann ich nicht testen, da ich es über dotnetfiddle.net/ gemacht hab und es bei mehr memory probleme gibt^^
    Dürfte aber trotzdem relativ gut skalierbar bleiben...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Da wäre interessant das auf einer "normalen" Maschine laufen zu lassen. Da wäre ich gespannt auf den unterschied bei den 5 Mio.
    Aber 0.4 ms ist schon mal um einiges schneller!

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    Schwakowiak schrieb:

    in der Liste sind ca ~72000 Elemente enthalten. Performanter ist das leider nicht
    Wie misst du das denn, wie lange dauerts, und wie lange darfs dauern?


    Übrigens, könnte mir vorstellen, unklare Fragestellung.
    Das wirkliche Problem mag sein, > 1000 Elemente in einem DGV anzuzeigen.