FileRead Buffer?

    • Offizieller Beitrag

    Hallo,

    Folgendes Problem: Ich baue gerade ein Wireshark ähnlichen Sniffer der die Daten die an die Netzwerkkarten gesendet werden in realtime entpackt. Allerdings gibt es in dem Programm auch eine Speicher und Öffnen funktion. Ich habe selbst den PCAP Standard mir angeeignet und schreibe und lese die PCAP files also nun mit einer eigenen Routine via FileWrite/FileRead.

    Läuft soweit alles gut. Nun steh ich vor folgendem Problem: PCAP Dateien können unglaublich groß sein. Also dacht ich mir, schreibe ich sie nur Teilweise in RAM und lese dann immer das aus was ich gerade brauche, doch zuvor wollte ich sicherheitshalber noch ein Test machen: Ist das überhaupt schneller?

    Der Test sieht wie folgt aus:

    Test A
    Die Daten werden mit FileRead eingelesen => Jedes mal wenn ich neue Daten brauche muss FileRead aufgerufen werden

    Test B
    Die Daten werden komplett in ein Buffer geschrieben => Jedes mal wenn ich neue Daten brauche kann ich mit BinaryMid mir die stelle raussuchen.

    Nun das verwunderliche: Mit dem Buffer zu arbeiten ist bei mir weitaus langsamer als mit FileRead.
    z.B. für eine 9mb große Datei dauert die variante mit FileRead und nem buffer von nem kb auf einem Testlaptop 169ms, die Buffer methode liegt bei 30735ms :D

    Jemand eine Idee woran es liegt? Hier mal ein Code zum Austesten.. Speichert die FileOpen funktion auch ziwschen? Falls ja, kann man da den Buffer irgendwie verändern?

    Spoiler anzeigen
    [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    #Region ;**** Directives created by AutoIt3Wrapper_GUI ****
    #AutoIt3Wrapper_UseUpx=n
    #AutoIt3Wrapper_Change2CUI=y
    #EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****

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

    ;BufferSize, ggf. ändern
    Local Const $iBufferSize = 1024; 1kb
    Local Const $sFilename = FileOpenDialog("","","all (*.*)")
    if @error Then Exit

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

    Local $iFileSize = FileGetSize($sFilename)
    ;Test mit wiederholtem aufrum von FileRead

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

    Local $hTestTimer = TimerInit()
    Local $hTest = FileOpen($sFilename,16)

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

    Local $iBytesRead = 0, $bTestWrite
    Do
    $bTestWrite &= FileRead($hTest,$iBufferSize)
    $iBytesRead += @extended
    Until $iBytesRead >= $iFileSize

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

    FileClose($hTest)

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

    ConsoleWrite("FileRead: "&TimerDiff($hTestTimer) & @LF)
    ConsoleWrite("Bytes Read: "&$iBytesRead & @LF)

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

    ;Test: Einmal wird der Buffer geschrieben, und dann mit BinaryMid die Daten beareitet

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

    Local $hTestTimer = TimerInit()
    Local $hTest = FileOpen($sFilename,16)

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

    $bBuffer = FileRead($hTest)

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

    Local $iBytesRead = 0, $bTestWrite = ""
    Do
    $bTestWrite &= BinaryMid($bBuffer,$iBytesRead + 1, $iBufferSize)
    $iBytesRead += $iBufferSize
    Until $iBytesRead >= $iFileSize

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

    FileClose($hTest)

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

    ConsoleWrite("BinaryMid: "&TimerDiff($hTestTimer) & @LF)
    ConsoleWrite("Bytes Read: "&$iBytesRead & @LF)

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

    MsgBox(0,"","Exit")

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

    Danke und Gruß,
    Spider

  • Hi,

    schau dir mal

    [autoit]

    Filegetpos()
    Filesetpos()

    [/autoit]

    an.
    Wenn du weisst wo die Daten in der Datei liegen, kannst du dir genau den Block rausholen den du brauchst, ohne die ganze Datei zu laden.
    Btw. kann man so auch Blöcke in die Datei schreiben, ohne sie vorher komplett zu öffnen (und in den Speicher zu schreiben).

    Allerdings ist JEDES Geschreibsel auf und von der Platte um Welten langsamer als das Lesen/Schreiben aus dem Speicher.
    Ich würde die 9MB komplett in den Speicher krachen und fertig^^

    • Offizieller Beitrag

    Hallo,

    Hehe, danke erstmal für die Antworten.

    Oscar: Klar, habs auch mit größeren Buffern und größeren Dateien probiert, trotzdem keine Chance das die Buffer variante schneller ist.

    Andy: Ja klar, das ist mir bewusst ;) Problem ist, dass eine PCAP Datei wie folgt aufgebaut ist:
    [GLOBAL HEADER] [PACKET 1 HEADER][PACKET 1 DATA]....[PACKET n HEADER][PACKET n DATA]
    D.h. die Datei wird einmal von oben nach unten eingelesen und die jeweiligen Positionen wo welches Paket steckt in eine Hashtable (System.Collections.Hashtable) geschrieben. Das mit der Hashtable läuft auch super flüssig.

    Problem ist wie gesagt, dass die Dateien im Normalfall ~10mb groß sind, eher weniger, aber es gibt ausnahmen, wo traces über mehere Tage gemacht werden, die dann auch gerne mal mehrere Gigabyte groß werden.

    Gruß,
    Spider

  • Hi,

    Zitat

    Problem ist wie gesagt, dass die Dateien im Normalfall ~10mb groß sind, eher weniger, aber es gibt ausnahmen, wo traces über mehere Tage gemacht werden, die dann auch gerne mal mehrere Gigabyte groß werden.


    Da du die PCAP-Dateien selbst schreibst, würde ich für jede dieser Dateien eine Indexdatei erstellen mit dem Muster

    Zitat

    und die jeweiligen Positionen wo welches Paket steckt


    Diese Indexdatei ist doch nur einen Bruchteil so groß wie die eigentliche PCAP-Datei!?
    Die Index-Datei kannst du auch in einem Rutsch in die Hashtable einlesen. Je nach Größe der PCAP-Datei liest du die Datei komplett in den Speicher oder mittels FileSet/Getpos direkt aus der Datei.

    • Offizieller Beitrag

    Hi,


    Da du die PCAP-Dateien selbst schreibst, würde ich für jede dieser Dateien eine Indexdatei erstellen mit dem Muster


    Diese Indexdatei ist doch nur einen Bruchteil so groß wie die eigentliche PCAP-Datei!?
    Die Index-Datei kannst du auch in einem Rutsch in die Hashtable einlesen. Je nach Größe der PCAP-Datei liest du die Datei komplett in den Speicher oder mittels FileSet/Getpos direkt aus der Datei.

    Hey Andy,

    Theoretisch liegst du da richtig, aber ich muss natürlich auch PCAP Dateien einlesen können die z.B. von Wireshark kommen ;) Da gibbet keine Indexdatei.

    Gruß,
    Spider

  • Hi,

    Zitat

    ber ich muss natürlich auch PCAP Dateien einlesen können die z.B. von Wireshark kommen Da gibbet keine Indexdatei.

    du entäuschst mich :D

    - Wenn du eine Datei mit der Größe bspw. kleiner 100MB einlesen willst, dann liest du diese direkt ein, ab in den Speicher damit. Ob von deinem Programm oder von Wireshark erstellt spielt dabei keine Rolle.

    - Ist die Datei größer 100MB erstelle ggf vor dem Einlesen einen Index und lies daraus ein.

    Die eigentliche Analyse der Daten erfolgt sowieso zeitunkritisch! Ob da vor dem Einlesen von einer 1-Gig-Datei 5 Sekunden lang ein Index erstellt wird, mit dem du später ohne "Ruckler" durch die Daten scrollen kannst, oder ob du während der Analyse alle 100MB eine "Gedenksekunde" zum nachladen von Daten einlegen willst, ist Geschmacksache.
    Ich persönlich warte lieber beim Starten der Anwendung und habe dann einen geschmeidigen Workflow^^

    • Offizieller Beitrag

    Hi,

    du entäuschst mich :D

    - Wenn du eine Datei mit der Größe bspw. kleiner 100MB einlesen willst, dann liest du diese direkt ein, ab in den Speicher damit. Ob von deinem Programm oder von Wireshark erstellt spielt dabei keine Rolle.

    - Ist die Datei größer 100MB erstelle ggf vor dem Einlesen einen Index und lies daraus ein.

    Die eigentliche Analyse der Daten erfolgt sowieso zeitunkritisch! Ob da vor dem Einlesen von einer 1-Gig-Datei 5 Sekunden lang ein Index erstellt wird, mit dem du später ohne "Ruckler" durch die Daten scrollen kannst, oder ob du während der Analyse alle 100MB eine "Gedenksekunde" zum nachladen von Daten einlegen willst, ist Geschmacksache.
    Ich persönlich warte lieber beim Starten der Anwendung und habe dann einen geschmeidigen Workflow^^


    Hehe ;) Genau so mach ich das mein lieber Andy! Mit nem klick auf Open wird die Datei von oben bis unten eingelesen, schön mit Ladebalken, und dabei gleich die Indexdatei erstellt. Leider dauert ein Paket einzulesen ca. 1 ms (der FileRead befehl, ist wie gesagt ein langsamerer PC), bei 100.000 Einträgen also 100 Sekunden, bei 1.000.000, 17 Minuten... Ein so ein Paket ist aber meist nie größer als 100 Byte. Deswegen meine Idee: Mach ein Buffer, lese immer 10 MB Päkchen in den RAM und lese davon dann ein. Dachte, dass würde schneller gehen, tut es aber nicht sondern es ist weitaus langsamer. Meine Frage war ausschließlich: Warum? :D Damit ich versuchen kann es zu verschnellern.

    Gruß,
    Spider

  • Mal angenommen, du speicherst als " Fixed Length Block" (soll es lt. Beschreibung geben^^), dann hat der Block ja eine fixe Länge (man, bin ich guuut!) und ist sehr einfach und vor allem schnell auszulesen!
    Testscript:

    Spoiler anzeigen
    [autoit]

    $a = ""
    for $i = 32 to 132 ;100 Byte "Paket" erstellen, "Fixed Length Block"
    $a &= Chr($i)
    next
    MsgBox(0, "Block", $a)

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

    for $i = 1 to 20 ;bissl spielen von 10-20
    $a &= $a
    next

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

    ConsoleWrite(Int(StringLen($a) / 1e6) & "MB" & @crlf)
    FileDelete("PCAP.bin")
    FileWrite("PCAP.bin", $a)

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

    ;sodele, einlesen......
    $t = TimerInit()
    $file = FileOpen("PCAP.bin", 0) ;binär ist ca. 3x schneller als text!!
    $inhalt = FileRead($file)
    ;$inhalt = string($inhalt) ;umformen zum string dauert wieder^^
    FileClose($file)
    $einlesen = TimerDiff($t)
    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $einlesen = ' & $einlesen & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
    $MB_pro_sekunde = Int(StringLen($a) / 1e3 / $einlesen)
    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $MB_pro_sekunde = ' & $MB_pro_sekunde & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console

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

    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $inhalt = ' & StringLeft($inhalt, 200) & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console

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

    ;splitten
    $t = TimerInit()
    $Bloecke = 0
    For $i = 1 to StringLen($inhalt) step 101 ;blocklänge+1
    $Block = StringMid($inhalt, $i, 200)
    ; ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $Block = ' & $Block & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
    $Bloecke += 1
    ; ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $bloecke = ' & $bloecke & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
    ; if $i < 1000 then ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $Block = ' & $Block & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
    next
    $splitten = TimerDiff($t)
    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $splitten = ' & $splitten & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
    $MB_pro_sekunde = Int(StringLen($a) / 1e3 / $splitten)
    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $MB_pro_sekunde = ' & $MB_pro_sekunde & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $bloecke = ' & $Bloecke & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console

    [/autoit]

    und Ausgabe meines Gurkenrechners

    Spoiler anzeigen

    Ich bekomme leider bei größeren Dateien einen Memory-Allocation-Error von AutoIt. Das ist infolge des Datentyps Variant. Daher auch das 3x schnellere Einlesen beim binary-flag. allerdings dauert das Umformen zum String wieder länger, Binarymid() ist der horror!
    Größere Dateien zu erstellen ist aber kein Thema.
    Aber sei es drum, 100 MB werden trotzdem in ca. 4 Sekunden eingelesen, ca. 400x schneller als bei dir.
    Ich werde morgen mal ein Beispiel basteln für Zugriffe auf variable Blockgrößen innerhalb der in den Speicher eingelesenen Daten, da ist ja nur noch die Startadresse des nächsten Blocks interessant, also maximal 1-2 Zeilen zusätzlich. Jetzt sehe ich auch dein Problem, ich vermute, du hangelst dich innerhalb der Datei (!) von einem Block zum anderen!?

    Ich denke, man könnte auch unter AutoIt den Dateizugriff massiv beschleunigen mittels Verwendung der WINAPI-Funktionen ReadFile() usw.
    Auch müsste man nur 1x Speicher für eine Struct für die gelesenen Daten anlegen.

    //EDIT mach mal in deinem Script den Buffer auf 1024^2, also 1 MB, dann passt das ^^

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    3 Mal editiert, zuletzt von Andy (23. November 2013 um 20:59)