CSV Datei Zeilen zählen - extrem schnell - GNUWin32

  • Hallo Forum,

    da ich nun schon seit 2 Tagen nach einer schnellen Lösung suchte um große CSV Dateien auszulesen bin ich im Entwicklerforum auf ein kurzes Skript gestoßen welches bei mir allerdings nicht funtionierte.
    Ich habe es deshalb etwas umgeschrieben und dachte daß es vielleicht mal jemand für nützlich erachten könnte.

    Ich habe hier eine CSV Datei mit 1,8 Millionen Zeilen in 165 ms ausgelesen (Anzahl der Zeilen ermittelt). Das CSV File ist 80Mb groß.
    Man benötigt dafür GNUWin32 installiert und daraus den wc und cut Befehl (ist in den CoreUtils).

    AutoIt
    #include <Timers.au3>
    Local $GNU = "C:\Program Files (x86)\GnuWin32\bin"
    Local $ReadFile = @MyDocumentsDir&"\CSV\Readme.csv"
    Local $SaveFile = @MyDocumentsDir&"\CSV\Count.txt"
    Local $starttime = _Timer_Init()
    RunWait(@ComSpec & ' /c wc -l '&$ReadFile&' | cut -d " " -f 1 > '&$SaveFile, $GNU, @SW_HIDE)
    ConsoleWrite(@CRLF & "Datei Groesse(MB) : " & Round((FileGetSize($ReadFile) / 1048576), 2))
    ConsoleWrite(@CRLF & "Zeilen            : " & FileReadLine($SaveFile))
    ConsoleWrite(@CRLF & "Dauer (ms)        : " & Round(_Timer_Diff($starttime), 2) & @CRLF)

    Weis nicht ob das hier schon mal besprochen wurde....

    Einmal editiert, zuletzt von Blaxxun (16. März 2016 um 01:48)

  • Ich habe jetzt leider keine so große Datei am start und kann diese Methode nicht ausprobieren.
    In der File.au3 gibts die (mit RegEx arbeitende) Funktion _FileCountLines. Die könntest du ebenfalls ausprobieren. Bei der Geschwindigkeit bin ich nicht sicher (die Funktion habe ich noch nie für große Dateien genutzt), aber sie funktioniert ohne GNU.

    lg
    M

  • da ich nun schon seit 2 Tagen nach einer schnellen Lösung suchte um große CSV Dateien auszulesen bin ich im Entwicklerforum auf ein kurzes Skript gestoßen welches bei mir allerdings nicht funtionierte.

    Kannst du vll. noch ergänzend die Variationen aufzeigen die du ausprobiert hast und ggf. die Ergebnisse? Mich würden die Unterschiede und die angewandten Methoden der Auswertung interessieren.

    Grüße Yaerox

    Grüne Hölle

  • Meine AutoIt-Version, die nicht die Installation eines weiteren Programmes erfordert (als Vergleich)

    Datei Groesse(MB) : 68.66
    Zeilen : 2000000
    Dauer (sec) : 1.82

    Es gibt Tage, da trete ich nicht ins Fettnäpfchen. Ich falle in die Friteuse.

    Einmal editiert, zuletzt von Runa (17. März 2016 um 13:31)

  • Für reines AutoIt sollte folgendes flotter sein:

    AutoIt
    $iT = TimerInit()
    StringReplace(FileRead($s_FilePath), @CRLF, @CRLF, 0, 1)
    $d_Lines = @extended
    ConsoleWrite(StringFormat("\n%s:\n\tZeilen: %d\n\tDauer:%4.1f s\n", "StringReplace", $d_Lines, TimerDiff($iT) / 1000))
  • Danke AspirinJunkie. Hab das mal getestet:

    Output hier:

    Spoiler anzeigen


    Test mit 1000 Durchläufen...
    1: Gesamtdauer im Schnitt (sec): 1.78
    1: MIN : 1.75

    1: MAX : 1.83
    2: Gesamtdauer im Schnitt (sec): 1.73
    2: MIN : 1.65
    2: MAX : 2.69

    Ich frage mich, warum die StringReplace-Variante so extreme "Ausbrüche" hat im Vergleich 8| Fühlbar ist da aber im Dauertest kaum ein Unterschied. Vielleicht nur wegen den Ausbrechern hier auf dem System. Werde das mal nachher auf meiner Workstation laufen lassen. Eventuell macht das einiges klarer.

    Es gibt Tage, da trete ich nicht ins Fettnäpfchen. Ich falle in die Friteuse.

  • Es gibt auch die Built-in Funktion,

    AutoIt
    #include <File.au3>
    _FileCountLines()


    die ähnlich der AspirinJunkie's Version ist.

    Hier eine ASM Version:

    Benötigt für eine 230 mb große CSV Datei ca. 7600 ms, um die Zeilen zu zählen. Built-in ca. 8500 ms. -> kein richtiger Gewinn. Ich frage mich, wie die GNU Variante auf 165 ms kommt!

    Auch am Arsch geht ein Weg vorbei...

    ¯\_(ツ)_/¯

    2 Mal editiert, zuletzt von UEZ (17. März 2016 um 16:49)

  • Ich frage mich, warum die StringReplace-Variante so extreme "Ausbrüche" hat im Vergleich

    Nur anhand von Minmum und Maximum-Wert kann man das schlecht beurteilen.
    Möglich wäre, dass die Festplatte während eines Durchlaufes gerade ausgelastet war und deshalb dort das Laden der Datei mal länger dauerte.
    Ob es sich um einen einzelnen Ausreißer handelt (der nicht repräsentativ das Verhalten abbildet) oder systematisch die Schwankung deutlich höher ist, kann man eher entscheiden wenn man sich die Standardabweichung dazu nimmt:


    Hier mal ein Ergebnis bei mir für eine 100mb Datei mit 2.000.000 Zeilen:

    In diesem Beispiel zeigt sich dann, dass die StringReplace-Variante bei diesem Durchlauf weniger stark schwankt als die Batch-Variante.

    Es gibt auch die Built-in Funktion,

    Die zwar etwas langsamer ist aber dafür ein paar Sonderfälle mit abdeckt - für den Produktivbetrieb somit die bessere Funktion.

  • Benötigt für eine 230 mb große CSV Datei ca. 7600 ms, um die Zeilen zu zählen. Built-in ca. 8500 ms. -> kein richtiger Gewinn.

    Naja, profilen hilft^^, und zwar vorher....
    Wenn Fileread() etwa 90% der Zeit braucht, nützt es dir nichts bei den verbleibenden 10% in Beschleunigung zu investieren!
    Dateien werden idR. nicht "am Stück", sondern in Blöcken zu einigen Bytes Größe eingelesen.
    Das AutoIt-native FileRead() arbeitet höchstwahrscheinlich mit Blockgrößen von 2KB oder 4KB. Woher ich das weiß? Weil ich es ausprobiert und verglichen habe.
    Bei kleinen Dateien ist das egal, aber wenn bei großen Dateien Blocks von 64kb gelesen werden, dann beschleunigt sich das Lesen der gesamten Datei in den Speicher um Faktor 3 bis 4.
    Jedenfalls bei meinem Laptop (mit SSD), ihr könnt die Blockgrößen im anliegenden Script gerne variieren.

    Habe mal die Vorgaben von ca. 96MB Dateigröße mit ca. 2Mio Zeilen umgesetzt und bei mir ist eine Blockgröße von 32 bis 64KB optimal...

    Das Suchen von @LF habe ich (wen wundert´s) in Assembler umgesetzt.
    Dazu habe ich ein XMM-Register als 16x 1 Byte(Char) verwendet, dort das Linefeed maskiert und die "Treffer" in ein 16-bit-Register geschrieben. Die Anzahl der gesetzten Bits in diesem Register ist die Anzahl der Linefeed innerhalb der 16 Bytes. Über die verschiedenen "popcount"-Methoden (Zählen von gesetzten Bits innerhalb eines Registers) wurden schon Bücher geschrieben. Div. Methoden hier
    Ich habe, weil ich nur die Handvoll 32-Bit-Register verwenden wollte, eine 16 byte große LookUpTable (LUT) auf dem Stack erstellt und per XLATB-Befehl die Anzahl der gesetzten Bits (nibble von 4 Bits Größe, also nur die untere Hälfte vom AL-Register) ausgelesen. Alle diese ausgelesenen Anzahlen ergeben summiert die Gesamtzahl der LineFeeds im String.
    Geht sicherlich noch schneller, @AspirinJunkie könnte ja mal den ( neuen? ) Intel-Compiler anwerfen 8o . Muster siehe HIER. Habe echt keine Ahnung inwieweit der compilierte Code schneller ist als mein ASM-Pendant, würde mich aber absolut nicht wundern, habe ja auch eine komplett andere Methode angewandt.
    Letztendlich ist es auch schnurz, ob die Anzahl der Linefeeds (oder auch anderer Zeichen) eine Handvoll Millisekunden schneller oder langsamer ist, wenn allein das Lesen des Dateiinhaltes schon 90% der Laufzeit ausmacht...
    Alleine das "kopieren" des Strings in eine Variable dauert sehr lange, also $data=dllstructgetdata(blablub). Da man das für die vorliegende Problemstellung aber garnicht braucht, lässt man es weg...


    Zusammenfassung:
    Bei großen Dateien ist das AutoIt-FileRead() SEHR langsam, schneller ist die gezeigte Methode über _WinAPI_FileRead() mit einer auf das eigene System angepasster Blockgröße.
    Benötigt man den Dateiinhalt nicht in Form einer Variable, ist es sinnvoller/schneller, direkt mit den Daten im Speicher (struct) zu arbeiten.

    Auf meinem Laptop AMD A6-3400M APU with Radeon(tm) HD Graphics @2,3Ghz (Samsung SSD):
    Vergleich Datei laden und die LF zählen:
    WinAPI: Read 96MB File #Lines: 2097152 Blocksize: 65536 bytes time: 214 ms

    AutoIt: Read 96MB File time: 5374 ms OHNE zählen der LF....wobei man fairerweise zugeben muss, daß natürlich die Rückgabe des Dateiinhaltes in einer Variable enthalten ist.

    //EDIT
    Speicherleck entfernt...

    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

    9 Mal editiert, zuletzt von Andy (19. März 2016 um 11:36)

  • Wenn Fileread() etwa 90% der Zeit braucht, nützt es dir nichts bei den verbleibenden 10% in Beschleunigung zu investieren!

    Ich meine mich erinnern zu können, dass das Lesen kein Ding war, eher das Suchen nach @LF. Aber mich wundern doch die 165ms ein wenig...

    Das mit der Block Größe ist richtig :thumbup: . So kann man den Ladevorgang doch erheblich beschleunigen. :klatschen:

    Auch am Arsch geht ein Weg vorbei...

    ¯\_(ツ)_/¯

    Einmal editiert, zuletzt von UEZ (19. März 2016 um 11:00)

  • Script oben etwas geändert:

    Code
    WinAPI:   Read 96MB File    #Lines: 2097152   Blocksize: 1024 bytes    time: 4599 ms  
    WinAPI:   Read 96MB File    #Lines: 2097152   Blocksize: 8192 bytes    time: 679 ms  
    WinAPI:   Read 96MB File    #Lines: 2097152   Blocksize: 27648 bytes    time: 282 ms  
    WinAPI:   Read 96MB File    #Lines: 2097152   Blocksize: 65536 bytes    time: 184 ms  
    WinAPI:   Read 96MB File    #Lines: 2097152   Blocksize: 128000 bytes    time: 150 ms  
    WinAPI:   Read 96MB File    #Lines: 2097152   Blocksize: 221184 bytes    time: 136 ms  
    WinAPI:   Read 96MB File    #Lines: 2097152   Blocksize: 351232 bytes    time: 129 ms  
    
    
    Autoit:   Read 96MB File   time: 3616 ms

    Sieht wohl so aus, als ob größere Blockgrößen doch schneller sind...selbst auf meinem langsamen Rechner und AutoIt komme ich in den Bereich der Ergebnisse des TE. Und die kommen aus (hoffentlich) optimierten GNU-Bibliotheken.
    Btw. dauert die Suche nach den LF mit meinem unoptimierten ASM-Code ca. 30ms bei 96MB Dateigröße, das Laden dauert somit 100ms, nicht schlecht....

    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

    Einmal editiert, zuletzt von Andy (19. März 2016 um 12:43)

  • Wow hätte nicht gedacht, dass FileRead noch dermaßen viel Optimierungspotential besitzt.
    Hatte da gleich mal ne kleine Funktion basierend auf den API-Funktionen gemacht - und siehe da: Doppelt so schnell als FileRead.

    Dann hab ich dein Skript genommen und dann nicht schlecht gestaunt dass eien 100mb Datei in 78 ms eingelesen sein sollte.
    Zwar rechnest du mit dem falschen $p bei der Mittelwertberechnung ($p hat zu dem Zeitpunkt den Wert 11) aber das kann nicht der Grund für die krassen Ergebnisse sein.
    Direkt ist mir nix weiter aufgefallen.
    Also hab ich deine _TextFileRead als Funktion zum Einlesen einer Textdatei genommen und auf einmal sind die Ergebnisse nicht mehr so krass:

    Jetzt deckt sich die Performance mit meiner Funktion.
    Also entweder ich hab das falsch implementiert oder der Effekt ist auf was anderes zurückzuführen (Caching oder sowas?).
    Vielleicht kannst du ja nochmal in mein Skript hier schauen und gucken ob ich deinen Ansatz korrekt eingebaut habe.

  • Bei deinem Script stimmt alles! In der Funktion _TextFileRead() steht nicht umsonst das "Text", denn AutoIt(C++)-typisch werden beim Schreiben in einen String (mein Auslesen der Struct) nur Daten bis zum ersten Nullbyte berücksichtigt.
    Ansonsten liegen wir gleichauf^^. Würde AutoIt Pointer unterstützen, wäre das der Faktor 3 im Vergleich zur nativen Funktion!

    Fraglich ist nur, wo die native AutoItfunktion die Zeit verliert. Ich bin ziemlich sicher, dass die DEV´s die C++-Standard-Bibliotheken benutzen...

  • Na dann hab ich mir mal ne kleine Funktion erstellt - kann ich sicherlich noch paar mal brauchen:

    Einmal editiert, zuletzt von AspirinJunkie (19. März 2016 um 16:42) aus folgendem Grund: kleiner Fehler behoben

  • :thumbup:
    Ja, wird man sicherlich gebrauchen können, ich binde diese Funktion mal testweise in geschwindigkeitskritische Scripte ein.
    Schaumamal, ob man so auch andere "Programmiersprachen" beschleunigen kann bzw. die Dateigrößen dort massiv erweitern kann, ohne an Performance zu verlieren...

  • Wow!
    Der Hammer wie hier einige abgehen!
    Sehr informativ! Danke für die professionellen Beiträge!!!

    Kannst du vll. noch ergänzend die Variationen aufzeigen die du ausprobiert hast und ggf. die Ergebnisse? Mich würden die Unterschiede und die angewandten Methoden der Auswertung interessieren.

    Also Variationen habe ich nicht sehr viele probiert. Ich war eher dran das Ding zum laufen zu bringen da die Apostrophe nicht stimmten und das DOS Fenster nur kurz aufblinkte und somit keinen Rückschluß zulies.

    @Mars@Bioshade

    Ich habe letztendlich den _FileReadToArray($File, $CSV) Befehl genommen da dann in $CSV[0] die Anzahl der Zeilen enthalten ist.
    Das ganze ist aber sehr langsam (6000ms) aber, wie ihr schon angedeutet habt einfach praktischer da man sich die GNU Installation erspart.


    @Andy

    Ich habe dein Skript laufen lassen und ich komme auf folgende Werte:

    Code
    WinAPI: Read 96MB File #Lines: 2097152 Blocksize: 1024 bytes time: 2848 ms
    WinAPI: Read 96MB File #Lines: 2097152 Blocksize: 8192 bytes time: 433 ms
    WinAPI: Read 96MB File #Lines: 2097152 Blocksize: 27648 bytes time: 141 ms
    WinAPI: Read 96MB File #Lines: 2097152 Blocksize: 65536 bytes time: 90 ms
    WinAPI: Read 96MB File #Lines: 2097152 Blocksize: 128000 bytes time: 55 ms
    WinAPI: Read 96MB File #Lines: 2097152 Blocksize: 221184 bytes time: 41 ms
    WinAPI: Read 96MB File #Lines: 2097152 Blocksize: 351232 bytes time: 40 ms
    
    
    Autoit: Read 96MB File time: 1606 ms

    Ich muss dazu sagen daß ich zwei SSD's im RAID-0 Verbund laufen habe. Die Zeiten sind der Hammer.

    @UEZ
    Die 165ms sind nicht gelogen. ich habe SSD's und das ist wahrscheinlich der Grund.

    @AspirinJunkie
    Ich habe deine neue Funktion auch getestet und ich bekomme Werte zwischen 270ms - 380ms mit den Default=Auto Settings.
    Ich weis nur nicht warum ich 40ms mit Andy's Testskript erhalte.

    Wenn ich jetzt noch herausfinden würde wie ich als Return der _FileReadFast() die Zeilenanzahl bekomme könnte ich mir die _FileReadToArray() Geschichte sparen.
    Aber da steige ich leider wissenstechnisch momentan noch aus.

  • Hi, das Zählen der Zeilen ist mit dem Assemblercode recht schnell erledigt, in deinem Fall sicherlich nur einige Millisekunden...

    AutoIt
    $asmcode = "0x8B7C24048B4C24088B54240C88D6C1E20888F2C1E20888F2660F6EC2660F70C0005583EC1089E3C70300010102C7430401020203C7430801020203C7430C0203030431F631ED660F6FC8660F740C37660FD7D185D2742989D083E00FD701C5C1EA0489D083E00FD701C5C1EA0489D083E00FD701C5C1EA0489D083E00FD701C583C61039CE76BF89E883C4105DC3"
    $codestruct = DllStructCreate("byte[" & StringLen($asmcode) / 2 - 1 & "]") ;speicher für asmcode...
    DllStructSetData($codestruct, 1, $asmcode) ;...mit code füllen


    erstellt einen Speicherbereich und schreibt den Code hinein.

    AutoIt
    $ret = DllCallAddress("uint:cdecl", DllStructGetPtr($codestruct), "ptr", $filestructptr, "uint", $filesize, "dword", 0x0A)
    $nr_lf = $ret[0]  ;Anzahl der LF

    ruft den Code auf und gibt die Anzahl der LF zurück.
    $filestructptr ist der Pointer auf den von "readfile" verwendeten Puffer ( Achtung, Puffer PLUS einmal die Blockgröße! ), $filesize enthält die Dateigröße.

    Ich denke, irgendwer wird diese Zeilen in die _FileReadFast()-Funktion einbauen können und per @extended die Anzahl der LF ausgeben...

    //EDIT
    Dann schreibe ich auch das Pendant in 64-Bit :D

    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

    Einmal editiert, zuletzt von Andy (21. März 2016 um 07:04)

  • Wenn ich jetzt noch herausfinden würde wie ich als Return der _FileReadFast() die Zeilenanzahl bekomme

    Die Funktion liefert den Dateiinhalt.
    In dem Fall als String.
    In diesem muss man nur noch die Zeilenenden zählen.
    Wie das geht haben wir oben schon erläutert.
    Z.B. über StringReplace - nur das die FileRead durch FileReadFast ersetzt wird.
    Schneller geht es dann sicherlich über Andys ASM-Code.
    Obwohl ich mir da noch unschlüssig bin wie sich die Funktion bei UTF-8 oder anderem verhält.

    Ansonsten stocher ich noch bisschen im Dunkeln was die Zeiten bei Andys Skript angeht.
    Plausibel erscheinen mir auf ner alten Notebookfestplatte Einlesezeiten von 78ms für 100mb überhaupt nicht.
    Da spielen noch irgendwelche anderen Effekte rein.
    Da fehlt mir aber ne ganze Menge Hintergrundwissen zum Caching von Windows und derartigem.

  • @AspirinJunkie,
    nachdem ich mir deinen Code angeschaut hatte, frage ich mich auch, wieso ich in der ReadFile()-Funktion überhaupt blockweise Daten auslese ;(
    Die komplette Dateigröße hineingeworfen reicht, um ein gutes Ergebnis zu erzielen, der Overhead des Block-Loops fällt somit komplett weg.

    Das Einlesen der 100MB in den Speicher dauert bei mir ca. 100ms, Übertragung aus der Struct in die Variable $string=dllstructgetdata(blablub) dauert EINE SEKUNDE! Die zehnfache Zeit für ein simples memcopy...ohne Worte.
    Irgendetwas innerhalb von AutoIt werkelt da sehr gemächlich, und wahrscheinlich (eher sicher) sind es nicht die API-Funktionen, wie wir hier gezeigt haben.


    Obwohl ich mir da noch unschlüssig bin wie sich die Funktion bei UTF-8 oder anderem verhält.

    Das Problem ist, dass eine Datei grundsätzlich als ASCII eingelesen wird. Erst im Nachhinein wird versucht, durch diverse Verfahren herauszufinden, in welcher Kodierung die "Datei" vorliegt. Es gibt Betriebssysteme, in welchen es eine Dateibeschreibung gibt, in der bspw. stehen würde, dass eine UTF8/16-Kodierung vorliegt. Das hat den immensen Vorteil, die langwierigen "Testverfahren" garnicht erst einsetzen zu müssen.
    UTF8/16 benutzen für EndOfLine 0x000D und/oder 0x000A statt Ascii 0x0D0A (CRLF) , haben also mitten im "Text" zwangsläufig Nullbytes. Sind diese vorhanden, zählt man statt 0x0A eben die 0x000A...

    Plausibel erscheinen mir auf ner alten Notebookfestplatte Einlesezeiten von 78ms für 100mb überhaupt nicht.

    Ja, 100-200MB pro Sekunde sollten eher hinkommen.
    Ich werde mal gesamte Verzeichnisse einlesen, bei hunderten unterschiedlicher Dateien sollte sich ein plausibler Mittelwert ergeben.

    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

    Einmal editiert, zuletzt von Andy (21. März 2016 um 21:27)

  • Das Problem ist, dass eine Datei grundsätzlich als ASCII eingelesen wird. Erst im Nachhinein wird versucht, durch diverse Verfahren herauszufinden, in welcher Kodierung die "Datei" vorliegt.

    Ich meinte nicht das Einlesen der Datei sondern deine Zeilenendezählung. Die arbeitet doch Byteweise oder?

    UTF8/16 benutzen für EndOfLine 0x000D und/oder 0x000A statt Ascii 0x0D0A (CRLF) , haben also mitten im "Text" zwangsläufig Nullbytes.

    Eigentlich nur UTF-16. Bei UTF-8 sollte es auch weiterhin noch 0D0A sein. Diese Zeichen befinden sich ja noch im ASCII-Bereich und sollten bei UTF-8 ja auch nur 1 Byte verbrauchen.
    Aber genau die Fälle meinte ich eigentlich.
    Gehen wir mal von einem Fall aus, dass in der Datei das Zeichen č(010D) oder ȍ(020D) vorkommen.
    Wenn die Kodierung außer Acht gelassen wird und nur Byteweise nach 0D oder 0A gesucht wird werden so diese fälschlicherweise als Zeilenende erkannt.

    Käme deine ASM-Code mit sowas klar? - ich frage weil verstehen werde ich den Code eh nie ;)