Laden...

Filtern großer Listen auf Basis von String-Vergleichen

Erstellt von inflames2k vor 6 Jahren Letzter Beitrag vor 6 Jahren 2.179 Views
inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 6 Jahren
Filtern großer Listen auf Basis von String-Vergleichen

Hallo,

zur Auswertung von Log-Einträgen (SQL-Logs, Windows Ereignisprotokolle) verwenden wir intern ein Programm, welches die Log-Einträge einliest.

Speziell bei SQL-Logs aber auch im Windows-Ereignisprotokoll kann es vorkommen, dass mit Datenmengen über 30000 Einträgen zu rechnen ist.

Nun haben wir eine Filterfunktion ähnlich der aus dem Process Monitor von Sysinternals erstellt. Damit kann auf einzelne Property-Werte der Log-Einträge gefiltert werden. Nun ist es jedoch so, dass speziell bei String-Werten die Filterung sehr lange dauert. Meine Frage ist nun, ob ihr eine Idee habt wie die Filterung beschleunigt werden kann.

Grundsätzlich wird eine Reihe von Filtern nacheinander angewendet. Ein Filtereintrag sieht wie folgt aus:


public class FilterEntry<TEventType> : INotifyPropertyChanged
    {
        private bool _active;

        public bool Active
        {
            get { return _active; }
            set {
                if (_active == value)
                    return;
                _active = value;
                this.OnPropertyChanged("Active");
            }
        }        

        /// <summary>
        /// Gets or sets the filter direction
        /// </summary>
        public FilterDirection FilterDirection { get; set; }

        /// <summary>
        /// Gets or sets the image index
        /// </summary>
        public Int32 ImageIndex
        {
            get
            {
                switch(this.FilterDirection)
                {
                    case FilterDirection.Exclude:
                        return 1;
                    case FilterDirection.Include:
                        return 0;
                }

                return -1;
            }
        }

        public FilterColumnType FilterColumn { get; set; }

        public string ColumnName
        {
            get
            {
                switch(this.FilterColumn)
                {
                    case FilterColumnType.EventID:
                        return "Ereignis-ID";
                    case FilterColumnType.Message:
                        return "Meldung";
                    case FilterColumnType.Source:
                        return "Quelle";
                    case FilterColumnType.Category:
                        return "Kategorie";
                }

                return string.Empty;
            }
        }

        public FilterCondition FilterCondition { get; set; }

        public string ConditionName
        {
            get
            {
                switch(this.FilterCondition)
                {
                    case Toolkit.FilterCondition.Contains:
                        return "enthält";
                    case Toolkit.FilterCondition.ContainsNot:
                        return "enthält nicht";
                    case Toolkit.FilterCondition.EndsWitch:
                        return "endet mit";
                    case Toolkit.FilterCondition.Equal:
                        return "gleich";
                    case Toolkit.FilterCondition.GreaterThan:
                        return "größer als";
                    case Toolkit.FilterCondition.LessThan:
                        return "kleiner als";
                    case Toolkit.FilterCondition.NotEqual:
                        return "ungleich";
                    case Toolkit.FilterCondition.StartsWith:
                        return "beginnt mit";
                }
                return string.Empty;
            }
        }

        public string ConditionValue { get; set; }

        public bool Match(ILogEntry<TEventType> entry)
        {
            bool matches = false;

            object sourceValue = this.GetSourceValue(entry);
            if (sourceValue is string)
            {
                // match string 
                string value = sourceValue.ToString();
                matches = this.MatchString(value);
            }
            else
            {
                // match number 
                Int32 value = (Int32)sourceValue;
                matches = this.MatchNumber(value);
            }

            return matches;
        }

        private bool MatchString(string value)
        {
            bool matches = false;

            switch(this.FilterCondition)
            {
                case FilterCondition.Contains:
                    matches = value.Contains(this.ConditionValue);
                    break;
                case FilterCondition.ContainsNot:
                    matches = !value.Contains(this.ConditionValue);
                    break;
                case FilterCondition.EndsWitch:
                    matches = value.EndsWith(this.ConditionValue);
                    break;
                case FilterCondition.Equal:
                    matches = value.Equals(this.ConditionValue);
                    break;
                case FilterCondition.GreaterThan:
                    matches = value.CompareTo(this.ConditionValue) > 0;
                    break;
                case FilterCondition.LessThan:
                    matches = value.CompareTo(this.ConditionValue) < 0;
                    break;
                case FilterCondition.NotEqual:
                    matches = !value.Equals(this.ConditionValue);
                    break;
                case FilterCondition.StartsWith:
                    matches = value.StartsWith(this.ConditionValue);
                    break;
            }

            return matches;
        }

        private bool MatchNumber(Int32 value)
        {
            bool matches = false;

            Int32 conditionValue = -1;
            if (Int32.TryParse(this.ConditionValue, out conditionValue))
            {
                switch (this.FilterCondition)
                {
                    case FilterCondition.Contains:
                        matches = value.ToString().Contains(this.ConditionValue);
                        break;
                    case FilterCondition.ContainsNot:
                        matches = !value.ToString().Contains(this.ConditionValue);
                        break;
                    case FilterCondition.Equal:
                        matches = value.Equals(conditionValue);
                        break;
                    case FilterCondition.GreaterThan:
                        matches = value > conditionValue;
                        break;
                    case FilterCondition.LessThan:
                        matches = value < conditionValue;
                        break;
                    case FilterCondition.NotEqual:
                        matches = value != conditionValue;
                        break;
                    case FilterCondition.StartsWith:
                        matches = value.ToString().StartsWith(this.ConditionValue);
                        break;
                    case FilterCondition.EndsWitch:
                        matches = value.ToString().EndsWith(this.ConditionValue);
                        break;
                }
            }

            return matches;
        }

        private object GetSourceValue(ILogEntry<TEventType> entry)
        {
            object sourceValue = string.Empty;
            switch (this.FilterColumn)
            {
                case FilterColumnType.EventID:
                    sourceValue = entry.EventID;
                    break;
                case FilterColumnType.Message:
                    sourceValue = entry.Message;
                    break;
                case FilterColumnType.Source:
                    sourceValue = entry.Source;
                    break;
                case FilterColumnType.Category:
                    sourceValue = entry.Category;
                    break;
            }

            return sourceValue;
        }

Die Filter sind in einer übergeordneten Klasse zusammengefasst, in der diese ausgewertet werden. Diese sieht wie folgt aus:


public class LogFilter<TEventType> : IDataFilter<ILogEntry<TEventType>>
    {
        public LogFilter()
        {
            this.Entries = new BindingList<FilterEntry<TEventType>>();
        }

        private bool _bSorting = false;
        public bool Sorting
        {
            get { return _bSorting; }
        }
       
        public BindingList<FilterEntry<TEventType>> Entries { get; set; }

        /// <summary>
        /// Method to append sorted entries
        /// </summary>
        /// <param name="filterEntries">the filter entries</param>
        private void AppendSortedFilterEntries(List<FilterEntry<TEventType>> filterEntries)
        {
            // append entries 
            foreach (FilterEntry<TEventType> entry in filterEntries)
            {
                this.Entries.Add(entry);
            }
        }

        /// <summary>
        /// Method to check if the event log entry matches a filter
        /// </summary>
        /// <param name="dataItem">the event log entry</param>
        /// <returns>true if the entry matches a filter and should be shown</returns>
        /// <remarks>
        /// Matching will be done in the following order:
        /// 1) Match Source filters
        /// 2) Match eventID filters
        /// 3) Match message filters
        /// </remarks>
        public bool Matches(ILogEntry<TEventType> dataItem)
        {
            // match source filters, ID Filters and message filters
            return this.MatchSourceFilters(dataItem) && this.MatchEventIDFilters(dataItem) && this.MatchMessageFilters(dataItem) && this.MatchCategoryFilters(dataItem);
        }

        /// <summary>
        /// Method to match filters
        /// </summary>
        /// <param name="dataItem">the event log entry</param>
        /// <param name="direction">the filter direction</param>
        /// <param name="column">the filter column</param>
        /// <returns>true if the event log entry matches a filter</returns>
        private bool MatchFilters(ILogEntry<TEventType> dataItem, FilterDirection direction, FilterColumnType column)
        {
            bool matches = false;
            IEnumerable<FilterEntry<TEventType>> entries = this.Entries.Where((x) => x.FilterColumn == column && x.FilterDirection == direction && x.Active);

            if (entries.Count() == 0)
                matches = direction == FilterDirection.Include ? true : false;

            foreach (FilterEntry<TEventType> entry in entries)
            {
               // oder verknüpfung
               matches = matches || entry.Match(dataItem);   
            }

            return matches;
        }

        /// <summary>
        /// Method to match event id filters
        /// </summary>
        /// <param name="dataItem">the event log entry</param>
        /// <returns>true if the entry matches a filter and should be shown</returns>
        private bool MatchEventIDFilters(ILogEntry<TEventType> dataItem)
        {
            // values to show must be in include filter but not in exclude filter
            return this.MatchFilters(dataItem, FilterDirection.Include, FilterColumnType.EventID) && !this.MatchFilters(dataItem, FilterDirection.Exclude, FilterColumnType.EventID);
        }

        /// <summary>
        /// Method to match event id filters
        /// </summary>
        /// <param name="dataItem">the event log entry</param>
        /// <returns>true if the entry matches a filter and should be shown</returns>
        private bool MatchCategoryFilters(ILogEntry<TEventType> dataItem)
        {
            // values to show must be in include filter but not in exclude filter
            return this.MatchFilters(dataItem, FilterDirection.Include, FilterColumnType.Category) && !this.MatchFilters(dataItem, FilterDirection.Exclude, FilterColumnType.Category);
        }

        /// <summary>
        /// Method to match event source filters
        /// </summary>
        /// <param name="dataItem">the event log entry</param>
        /// <returns>true if the entry matches a filter and should be shown</returns>
        private bool MatchSourceFilters(ILogEntry<TEventType> dataItem)
        {
            // values to show must be in include filter but not in exclude filter
            return this.MatchFilters(dataItem, FilterDirection.Include, FilterColumnType.Source) && !this.MatchFilters(dataItem, FilterDirection.Exclude, FilterColumnType.Source);
        }

        /// <summary>
        /// Method to match the message filters
        /// </summary>
        /// <param name="dataItem">the event log entry</param>
        /// <returns>true if the entry matches a filter and should be shown</returns>
        private bool MatchMessageFilters(ILogEntry<TEventType> dataItem)
        {
            // values to show must be in include filter but not in exclude filter
            return this.MatchFilters(dataItem, FilterDirection.Include, FilterColumnType.Message) && !this.MatchFilters(dataItem, FilterDirection.Exclude, FilterColumnType.Message);
        }

        /// <summary>
        /// Method to reset the filters
        /// </summary>
        public void Reset()
        {
            // clear all filter entries
            this.Entries.Clear();
        }
    }

Dem LogFilter wird anschließend noch eine Klasse übergestülpt die noch weitere Filter hält. Diese sieht wie folgt aus:


public class SQLLogFilter
    {
        public SQLLogFilter()
        {
            this.Filters = new Dictionary<string, IDataFilter<ILogEntry<SQLLogEntryType>>>();            
            this.Filters.Add("EventTimeFilter", new EventTimeFilter<SQLLogEntryType>(ApplicationSettings.SQLLogHistoryMonths));
            this.Filters.Add("EventFilter", new LogFilter<SQLLogEntryType>());
        }

        public Dictionary<string, IDataFilter<ILogEntry<SQLLogEntryType>>> Filters { get; set; }

        public IEnumerable<FilterColumn> FilterColumns
        {
            get
            {
                yield return this.CreateFilterColumnData("Quelle", FilterColumnType.Source);
                yield return this.CreateFilterColumnData("Meldung", FilterColumnType.Message);
            }
        }

        private FilterColumn CreateFilterColumnData(string displayName, FilterColumnType columnType)
        {
            FilterColumn column = new FilterColumn();
            column.DisplayName = displayName;
            column.Column = columnType;
            return column;
        }

        /// <summary>
        /// Method to match the details to the filters
        /// </summary>
        /// <param name="details">the details</param>
        /// <returns>true if the details matches</returns>
        public bool Matches(SQLLogEntry details)
        {
            bool matches = true;

            // match time
            matches = this.Filters["EventTimeFilter"].Matches(details) &&                      
                      this.Filters["EventFilter"].Matches(details);

            return matches;
        }

        public void Reset()
        {
            foreach (string filterName in this.Filters.Keys)
            {
                this.Filters[filterName].Reset();
            }
        }
    }

Mein Aufruf zur Filterung ist anschließend:


foreach (SQLLogEntry entry in this.Model.LogEntries)
{
   if (filter.Matches(entry))
   {
      this.FilteredList.Add(entry);
    }
}

Ich weiß, es ist ein bischen viel Code für das Forum. Aber um das ganze Ausmaß zu erkennen aus meiner Sicht notwendig.

Mein Problem ist nun, fange ich an nach einem Textwert zu filtern, dauert dies bei 30k Einträgen sehr lang (auch dann, wenn nur ein FilterEntry festgelegt wurde).

Hat jemand von euch eine Idee, wie ich die Filterung performanter durchführen kann, ohne großartig den aktuellen Aufbau zu verlieren?

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

P
1.090 Beiträge seit 2011
vor 6 Jahren

Hallo inflames2k,

wenn ich das richtig sehe prüfst du bei jedem der 30.000 Aufruf, was wie gefiltert wird. Also ob es jetzt z.B. ein String ist der auf Contains geprüft wird.

Und wenn ich es richtig Verstanden habe, ändert das sich ja nicht.

Grundlegend kannst du die Entscheiungen 1 mal vor den 30.000 Aufrufen treffen. Z.B. mit dem Factory Pattern welche dir dann einen Passenden Filter zurück liefert.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

6.911 Beiträge seit 2009
vor 6 Jahren

Hallo inflames2k,

und erweiternd zu Palins Vorschlag kannst du gem. "teile und herrsche" die Eingabemenge aufteilen und jeden Teil parallel filtern und dann zu einem Gesamtergebnis zusammenbauen. Z.B. durch PLinq.

Wenn mit der gleichen Eingabemenge mehrere "Abfragen" -- hier nicht mehrere Filter hintereinander, sondern User macht Filterung A und später Filterung B -- so ist es sicherlich vorteilhaft einen "Index" über die Eingabemenge zu erstellen und dann auf diesen die Filteroperationen durchzuführen. Also dass die Aufwandsklasse geringer als O(n) wird.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 6 Jahren

Grundsätzlich stimme ich euch zu.

Allerdings führt ja der Aufruf von filter.Matches(entry) in meinen SQLLogFilter, also in die Methode


 /// <summary>
        /// Method to match the details to the filters
        /// </summary>
        /// <param name="details">the details</param>
        /// <returns>true if the details matches</returns>
        public bool Matches(SQLLogEntry details)
        {
            bool matches = true;

            // match time
            matches = this.Filters["EventTimeFilter"].Matches(details) &&                      
                      this.Filters["EventFilter"].Matches(details);

            return matches;
        }

this.Filters["EventFilter"] enthält einen LogFilter. Der Aufruf von Matches führt zu folgender Methode:


        /// <summary>
        /// Method to check if the event log entry matches a filter
        /// </summary>
        /// <param name="dataItem">the event log entry</param>
        /// <returns>true if the entry matches a filter and should be shown</returns>
        /// <remarks>
        /// Matching will be done in the following order:
        /// 1) Match Source filters
        /// 2) Match eventID filters
        /// 3) Match message filters
        /// </remarks>
        public bool Matches(ILogEntry<TEventType> dataItem)
        {
            // match source filters, ID Filters and message filters
            return this.MatchSourceFilters(dataItem) && this.MatchEventIDFilters(dataItem) && this.MatchMessageFilters(dataItem) && this.MatchCategoryFilters(dataItem);
        }

Alle Methodenaufrufe darin führen später dann zu:


 /// <summary>
        /// Method to match filters
        /// </summary>
        /// <param name="dataItem">the event log entry</param>
        /// <param name="direction">the filter direction</param>
        /// <param name="column">the filter column</param>
        /// <returns>true if the event log entry matches a filter</returns>
        private bool MatchFilters(ILogEntry<TEventType> dataItem, FilterDirection direction, FilterColumnType column)
        {
            bool matches = false;
            IEnumerable<FilterEntry<TEventType>> entries = this.Entries.Where((x) => x.FilterColumn == column && x.FilterDirection == direction && x.Active);

            if (entries.Count() == 0)
                matches = direction == FilterDirection.Include ? true : false;

            foreach (FilterEntry<TEventType> entry in entries)
            {
               // oder verknüpfung
               matches = matches || entry.Match(dataItem);   
            }

            return matches;
        }

Wie man sieht werden also möglicherweise auch mal 10 oder 20 Filter angewendet und nicht nur einer. Wie ich mir hier also für die einzelnen Filter merken soll, was zu tun ist müsste man mir also noch einmal erklären. Im groben und ganzen verstehe ich aber auch nicht, was das bringen soll.

Der ENUM-Wert ändert sich während der Filterung nicht. Ich nehme nicht an, dass das Switch jetzt den großen Einfluss hat, eher das Contains, Equals etc.

Mit PLinq werde ich mir mal ansehen wie ich das sauber hinbekomme.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

P
1.090 Beiträge seit 2011
vor 6 Jahren

Erstmal gfoidl Vorschlag ist sicher besser und einfacher Umzusetzen.

Ich denke mal wenn du eine BlockingCollection für die Gefilterten Ergebnisse nimmst, kannst du die Schleife in eine Parallel.ForEach umwandeln. Ist schnell gemacht und wenn dann schnell genug ist hast du dein Problem gelöst.

Zum andern ein Case Select ist jetzt nicht wirklich langsam. Aber wenn ich es nur 1x Aufrufen muss statt 30.000 mal ist es deutlich schneller. Bei z.B. Contains kommst du ja nicht drum herum es für jeden Datensatz aufzurufen.

Wenn du mehrere Filter anwenden willst kannst du sie in eine Liste Verwenden und dann über die Liste laufen. Der 1. Filter der Fals ist ist dann deine Abbruchbedingung. Die Filter kannst du dann gegebenen Falls noch nach Performance Sortieren. Als ich denke der Integer Vergleich ID = 1 ist schneller als ein String.Contains.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

O
79 Beiträge seit 2011
vor 6 Jahren

Üblicherweise ändern sich die Filter während eines (!) Filtervorganges nicht. Ergo schreit das regelrecht nach einer Prepare-Methode für deine Filter, der du die Filterkriterien mitgibst und die z.B. eine List<delegate> zurückgibt.

In dieser List<> sind dann nur noch die Filtermethoden drin, die du tatsächlich benutzen mußt für diesen Filtervorgang. So entscheidet sich genau einmal pro Filterung, welche Filter zu nehmen sind - und nicht im Extremfall 30.000x.

Die einzelnen Filterroutinen auch noch nach Geschwindigkeit zu sortieren und/oder nach Wahrscheinlichkeit eines Treffers (wodurch der Rest nicht mehr abgearbeitet werden muß) ist dann das Sahnehäubchen oben drauf.

Vergiss eine UnPrepare-Methode nicht 😉

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 6 Jahren

Huh,

ich habe das jetzt mal so angepasst, dass jeder Filter sich die auszuführende Operation merkt. Dazu habe ich einen Delegate wie folgt definiert:


delegate bool FilterDelegate(Object value);

Dieser wird beim ersten Aufruf der Methode Match ermittelt und für alle weiteren Aufrufe wiederverwendet. Einzige Änderung die an einem Filter durchgeführt werden kann ist die Aktivierung / Deaktivierung. - Daher ist ein UnPrepare nicht nötig. Ein ändern des Filters führt immer zur Generierung eines neuen Filter-Objektes.

Ein Beispiel der Zuweisung des Delegates für den Filter:


case FilterCondition.Contains:
      filterDelegate = (object value) => value.ToString().Contains(this.ConditionValue);
      break;

Die Match-Methode sieht nun wie folgt aus:


public bool Match(ILogEntry<TEventType> entry)
{
   bool matches = false;

   if (this._filterMethod == null)
         this._filterMethod = this.EvaluateFilterMethod(entry);

  object sourceValue = this.GetSourceValue(entry);
  matches = this._filterMethod(sourceValue);

  return matches;
}

Allerdings ist das filtern dadurch nicht wesentlich schneller gewurden. Der nächste Schritt ist dann wohl tatsächlich das sinnvolle Aufteilen der Datenmenge und Parallele Filtern dieser.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 6 Jahren

Mache ich etwas verkehrt? Folgendes ändert nichts am Laufzeitverhalten:


BlockingCollection<EventLogEntryDetails> filteredCollection = new BlockingCollection<EventLogEntryDetails>();
// aufteilen der eingabemenge
IEnumerable<IEnumerable<EventLogEntryDetails>> chunked =  this.Chunk(info.Entries, 5000);
chunked.AsParallel().ForAll((list) =>
{
    BlockingCollection<EventLogEntryDetails> currentEntries = new BlockingCollection<EventLogEntryDetails>();
    list.AsParallel().ForAll(
                        (entry) =>
                        {
                            if (filter.Matches(entry))
                                currentEntries.Add(entry);
                        });

    foreach (EventLogEntryDetails details in currentEntries)
        filteredCollection.Add(details);
});        

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

4.931 Beiträge seit 2008
vor 6 Jahren

Verstehe ich das richtig, daß du häufig Teilstrings suchst? Dann ist die String.Contains-Methode nicht sehr optimiert - evtl. könntest du da auf andere Suchalgorithmen wie z.B. Knuth-Morris-Pratt-Algorithmus umsteigen (oder auch meine Umsetzung des Boyer-Moore-Horspool Suche (und weitere) benutzen).

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 6 Jahren

Hallo Th69,

werde ich mir mal ansehen. Das Problem tritt aber bei allen String-Operationen auf (Contains, Equals, ...).

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

P
1.090 Beiträge seit 2011
vor 6 Jahren

Ich hatte mir das eher so Vorgestellt

Intrface Deklarieren

public Interface IFilter
{
     bool Match(ILogEntry<TEventType> entry);
     
     int CallOrder; // Ist hier jetzt nur Beiespiel hast angelegt, daran könntest du einmal deine Liste Sortiern damit die schnelleren Prüfungen zuerst statt finden.
}

Die Einzelnen Filter Strategien implementieren.

public class MessageContainsFilter : IFilter
{
          private String _valueToContains;

          public MessageContainsFilter(String valueToContains)
          {
                       _valueToContains = valueToContains;
           }

          bool Match(ILogEntry<TEventType> entry)
          {
                return entry.Message.Contains(valueToContains );
           }
}


public class SourceContainsFilter : IFilter
{
          private String _valueToContains;

          public SourceContainsFilter(String valueToContains)
          {
                       _valueToContains = valueToContains;
           }

          bool Match(ILogEntry<TEventType> entry)
          {
                return entry.Source.Contains(valueToContains );
           }
}

public class IDEqualFilter : IFilter
{
          private int_value;

          public SourceContainsFilter(int value)
          {
                       _value = value;
           }

          bool Match(ILogEntry<TEventType> entry)
          {
               return entry.ID = value;
          }
}

Filter Beim Passenden Case einer Liste hinzufügen

case Equal
 
case ID
  filterListeAdd(new IDEqualFilter(Value)); //Value gegeben fals hier nach int Casten.  
//Anderen Cases

Und dann anwenden.


bool Match(ILogEntry<TEventType> entry)
{
      for each (filter in filterListe)
     {
          if(! filter.Match(entry) return fals;
      }

      return true,
}

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

4.931 Beiträge seit 2008
vor 6 Jahren

Was heißt denn bei dir "sehr lange"?
Wenn das auch schon bei "Equals" die Probleme macht (und nicht nur explizit bei "Contains"), dann solltest du mal einen Profiler verwenden.
Schreib doch mal eine handgeschriebene Schleife über die 30k-Einträge und vergleiche dessen Zeiten mit deinem Filter -> wenn das dann annähernd gleich ist, dann liegt es einfach an der Datenmenge (auch wenn ich jetzt 30.000 nicht viel finde, d.h. das sollte eigentlich in deutlich unter einer Sekunde in Memory durchgerattert sein).

Edit: Ich habe gerade den Code unten durchlaufen lassen:
Equals braucht nur 1-2 ms, und selbst Contains nur 25-30 ms (bei 30.000 Durchgängen).


using System;
using System.Diagnostics;
 
public class Test
{
	public static void Main()
	{
		Stopwatch stopwatch = new Stopwatch();
		stopwatch.Start();
		for (int n = 0; n < 30000; n++)
		  if ("Hello this is a long text".Contains("text"))
		    ;
 
		stopwatch.Stop();
		Console.WriteLine("Time: {0}", stopwatch.Elapsed);
	}
}

Hinweis von gfoidl vor 6 Jahren

Code bitte direkt einfügen. [Hinweis] Wie poste ich richtig? 6.1. gilt analog.

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 6 Jahren

Nach weiterer Recherche muss ich nun wohl einsehen, dass der Filter selbst wohl nicht die Ursache ist. Wie mir scheint liegt es an meiner Ereignisprotokollauswertung selbst und wie ich meine internen Objekte aufgebaut habe.

Das Problem ist wohl nicht das Filtern sondern am Beispiel EventLogs die EventRecord-Klasse und deren Methode "FormatDescription". - Mal sehen ob ich meinen Programmablauf optimieren kann.

Insofern erkläre ich das Thema erst einmal für beendet.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |