Wird bei DllImport die DLL-Datei pro Aufruf geladen ?



  • Ich habe ein Compare-Programm gemacht, dabei werden relativ große Blöcke der zu vergleichenden Dateien eingelesen ( 1 GB ) und der Restblock. Diese werden nach dem Einlesen verglichen (Block von Datei 1 mit Block von Datei 2). Mit einem simplen Byte-Array Vergleich (byte-weise for-Schleife) dauert das ganze bei ca. 18 TB Datenmenge ca. 28 Minuten.
    Ich habe das gleiche Programm auch in Delphi geschrieben und dort den Blockvergleich in Assembler gemacht, da dauert das gleiche nur 35 Sekunden. Bei Delphi ist die Assembler-Routine natürlich wie alles andere auch, fester Bestandteil der EXE und wird nur einmal geladen.
    Um im C# Programm die Laufzeit zu verkürzen habe ich in C++ den Blockvergleich in einer DLL für C# mit __asm gemacht (analog dem Delphi-Programm).
    Die Laufzeit habe ich um ein paar Sekunden verkürzt, statt 28' 08" dauert es 27' 50".
    Über den Debugger habe ich gesehen, dass die DLL Datei erst zum Zeitpunkt des Aufrufes einer Routine der DLL auf Vorhandensein geprüft und geladen wird, erst danach wird die Routine ausgeführt. Das mag kein Problem sein, wenn so ein Aufruf nur wenige male stattfindet. Andernfalls ist es eine Super-Bremse.

    Gibt es eine Möglichkeit, die DLL zum Compilierungszeitpunkt in die EXE einzubinden oder eine andere Möglichkeit dafür zu sorgen, dass die DLL nur einmal geladen wird und danach im Speicher verbleibt, bis das Programm beendet wird, um den zyklischen Ladevorgang zu verhindern ?

    Ich habe auch andere Blockvergleichsroutinen benutzt
    memcmp aus msvcrt.dll (auch DllImport)
    RtlCompareMemory aus ntdll.dll (auch DllImport)

    Das bringt vermutlich aus dem gleichen Grund keine Laufzeitverbesserung.

    Hier die DLL-Routine für den Puffer-Vergleich

    //puvgl.cpp  Vergleich zweier Pufferinhalte
    
    extern "C" __declspec(dllexport) int PuVgl(char *b1, char *b2, int Psize)
    {
       int rc = 0;
    
       __asm
       {
          PUSH  ESI
          PUSH  EDI
          PUSH  EBX
          PUSH  ECX
          PUSH  DS
          PUSH  SS
    
          XOR   EBX,EBX     // EBX löschen 
          MOV   ESI,[b1]    // Puffer Datei 1
          MOV   EDI,[b2]    // Puffer Datei 2
          MOV   ECX,Psize   // Länge in Bytes 
    
    
          MOV   BL,CL       // Bit 0+1 in BL retten 
          SHR   ECX,2       // Länge / 4 : in DWords 
          AND   BL,3        // Restlänge = 0 ? 
          JZ    CmpDW       // --> ja, nur DWords vergleichen 
    
          CLD
          REPZ  CMPSD       // DWords vergleichen 
          JNZ   Diff        // --> Differenz 
          MOV   ECX,EBX     // Restlänge 1..3 
          CLD
          REPZ  CMPSB       // Restlänge vergleichen 
          JZ    Ende
         Diff:
          POP   SS
          POP   DS
          MOV   [rc],1      // RCode = 1 : Differenz ! 
          PUSH  DS
          PUSH  SS
          JMP   Ende
    
         CmpDW:
          CLD
          REPZ  CMPSD       // DWords vergleichen 
          JNZ   Diff        // --> Differenz 
    
         Ende:
          POP   SS
          POP   DS
          POP   ECX
          POP   EBX
          POP   EDI
          POP   ESI
       }
    
       return rc;
    }
    


  • Wie hast du die DLL in C# eingebunden? Via P/Invoke? Da wird die DLL afaik nur einmal geladen und bleibt dann geladen. Zum Compilier-Zeitpunkt einbinden macht ja in Zusammenhang mit "DLL" gar keinen Sinn, der Sinn einer DLL ist ja, dass sie dynamisch zur Laufzeit geladen wird, oder?

    MfG SideWinder



  •         [DllImport(@"U:\c\2\puvgl.dll", CallingConvention = CallingConvention.Cdecl)]
            static extern int PuVgl(byte[] b1, byte[] b2, int size);
    

    So habe ich sie eingebunden.
    Wie geht das mit P/Invoke ?

    Es müsste ja keine DLL sein, es könnte auch ein LIB für den Linker sein, wenn das geht.
    Ich habe nur einen Weg gesucht, die schnelle ASS-Routine in C# zu nutzen, da C# selbst ja keinen Assembler-Code mehr erlaubt, wie in C++ oder Delphi weiterhin möglich.



  • DLLs die du per DllImport einbindest bleiben schon geladen. Sie werden nur "lazy" geladen, also beim 1. Aufruf. Weitere Aufrufe gehen aber sehr schnell.

    Ich vermute das wird aus ganz anderen Gründen langsam sein:

    1. Die Byte-Arrays werden beim Aufruf kopiert werden.

      Du könntest probieren statt der Byte-Arrays IntPtr zu übergeben. Zum Aufruf musst du die Arrays dann manuell pinnen und die Adresse des gepinnten Arrays übergeben.

      Wie das geht ist z.B. hier beschrieben: https://stackoverflow.com/questions/23254759/how-can-i-pin-an-array-of-byte
      Wobei du das handle.Free(); in einem finally Block machen solltest. Wenn das nämlich nicht aufgerufen wird, dann bleibt das Objekt ewig gepinned = memory leak.

    2) Die üblichen Funktionen mit denen man in .NET Zeugs aus Dateien lädt geben ein neues Byte-Array zurück.

    Das macht dann mächtig Druck auf den LOH (Large-Object-Heap) und führt zu entsprechend vielen LOH Collections. Und die sind halt auch nicht gratis.

    Das wiederrum könntest du lösen indem du auch die Funktionen zum Laden der Daten aus den Files in C++ implementierst.

    Und noch ein zusätzlicher Tip: Verwende auf der C++ Seite memcmp statt deines Assembler-Codes. memcmp ist sehr gut optimiert und sollte viel schneller sein als dein Code.

    EDIT: OK, (2) ist Quatsch, da hatte ich irgendwas falsch in Erinnerung.



  • Hallo hustbaer, ich danke Dir für Deine Antworten.
    Ich denke aber, die deutlich längere Laufzeit liegt daran, das DLL bei jedem Aufruf neu geladen werden.
    Ich habe deshalb ein kleine C++ DLL gemacht, die lediglich eine Zahl weiterzählt.
    Und dazu ein C# Testrahmen, der eine Zählung mit und ohne diese DLL macht.
    Das Ergebnis: mit DLL = 65 Sekunden / ohne DLL = 1 Sekunde - als ca. das 65-fache an Laufzeit.
    Der cl-Compiler erstellt ja auch eine obj-Datei, kann man die nicht an die C# EXE hinzu linken ?

    Ich compiliere die DLL im x86 Native Tools Command Prompt for VS2022 Fenster mit folgendem Kommando

    U:\c\1>cl Zaehle.cpp /LD /EHsc
    Microsoft (R) C/C++-Optimierungscompiler Version 19.33.31630 für x86
    Copyright (C) Microsoft Corporation. Alle Rechte vorbehalten.
    
    Zaehle.cpp
    Microsoft (R) Incremental Linker Version 14.33.31630.0
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /out:Zaehle.dll
    /dll
    /implib:Zaehle.lib
    Zaehle.obj
       Bibliothek "Zaehle.lib" und Objekt "Zaehle.exp" werden erstellt.
    
    U:\c\1>
    

    So sieht die DLL aus

    //Zaehle.cpp
    #include <stdio.h>
    
    extern "C" __declspec(dllexport) int Zaehle(int Zahl)
    {
    	return Zahl + 1;
    }
    

    und so das C# Testprogramm dazu

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace TestDLL
    {
        internal class Program
        {
            static string displayTime() // gibt zeit + datum zurück
            {
                string time = null;
                time += System.DateTime.Now.Hour + ":";
                time += System.DateTime.Now.Minute + ":";
                time += System.DateTime.Now.Second;
                return time;
            }
    
            [DllImport(@"U:\c\1\Zaehle.dll", CallingConvention = CallingConvention.Cdecl)]
            static extern int Zaehle(int Zahl);
    
            static void Main(string[] args)
            {
                int m = 1000000000;
    
                Console.WriteLine("START mit DLL - " + displayTime());
                int i = 0;
                while (i < m)
                { i = Zaehle(i); } // in DLL zählen
                Console.WriteLine("ENDE mit DLL - " + displayTime() + " Anzahl Aufrufe= " + i.ToString());
    
                Console.WriteLine("START ohne DLL - " + displayTime());
                i = 0;
                while (i < m)
                {  i++; } // ohne DLL
                Console.WriteLine("ENDE ohne DLL - " + displayTime() + " Anzahl Aufrufe= " + i.ToString());
    
                Console.ReadKey();
            }
            /*  E R G E B N I S :
             START mit DLL - 10:54:42
             ENDE mit DLL - 10:55:47 Anzahl Aufrufe= 1000000000 = 65 Sekunden
             START ohne DLL - 10:55:47
             ENDE ohne DLL - 10:55:48 Anzahl Aufrufe= 1000000000 = 1 Sekunde
             */
        }
    }
    


  • Rufe mal nur einmalig die DLL-Funktion auf und messe dessen Zeit (am besten per Stopwatch in Millisekunden bzw. Ticks), dies ist dann die zusätzliche Zeit für das Laden der DLL.

    Aber jeder DllImport-Aufruf ist wegen des zusätzlichen Marshallings zeitaufwendiger als ein interner Methodenaufruf.
    Du solltest daher die externen Aufrufe begrenzen, d.h. wenn du eine Schleife benötigst, dann sollte diese komplett in der DLL stattfinden.

    PS: Weil du danach gefragt hast: der Aufruf mittels DLLImport ist P/Invoke.



  • @Th69
    mit einmaligem Aufruf - das ist logisch, geht aber nicht, wenn man nur bestimmte Aufgaben, wie einen Vergleich von zwei Puffern auslagern will und nicht das ganze Programm. Ich habe das gleiche Programm in Delphi - und das funktioniert sehr schnell. In C# kann ich die Ass-Routine nicht benutzen, deshalb die Auslagerung in eine C++ DLL.
    Kann man denn die vom cl-Cpompiler (VS 2022 c++) erstellten Dateien LIb, OBJ usw. nicht benutzen und damit auf DllImport verzichten ?
    Oder kann man bei C# keine OBJ Dateien von anderen Compilern benutzen ?
    Man benutzt ja auch System-Routinen, die bestimmt nicht alle in C# programmiert sind.
    Diese Dateien erstellt der cl-Compiler/Linker

    22.09.2022  10:09                25 MakeZaehleDLL.bat
    22.09.2022  10:12               114 Zaehle.cpp
    22.09.2022  10:12            80.896 Zaehle.dll
    22.09.2022  10:12               632 Zaehle.exp
    22.09.2022  10:12             1.678 Zaehle.lib
    22.09.2022  10:12               657 Zaehle.obj
                   6 Datei(en),         84.002 Bytes
    


  • Ich bin jetzt nicht sonderlich fit in C#, aber ich gehe davon aus, dass die C# Beispielschleife einfach schon zur Compiletime ausgewertet wird, es stehen ja alle Informationen zur Verfügung. Um das etwas realistischer zu gestalten könnte man das m als Kommandozeilenparameter übergeben.

    Was ich online noch gefunden habe, um dll Aufrufe etwas performanter zu machen: [SuppressUnmanagedCodeSecurity] (https://learn.microsoft.com/en-us/visualstudio/code-quality/ca2118?view=vs-2022)



  • BTW: Du kannst Assembler in C# schon auch nutzen wenn es sein muss: https://www.codeproject.com/Tips/855149/Csharp-Fast-Memory-Copy-Method-with-x-Assembly-Usa

    Mich wundert ein bisserl, dass das Byte-Array-Comparen wirklich so viel langsamer sein soll in .NET 6 als mit Assembler (das sind ja Größenordnungen von denen du da sprichst...), also vllt. ist da auch nur eine Performance-Krücke in deinem C#-Code.

    MfG SideWinder



  • SideWinder,
    ich vergleiche etwa 18 TB Daten, es sind 768 Unter-Ordner und 15.214 Dateien.
    Den maximale Puffer zum Einlesen habe ich ab 1 GB festgelegt.
    18 TB sind etwa 18.000 GB. Kleinere Dateien ( < 1 TB ) werden komplett eingelesen und verglichen.
    Die DLL-Routine mit dem Compare-UP (Vergleich von den Pufferinhalten, max. 1 TB) wir also etwa 18.000 mal aufgerufen. Da wird bei DllImport bei jedem Aufruf der Routine die DLL-Routine geladen usw. Deshalb wahrscheinlich diese lange Laufzeit von knapp einer halben Stunde. Mein Delphi-Programm vergleicht diese Datenmenge in weniger als 40 Sekunden.

    CompHKw - Dateien Vergleichen - (C) 07.09.2022 H.Kresse, Dresden <= Das ist das Delphi-Programm
    ---------------------------------------------------------
    Parameter: "U:\#hk" "U:\#hk" "/U" "/VJ" "/F" "/M:2" "/A"
    ---------------------------------------------------------
    Vergleich: U:\#hk\
          mit: U:\#hk\
    ~~~~~~~~~~~~~~~~~~~
    15.214.Datei: U:\#hk\ZX81\Tapes\super9.tzx
    =================================================================
        768 Unter-Pfade gefunden   - identisch.
     15.214 Dateien     gefunden   - und verglichen...
     15.214 Dateien     verglichen - identisch.
          Es wurden 18.068.947.238 Bytes = 17.645.456 K-Bytes verglichen
          Start 13:09:19  Ende 13:09:56
    ========================================================<Ende>===
    Linke MausTaste / ESC = Schließen   Rechte MausTaste / F1 = Info
    

    Wenn ich statt der DllImport-Routinen die Puffer mit einer for-Schleife (ohne DllImport-Aufruf) vergleiche, dann dauert das auch etwa 30 Minuten.
    Mein Ziel war, das mit dem schon etwas betagtem Delph7 geschriebene Programm mit C# zu realisieren.
    Ich brauche das Programm, um nach den Sicherungen die gesicherten Daten mit den Originalen zu vergleichen, um einen Haken an die Sicherung zu machen.
    Der Puffervergleich mit Assembler ist wirklich extrem schnell, das wird von nur drei entscheidenden Befehlen gemacht. Eigentlich vergleicht nur der Befehl REPZ CMPSD die beiden Puffer von je 1 TB Länge. Man könnte jetzt auch 16 Byte lange Worte benutzen, da wäre es noch etwas schneller.

          CLD
          REPZ  CMPSD       // DWords vergleichen   ECX enthält die Anzahl DWORDS (8 Bytes)
          JNZ   Diff        // --> Differenz 
    

    Dagegen ist die C# Vergleichsroutine simpel und extrem langsam

                    for (int i = 0; i < VerglLen; i++)
                    {
                        if (ByPu1[i] != ByPu2[i]) // ungleiche Bytes gefunden ?
                        {
                            if (AnzDiff == 0) // Erste Differenz dieser Datei ?
                            {
                                sZei = sZei + "- ==> Unterschiede gefunden";
                                if (!Flag_F)
                                {
                                    PutListV(sZei);
                                }
                                sZei = "";
                            }
                            rc = 1; // es gibt Differenzen
                            isDiff = 1;
                            AnzDiff++;
                            GesDiff++; // Differenzen (Anzahl Bytes)
    
                            if (AnzDiff <= Anz_M)
                            {
                                ZeigeDiff(VerglOffset + i, ByPu1[i], ByPu2[i], VerglLen);
                            }
                        }
                    } // for (int i = 0; i < VerglLen; i++)
    

    Mein PC hat eine sehr schnelle CPU (AMD Ryzen 9 5900X), einen schnellen RAM (3200 MHz) und das benutze Laufwerk U: ist eine sehr schnelle m.2 (Samsung 980 PRO EVO). An der Hardware kann die gebremste Laufzeit nicht liegen.



  • Wir wissen nicht, wie der Vergleich in Delphi ausgeführt wird, in C# vergleichst du aber einzelne Bytes, das kann relativ teuer werden. Am schnellsten wird es wohl sein, wenn man Blöcke vergleicht, die der Registerbreite deiner CPU entsprechen (also 64bit). Wenn die unterschiedlich sind kann man sich im Detail angucken, wo sich die Blöcke tatsächlich unterscheiden.



  • Probiere mal direkt in C# (sofern du .NET Core 2.1 oder neuer, also z.B. NET 5 oder 6 benutzt):

    // byte[] is implicitly convertible to ReadOnlySpan<byte>
    static bool ByteArrayCompare(ReadOnlySpan<byte> a1, ReadOnlySpan<byte> a2)
    {
        return a1.SequenceEqual(a2);
    }
    

    (u.a. aus Comparing two byte arrays in .NET)

    Wenn du aber bei deinem Assembler (bzw. C++) Code bleiben willst, dann könntest du auch ein C++/CLI-Projekt erstellen und dieses direkt als Referenz einbinden (aber auch hier wird ein Marshalling durchgeführt).



  • @DocShoe , in Delphi mache ich das mit den gleiche ASS-Befehlen, wie in C++. Und das klappt seit mehr als 20 Jahren so.

    { ----------------------------------------------------------------------
      -                     Puffer 1 und 2 vergleichen                     -
      ---------------------------------------------------------------------- }
    Function CompPuff : Boolean;
      Var
        RCode  : Byte;
      Begin
        asm
          PUSH  ESI
          PUSH  EDI
          PUSH  EBX
          PUSH  ECX
          PUSH  DS
          PUSH  SS
    
          XOR   EBX,EBX               { EBX löschen }
          MOV   [RCode],BL            { RCode löschen }
          MOV   ESI,Offset Puffer1
          MOV   EDI,Offset Puffer2
          MOV   ECX,[Laenge1]         { Länge in Bytes }
    
          MOV   BL,CL                 { Bit 0+1 in BL retten }
          SHR   ECX,2                 { Länge / 4 : in DWords }
          AND   BL,3                  { Restlänge = 0 ? }
          JZ    @CmpDW                { --> ja, nur DWords vergleichen }
    
          CLD
          REPZ  CMPSD                 { DWords vergleichen }
          JNZ   @Diff                 { --> Differenz }
          MOV   ECX,EBX               { Restlänge 1..3 }
          CLD
          REPZ  CMPSB                 { Restlänge vergleichen }
          JZ    @Ende
         @Diff:
          POP   SS
          POP   DS
          MOV   [RCode],1             { RCode = 1 : Differenz ! }
          PUSH  DS
          PUSH  SS
          JMP   @Ende
    
         @CmpDW:
          CLD
          REPZ  CMPSD                 { DWords vergleichen }
          JNZ   @Diff                 { --> Differenz }
    
         @Ende:
          POP   SS
          POP   DS
          POP   ECX
          POP   EBX
          POP   EDI
          POP   ESI
        end;
        if RCode = 1 then CompPuff := True     { Fehler }
                     else CompPuff := False;   { ok.    }
    
      End; { Function CompPuff }
    


  • @hkdd sagte in Wird bei DllImport die DLL-Datei pro Aufruf geladen ?:

    @DocShoe , in Delphi mache ich das mit den gleiche ASS-Befehlen, wie in C++. Und das klappt seit mehr als 20 Jahren so.

    Ich kann kein x86 Assembler, aber ich sehe, dass dort DWORDs und keine BYTEs verglichen werden, das ist zumindest ein Unterschied zwischen asm und C#. Man müsste mal schauen, wie der IL Code und letztendlich der ASM Code des C# Kompilats aussieht.



  • @hkdd sagte in Wird bei DllImport die DLL-Datei pro Aufruf geladen ?:

    REPZ CMPSD

    Dieser Befehl arbeitet so, wie eine ganze Befehlsfolge.
    zuvor CLD: wenn das D-Flag gelöscht ist, wird aufsteigen verglichen, andernfalls absteigend.
    ESI und EDI sind die Adressen der zu vergleichenden Puffer.
    Bei CMPSD bedeutet D, es werden Doppel-Worte = je 4 Bytes verglichen (CMPSB : es werden Bytes verglichen).
    Ist ein Vergleich gemacht und es gibt es keine Differenz, werden die Register ESI und EDI jeweils um 4 Bytes erhöht (auf das nächste Doppelwort im Puffer)
    Weiterhin wird die Anzahl der zu vergleichenden Doppelworte in ECX um 1 vermindert, wenn ECX dabei =0 wird, ist der Befehl beendet, andernfalls (ECX > 0) und REPZ vor CMPSD, dann findet der Vergleich des nächsten DWORD statt. REPZ bewirkt, das CMPSD so lange ausgeführt wird, bis entweder ECX = 0 wird oder eine Differenz auftritt.
    Falls eine Differenz erkannt wird, wird der Vergleich beendet (ESI und EDI stehen auf dem DWORD mit der Differenz) und der folgende JZ sprung wird nicht ausgeführt, "Z" ist nur dann der Fall, wenn ECX=0 und alles verglichenen DWORDs sind identisch



  • @hkdd sagte in Wird bei DllImport die DLL-Datei pro Aufruf geladen ?:

    Hallo hustbaer, ich danke Dir für Deine Antworten.
    Ich denke aber, die deutlich längere Laufzeit liegt daran, das DLL bei jedem Aufruf neu geladen werden.

    Das ist sicher nicht so. Wenn du mir nicht glaubst, dann bau doch einfach ein DllMain in deine DLL ein und pack ein paar OutputDebugString rein. Dann siehst du selbst dass sie nur 1x geladen wird.

    Ich habe deshalb ein kleine C++ DLL gemacht, die lediglich eine Zahl weiterzählt.
    Und dazu ein C# Testrahmen, der eine Zählung mit und ohne diese DLL macht.
    Das Ergebnis: mit DLL = 65 Sekunden / ohne DLL = 1 Sekunde - als ca. das 65-fache an Laufzeit.

    D.h. der Overhead des PInvoke Aufrufs ist ca. 64x das was ein Schleifendurchlauf braucht der einen int addiert. Das muss dir doch selbst klar sein dass das mehrere Grössenordnungen davon entfernt ist was das Entladen+erneute Laden einer DLL brauchen würde. Und es ist auch völlig irrelevant wenn du pro Aufruf einen 1 GB Block kopierst. Ich meine du machst da 1 Mrd Aufrufe, vs. dein Backup/Verify-Programm wo du 18k Aufrufe machst.

    Aber mal was anderes: Wie lange braucht dein C# Programm denn wenn du die PInvoke Aufrufe einfach weglässt? Also wenn du einfach nur die Daten aus den beiden Files liest, ohne zu vergleichen.

    Der cl-Compiler erstellt ja auch eine obj-Datei, kann man die nicht an die C# EXE hinzu linken ?

    Nein, geht nicht.

    Man benutzt ja auch System-Routinen, die bestimmt nicht alle in C# programmiert sind.

    Ja. Über PInvoke. Also genau das DllImport von dem du fälschlicherweise annimmst es sei so langsam. Obwohl du dir eigentlich schon selbst bewiesen hast dass es sehr schnell ist (dein "Zaehle" Programm).



  • @hustbaer sagte in Wird bei DllImport die DLL-Datei pro Aufruf geladen ?:

    Wie lange braucht dein C# Programm denn wenn du die PInvoke Aufrufe einfach weglässt?

    Ich danke Dir für Deine Beharrlichkeit - Du hast Recht und ich ärgere mich, dass ich nicht selbst einmal diesen Versuch gemacht habe.
    Ohne jeden Vergleich läuft das Programm mit den gleichen Dateien 28' 10", d.h. der Vergleich spielt keine Rolle bei der Laufzeit und deshalb ist meine Vermutung DllImport sei der Bremser offenbar falsch - Du hattest Recht ☺ .
    Dann sind also die Zugriffsmethoden, die ich bei C# benutze im Vergleich zu denen in Delphi die Bremser.

    So mache ich das in C#
    Zunächst lese ich die beiden Dateien als Stream ein,
    später dann immer Blöcke aus dem Stream in einer max. Blocklänge von 1 TB, falls die Dateien > 1 TB sind.
    Diese Blöcke werden anschließend verglichen

                    FileStream fs1 = new FileStream(Dsn1, FileMode.Open, FileAccess.Read);
                    FileStream fs2 = new FileStream(Dsn2, FileMode.Open, FileAccess.Read);
    
                    long VerglRestLen = VerglLenGes;
                    long VerglOffset = 0;
                    int VerglLen = 0;
                    int isDiff = 0;
    
                    ByPu1 = new byte[(int)MaxPufL + 0x1000];
                    ByPu2 = new byte[(int)MaxPufL + 0x1000];
    
                NxtPuVergleich:
    
                    if (VerglRestLen > MaxPufL)
                    { VerglLen = (int) MaxPufL; }
                    else
                    { VerglLen = (int) VerglRestLen; }
    
                    fs1.Read(ByPu1, 0, (int)VerglLen);
                    fs2.Read(ByPu2, 0, (int)VerglLen);
    

    Hier Schnipsel des Delphiprogrammes

           {$I-}
            AssignFile(Handle1, DSN1);
            FileMode := 0;    { Reset nur für Eingabe }
            Reset(Handle1,1);
            Error1 := IOResult; { Null = Open OK }
            {$I+}
            :::
            {$I-}
            BlockRead(Handle1, Puffer1, ReadLen, Laenge1);
            Error1 := IOResult; { Null = Read OK }
            if Error1 <> 0 then
              Laenge1 := 0;
            {$I+}
    

    Das sind im Prinzip die Zugriffe, die es bereits unter DOS gab und die ich in Borlands Turbo Pascal schon benutzt habe.
    Die kann man im aktuellen Delphi 10 aber auch noch benutzen.

    Ich muss nun prüfen, ob man so elementare Zugriffe zum simplen sequentiellen byteweisen lesen von Blöcken einer beliebigen Datei auch in C# machen kann. Die stream-Methode ist für so ein Programm wahrscheinlich völlig ungeeignet. Bei C++ gibt es auch noch diese ursprünglichen DOS-Zugriffe.



  • Wenn du´s wirklich ausreizen möchtest kannst du ja mal diesen Ansatz ausprobieren:

    1. beide Dateien mit OpenFile öffnen
    2. FileMapping für beide Dateien erzeugen (CreateFileMapping)
    3. blockweise jeweils die gleichen X Byte pro Datei in den Speicher einblenden (MapViewOfFile)
    4. Speicherbereiche per RtlCompareMemory vergleichen (Vorsicht: undokumentierte Funktion)
    5. Blockgröße und Offset anpassen, weiter bei 3) oder Ende, falls Dateiende erreicht

    Vielleicht kann Windows da intern etwas optimieren.



  • Für .NET gibt es dafür die Klasse MemoryMappedFile, s.a. Benchmark-Vergleich in C# MemoryMappedFile Example.

    PS:
    @hkdd sagte in Wird bei DllImport die DLL-Datei pro Aufruf geladen ?:

    später dann immer Blöcke aus dem Stream in einer max. Blocklänge von 1 TB, falls die Dateien > 1 TB sind.

    Du meinst GB??!



  • @Th69

    Ich wusste nicht, dass es sowas in .NET gibt. Aber meine Absicht war eigentlich so viel wie möglich nicht in C# zu machen, sondern möglichst viel von der WINAPI direkt erledigen zu lassen. Mit der MemoryMappedFile Klasse braucht man ja schon wieder .NET Streams, während RtlCompareMemory direkt mit Adressen arbeitet.



  • @Th69 sagte in Wird bei DllImport die DLL-Datei pro Aufruf geladen ?:

    Du meinst GB

    ja, natürlich

    Wie ist das eigentlich bei File.ReadAllBytes
    https://learn.microsoft.com/de-de/dotnet/api/system.io.file.readallbytes?view=net-6.0
    In dem Link werden die möglichen Ausnahmen aufgezeigt.
    Was passiert aber, wenn die Datei so groß ist, dass dafür kein byte[] Array im verfügbaren Speicher angelegt werden kann.
    Muss man das byte[] Array selbst wieder freigeben ?
    Ich benutze deshalb die max.Puffergröße von 1 GB, bei meinem derzeitigen RAM von 32 GB könnte es auch etwas mehr sein.
    Auf meinem PC gibt es aber Dateien, die deutlich größer als 1 TB sind.
    Eine Ausnahme sinngemäß "Datei für vorhandenen Speicher zu groß" habe ich nicht gefunden.

    07.09.2022  09:39    18.353.100.330 2022-09-06 xxx HD.mp4
    08.09.2022  16:22    17.293.742.949 2022-09-07 xxx HD.mp4
    09.09.2022  10:37    17.293.367.198 2022-09-08 xxx HD.mp4
    10.09.2022  07:32     8.115.634.344 2022-09-09 xxx HD.mp4
    22.09.2022  08:38     9.881.955.469 2022-09-21 xxx HD.mp4
    23.09.2022  07:25    15.526.817.738 2022-09-22 xxx HD.mp4
                   6 Datei(en), 86.464.618.028 Bytes
    

Anmelden zum Antworten