2D Array durchsuchen, _ArraySearch() zu langsam :(

  • moinsen mal wieder :)

    leider hab ich ein massives performance wenn ich mein array durchsuche:

    ich durchsuche mein array ($ProcList) mit allen laufenden prozessen (meist 50 - 200):

    $index = _ArraySearch($ProcList, $ExeFileName, 0, 0, 0, 0, 1, 0)

    ... das ganze in einer schleife und zwar ist $ExeFileName eine liste die ca. 2300 einträge umfasst.

    ... dauert bei mir ca 5 sekunden, dann ist aber der prozessor voll ausgelastet, was ich extrem störend
    finde, da die anwendung dezent im hintergrund laufen soll ohne das system zu freezen.
    baue ich sleeps ein, streckt sich das ganze wie kaugummie... :(
    was kann ich machen ? - gibt es eine performantere alternative zu _ArraySearch ??

    Gruss aus celle

    Einmal editiert, zuletzt von WhiteLion (27. August 2011 um 02:39)

  • Evtl kannst du dir mal die Funktion angucken, du findest sie ja in der UDF Arra.au3. Ich denke mal da wird nichts anderes gemacht als eine bzw mehrere For Schleifen genutzt, die es dann durchsuchen. Evtl gibt es dinge die du weglassen kannst, das sparrt Zeit. Da ich den genauen Aufbau deines Arrays nicht kenne kann ich dir leider nicht viel mehr sagen.

    Gruß

  • hier ein auszug der funktion:

    Daraus werden die exe-files (2300) geholt von $AppsDirFilesIni[$x][4]:
    $AppsDirFilesIni[4000][21]


    [autoit]

    Func _getProcessInfos($index_StartSearch_AppsDirFilesIni=0)
    _check_GUI_Buttons()
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","IN------------------------------------------------------_getProcessInfos"&@LF)
    ;if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","_getProcessInfos STARTE BEI INDEX:"&$index_StartSearch_AppsDirFilesIni&@LF)
    Local $x
    Local $index = 0
    ;Sleep(10)
    $ProcList = ProcessList()
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","INI index:"&$x&" search start from: "&$index_StartSearch_AppsDirFilesIni&@LF)
    ;_ArrayDisplay($ProcList, "$FileList")
    for $x=0+$index_StartSearch_AppsDirFilesIni to 3999
    $ExeFileName = $AppsDirFilesIni[$x][18]&$AppsDirFilesIni[$x][4]
    ;_ArrayDisplay($AppsDirFilesIni, "$FileList")
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","INI index:"&$x&" $ExeFileName:"&$ExeFileName&@LF)
    ;anzahl der elemente in $AppsDirFilesIni:
    Sleep(10)
    if $AppsDirFilesIni[0][9]-1 >= $x then
    $index = _ArraySearch($ProcList, $ExeFileName, 0, 0, 0, 0, 1, 0)
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","process search - error:"&@error&@LF)
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","process search:"&$index&@LF)
    if @error=0 and $index >= 0 then
    ;$RunningApplication[0] = $ExeFileName
    $AppsDirFilesIni[$x][10] = $ProcList[$index][0]
    $AppsDirFilesIni[$x][11] = $ProcList[$index][1]
    $AppsDirFilesIni[$x][12] = _PidGetPath($ProcList[$index][1])
    $AppsDirFilesIni[$x][13] = _createSHA1HashEditor($AppsDirFilesIni[$x][12])
    $AppsDirFilesIni[$x][14] = $index
    $IndexFound_AppsDirFilesIni = $x
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","INI index:"&$x&" setting continue index to: "&$IndexFound_AppsDirFilesIni&@LF)
    ; return >= 4 - patch okey, in liste!
    ; return <> 0 - patch was gefunden, aber falsch -> weitersuchen
    ; return = 0 nix gefunden
    $AppsDirFilesIni[$x][20] = (_regkey_classification($x) + _ApplicationDetectPath($x) + _hash_bewertung($x))
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log"," SUMME der BEWERTUNG $AppsDirFilesIni[$x][20]:"&$AppsDirFilesIni[$x][20]&@LF)
    if $AppsDirFilesIni[$x][20] = 1 or $AppsDirFilesIni[$x][20] = 2 or $AppsDirFilesIni[$x][20] = 3 Then
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","AUSKLAMMERN:"&$AppsDirFilesIni[$x][4]&@LF)
    $AppsDirFilesIni[$x][18] = 1
    EndIf
    if $AppsDirFilesIni[$x][20] >= 4 Then
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","OUT-------------------Return:"&$AppsDirFilesIni[$x][20]&"-----------------------------------_getProcessInfos"&@LF)
    Return $AppsDirFilesIni[$x][20]
    EndIf
    ;if $debuglog = 1 then FileWrite($ToolTitle&"debug.log", $RunningApplication[0] & " " & $RunningApplication[1] & " "& $RunningApplication[2] &" "& $RunningApplication[3] & " " & $RunningApplication[4] & " "& $RunningApplication[5]& " "& $RunningApplication[6])
    EndIf
    Else
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","clear RunningApplication array"&@LF)
    ;$index = ""
    $AppsDirFilesIni[$x][10] = ""
    $AppsDirFilesIni[$x][11] = ""
    $AppsDirFilesIni[$x][12] = ""
    $AppsDirFilesIni[$x][13] = ""
    $AppsDirFilesIni[$x][14] = ""
    $AppsDirFilesIni[$x][20] = ""
    $IndexFound_AppsDirFilesIni = ""
    EndIf
    if $AppsDirFilesIni[0][9]-1 <= $x then
    $IndexFound_AppsDirFilesIni = ""
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","OUT-------------------Return:leer-----------------------------------_getProcessInfos"&@LF)
    Return ""
    EndIf
    Next
    if $debuglog = 1 then FileWrite($ToolTitle&"debug.log","OUT-------------------Return:leer-----------------------------------_getProcessInfos"&@LF)
    $IndexFound_AppsDirFilesIni = ""
    return ""
    EndFunc

    [/autoit]
  • Ich weiß nicht genau was dein Programm machen soll.
    Daher wäre es vielleicht besser wenn du mal ganz konkret sagst was du am Ende erreichen willst und dann kann man mal schauen ob es an deinem Ansatz oder deiner Umsetzung etwas verbessern kann.
    Spontan überblicke ich den Code erstmal nicht so schnell.
    Ins Auge fallen mir da aber z.B. die For-Schleife die eine feste Endgröße von 3999 besitzt - sieht spontan ineffektiv aus - allerdings weiß ich ja nicht wofür die ist.

    Die eigentlich wichtige Frage dabei: Warum weiß du das es das ArraySearch ist was so langsam ist und nicht z.B. die _PidGetPath() oder die _createSHA1HashEditor (welche nicht im Skript dabei liegen)?

    Wie gesagt - beschreib lieber was du erreichen willst anstatt uns deinen komplexen Code zu erklären - dann kann man vielleicht mehr helfen.

  • Ich weiß nicht genau was dein Programm machen soll.
    Daher wäre es vielleicht besser wenn du mal ganz konkret sagst was du am Ende erreichen willst und dann kann man mal schauen ob es an deinem Ansatz oder deiner Umsetzung etwas verbessern kann.
    Spontan überblicke ich den Code erstmal nicht so schnell.
    Ins Auge fallen mir da aber z.B. die For-Schleife die eine feste Endgröße von 3999 besitzt - sieht spontan ineffektiv aus - allerdings weiß ich ja nicht wofür die ist.

    Die eigentlich wichtige Frage dabei: Warum weiß du das es das ArraySearch ist was so langsam ist und nicht z.B. die _PidGetPath() oder die _createSHA1HashEditor (welche nicht im Skript dabei liegen)?0

    ich weiss es daher, da diese funktionen nur bei einem treffer von _arraysearch ausgeführt werden.
    siehe: if @error=0 and $index >= 0 then
    das sind max. 2-5 treffer

    das array wird auch nicht bis zum ende (3999) durchsucht, sondern nur bis zum letzten eintrag:
    siehe: if $AppsDirFilesIni[0][9]-1 >= $x then
    okey, das ist optimierbar, drüfte aber wohl kaum das problem lösen :(


    Wie gesagt - beschreib lieber was du erreichen willst anstatt uns deinen komplexen Code zu erklären - dann kann man vielleicht mehr helfen.

    ich möchte erreichen, dass meine suche schneller vonstatten geht als bislang.

  • oh sry ... hab ich falsch verstanden.

    also sinn ist es die aktuell laufenden prozesse (in meinen script: $ProcList = ProcessList()) mit den prozessen aus meinem array was 2300 prozesse beinhaltet ($AppsDirFilesIni[4000][21]) zu vergleichen, wenn ich einen der 2300 prozesse unter den laufenden finde, dann führe ich vergleiche durch um sicher zu gehen, dass es sich auch wirklich um den gesuchten handelt. (die vergleiche sind aber wie gesagt nicht das problem, sondern nur das ständige scannen, was ich ja hier mit _ArraySearch anstelle)

  • Die Hauptbremse dürfte nicht _arraysearch sondern dein sleep(10) in Zeile 16 sein.
    Selbst bei nur 100 Durchläufen der Schleife verschwendest du damit schon 1 ganze Sekunde. Bei 4000 Durchläufen wären es schon 40 Sekunden.
    100% CPU Last sollte wohl mit Autoit auf keinem halbwegs aktuellen System möglich sein, denn Autoit nutzt maximal einen CPU Kern.

    Die zweite Bremse ist das permanente Schreiben in Dateien. Mit aktivierter Debug Funktion wird dein Script deutlich länger brauchen, da jeder Schreibzugriff auf die Festplatte einige ms Zeit beansprucht, je nach Festplatte.

  • ja, das sleep ist auch nur weil es sonst den prozessor (zumindest einen kern) ... auslastet
    mache ich sleep(1), dann gehts auf 6-10% auslastung (was ok wäre), aber der scannvorgang dauert pro durchlauf ca. 30 sekunden. :(

  • Hier sind 4 Schritte zur Optimierung:
    1) Sortiere deine Blockliste
    2) Suche in der großen Menge nach den Elementen der kleineren Menge und nicht umgekehrt
    3) verwende _ArrayBinarySearch
    4) Führe dein Sleep nur alle 1000 Durchläufe aus.

  • den prozessen aus meinem array was 2300 prozesse beinhaltet ($AppsDirFilesIni[4000][21])


    Wie kommst du da auf derartige Array-Dimensionen?
    Was steht denn in den 21 Spalten jeweils für Informationen?

    Ansonsten das umsetzen was Progandy schon geschrieben hat.
    Vor allem Punkt 3 (dafür ist Punkt 1 nötig) sollte dir schon sehr viel bringen.

    Desweiteren könntest du auch die Anzahl der Suchvorgänge reduzieren wenn du Prozesse ausklammerst welche definitiv nicht zu deinen gesuchten gehören werden (z.B. vielleicht die autoit3.exe oder Systemprozesse wie svchost).

  • @progandy
    okey, danke ...
    ich werds mal so versuchen :)

    AspirinJunkie
    das meiste informationen um die prozesse zu filtern wie....
    registrierungs-schlüssel werte, hashwerte, pids, pfade... etc.

    EDIT: also _ArrayBinarySearch kann ich leider nicht anwenden, dann es sich ja um ein 2D array handelt.
    das mit dem durchsuchen des großen arrays statt des kleinen hat einiges gebracht.
    Sortiere deine Blockliste <- das könntest du noch mal erklären... was meinst du mit blockliste ?

    Einmal editiert, zuletzt von WhiteLion (26. August 2011 um 22:29)

  • Hi,
    um spezielle Geschwindigkeitsprobleme zu lösen ist es meist sinnvoll, nicht auf allgemeine Funktionen ( z.B. _arraysearch() )zurückzugreifen, sondern eine eigene Funktion zu schreiben...

    Mach dir einfach mal bewußt, was dein Programm macht:
    Erstelle eine Liste mit x-tausend Einträgen von gespeicherten Prozessnamen.
    Erstelle eine Liste mit y-hundert laufenden Prozessen.
    Vergleiche JEDEN gespeicherten Eintrag x mit JEDEM laufenden Prozess y....
    Das ist sehr ineffektiv!

    Im Prinzip soll doch der laufende Prozess anhand seines Namens in der Suchliste gefunden werden.
    Das erinnert mich z.B. daran, ein Wort in einem Wörterbuch oder Lexikon zu finden. (Ja, das ist der grosse Mist am Internet, die einfachsten Sachen wie die Benutzung von Lexika werden verlernt)
    Wenn ich in einem Lexikon das Wort "Prozess" suche, dann fange ich bestimmt nicht auf der ersten Seite bei "A" an und blättere solange weiter, bis ich beim Anfangsbuchstaben "P" angelangt bin^^, sondern schlage das Kapitel "P" direkt auf.
    Bzw. schlage ich sogar im Kapitel "P" relativ weit hinten nach, weil der 2. Buchstabe von "Prozess" ein "r" ist, und der im Alphabet weit hinten steht.
    Wenn das Lexikon 3999 Seiten hat, "kostet" meine Suchanfrage höchstens 3-4 Seiten blättern 8o , während du im schlimmsten Fall ALLE 3999 (in deinem Fall unsortierte) Seiten durchsuchen musst :huh:

    In AutoIt gibts eine sehr schnelle Methode solche Listen zu durchsuchen, AspirinJunkie hat HIER einige Alternativen vorgestellt.

    Wenn das umkopieren der Prozesse (Array aus Processlist() ) in die Liste zu lange dauert oder du nur mit arrays arbeiten willst, erstelle ein sortiertes Array deiner gespeicherten Prozessnamen und merke dir die Indizes (und die Anzahl) der Prozessnamen mit den Anfangsbuchstaben aus dem Alphabet.
    Pseudocode:

    Code
    $a=_index(AnfangsbuchstabeProzessname)
    $b=_index(AnfangsbuchstabeProzessname)+_anzahl(Prozessnamen_mit_diesem_Anfangsbuchstaben) 
    For $i=$a to $b  ;durchsucht nur die Einträge mit gleichem Anfangsbuchstaben
    if $exefilename=$aProclist[$i] then _gefunden()
    next
  • Mich hätte eher interessiert wie du bei 2300 Elementen auf eine Größe der 1. Dimension von 4000 Elementen kommst.
    Binäre Suche über ein 2D-Array gibt es auch und wurde auch schon mehrmals umgesetzt (>>Klickmich<<)
    Wenn du noch mehr Performance brauchst musst du aber weg von einem Array zu einer anderen Datenstruktur.
    Eine SQL-Datenbank wäre eine Methode aber für dein Anliegen sollte ein Dictionary-Objekt die vernünftigste Wahl darstellen.
    Mal ein Beispiel zur Anwendung:

    Beispiel zur Verwendung des Scripting.Dictionary-Objektes
    [autoit]

    ;//// Dictionary Erstellen ///////////
    $oDict = ObjCreate("Scripting.Dictionary")

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ;//// Einträge hinzufügen ///////////
    $oDict.add ("Berlin", 3395189)
    $oDict.add ("Hamburg", 1743627)
    $oDict.add ("München", 1259677)
    ; alternativ: (bringt keinen Fehler wenn Schlüssel schon existiert)
    $oDict("Köln") = 983347
    $oDict("Dresden") = 517052
    ;Beispiel hier: Einwohnerzahl wird der jeweiligen Stadt zugeordnet.

    [/autoit] [autoit][/autoit] [autoit]

    ;//// Anzahl der Elemente bestimmen ///////////
    MsgBox(0,"Anzahl Elemente in Dictionary", $oDict.Count)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ;//// Überprüft ob ein Element vorhanden ist ///////////
    If $oDict.Exists ("Bonn") Then MsgBox(0, "", "Bonn ist eingetragen") ;Bonn ist im Bsp. nicht enthalten

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ;//// Löscht ein Element aus dem Dictionary ///////////
    $oDict.remove ("München")

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ;//// Elemente aufrufen ///////////
    ConsoleWrite("Einwohner(Berlin): " & $oDict("Berlin") & @CRLF)
    ConsoleWrite("Einwohner(Dresden): " & $oDict("Dresden") & @CRLF & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    ;//// alle Schlüssel durchgehen ///////////
    For $k in $oDict.Keys
    ConsoleWrite("Einwohner(" & $k & "): " & $oDict($k) & @CRLF)
    Next
    ;/// Alle Werte statt Schlüssel erreicht man mit $oDict.Items ////

    [/autoit] [autoit][/autoit] [autoit]

    ;//// Leert das Dictionary ///////////
    $oDict.RemoveAll

    [/autoit]


    Ich hätte dir ja ein besser an deinen Anwendungsfall angepasstes Beispiel gemacht aber da du selbst auf Nachfrage nicht den Großen Zusammenhang und Zweck erklärst und nicht postest wie deine Arrays enstehen und du partout nur diese Funktion als Codefetzen postest musst du dich da halt selbst reinfitzen.
    Konkret würdest du mit dem Dictionary dein Array $AppsDirFilesIni ersetzen. Als Schlüssel würde $ExeFileName fungieren und als Wert würde jedem Schlüssel ein 1D-Array mit den restlichen Informationen zum Prozess zugewiesen.
    Die resultierende Suche wäre dann massiv schneller als das was du bisher hast und ebenfalls schneller als die binäre Suche über ein Array.

    Mit "Blockliste" meint Progandy übrigens dein $AppsDirFilesIni

    Einmal editiert, zuletzt von AspirinJunkie (27. August 2011 um 13:15)

  • Mal eine kleine Off Topic Frage an AspirinJunkie:

    Gibt es hier irgendwo ein gutes und umfangreiches Tutorial zum Dictionary Object ?
    Kann man damit auch 2-dimensionale Arrays darstellen, also anstelle von Ort+Einwohnerzahl auch noch mehr zugehörige Werte wie Postleitzahl usw. ?
    Wo findet man Informationen zu den verfügbaren Funktionen (add,remove,exists,keys,item....) ?

    Wollte mir das schon lange mal ansehen.

  • Ein Tutorial - keine Ahnung.
    Bugfix hatte mal eine UDF dazu gebastelt. Ich persönlich denke aber das man das Handling nicht extra in Funktionen verpacken muss sondern gleich direkt damit arbeiten kann.
    Prinzipiell ist es nicht wirklich viel was das Dictionary kann - das meiste sollte schon mit diesem kleinen Beispiel vorgeführt worden sein.
    Die komplette Referenz findest du >>Hier<<.

    Prinzipiell sollte man sich eigentlich nur merken das man mit einem Schlüssel (ein String) auf damit verknüpfte Werte sehr schnell zugreifen kann. In welcher Form die Werte dann vorliegen ist egal. Das können Zahlen, Strings, Arrays oder auch weitere Dictionary-Objekte sein - völlig egal. Wichtig ist nur der Schlüssel.
    Und hier kannst du auch sehen wie du weitere Informationen hinterlegen kannst. Du würdest statt dem Namen der Stadt (der Schlüssel) einen Zahlenwert (hier die Einwohnerzahl) zuzuweisen stattdessen ein kleines 1D-Array zuweisen welches z.B. als 1. die Einwohnerzahl enthält und als 2. Element die Fläche der Stadt oder so. Wie gesagt - was du dem Schlüssel zuweist ist egal.
    Musst aber darauf achten das der Schlüssel nur einmal vorkommt.
    Einfach mal ein bisschen damit rumspielen - das ist nicht komplizierter als Arrays oder so.