Warum klappt Binary bei mir nicht



  • Ich habe gerade 5 ways to compare two byte arrays. Benchmarking (für .NET 4) gefunden.
    Auch wenn dort "nur" relativ kleine Arrays verglichen werden (<= 512KB), so paßt sicherlich der relative Vergleich (unter "Measurement results and first conclusions"), d.h. P/Invoke von memcmp sowie die unsafe-Methode (mit jeweils 8 Byte Vergleich, d.h. so wie auch memcmp intern funktioniert) sind wohl die schnellsten.
    Seit .NET Core bzw. 5/6/7 hat sich sicherlich beim JIT noch etwas verbessert, aber der direkte Byte-Vergleich sowie die LINQ-Methoden werden wohl weiterhin nicht schneller sein.



  • @Th69
    In dem SO Thread den du selbst schon verlinkt hast sind auch ein paar interessante Vergleiche. Anscheinend wurde SequenceEqual in späteren .NET Versionen massiv optimiert: https://github.com/dotnet/runtime/blob/v6.0.4/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs#L1420-L1634

    D.h. man kann sich wohl bei neueren .NET Versionen darauf verlassen dass es schnell ist. Zumindest so lange man es direkt aufruft und nicht irgend ein Linq Zeugs dazwischensteckt.



  • Ich habe mein Testprogramm dahingehend überarbeitet, dass ich nur die Verwaltungsroutine laufen lasse (DIR lesen, Dateien mit anderem DIR synchronisieren usw.) ohne READ und ohne COMPARE.
    Man kann aber auch zusätzlich die READs ausführen ohne COMPARE.
    Die unmöglichen Puffervergleiche sind nicht mit aufgeführt.
    Wenn man COMPARE ausführt, habe ich verschiedene Routinen getestet und hier die besten Ergebnisse.
    SequenceEqual benötigt mehr als die 100-fache Zeit, wie ein einfacher byteweiser for-Vergleich, wie kann man so einen Unsinn zur Verfügung stellen ?
    Diese Methode vergleicht offenbar immer den gesamten Pufferinhalt in der definierten Länge.
    Das ist aber nur eine Maximal-Länge, kleinere Dateien werden auch in diesen Puffer gelesen, z.B. eine 30 Bytes lange ini-Datei.
    Für den notwendigen Vergleich muss man deshalb immer die zu vergleichende Länge mitgeben, die ist meistens deutlich kleiner, als die Pufferlänge.
    Deshalb dauert diese Funktion wahrscheinlich so lange, weil immer der Puffer in der maximal-Länge verglichen wird - und das wahrscheinlich auch noch total uneffektiv. Man müßte für diese Funktion die Puffer für jeder Datei separat in der passenden Länge neu anlegen und danach wieder freigeben - und das nur, weil man dort die relevante zu vergleichende Länge nicht angeben kann.

    Um den for-Vergleich zu optimieren, könnte man über das byte[] Array mittels UNION ein Int64[] Array legen und immer dafür sorgen, dass im Puffer ein Vielfaches von 8 Bytes steht, Dateien die etwas kürzer sind können im Puffer mit 0x00 auf die nächste 8-fache Größe aufgefüllt werden (einfach 8 0x00 anfügen). Man würde in ein byte[] Array einlesen und ein Int64[] Array mit for-Schleife vergleichen. Das würde die Anzahl der Vergleiche auf ein Achtel reduzieren.

    Start = 07.12.2022 07:37:24
    MitRead=False, MitComp=False
    16.777.216 Bytes = Puffergröße
    0 Bytes eingelesen
    770 Directories
    15.228 Dateien
    0 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 07:37:26, Dauer = 00:00:01.2
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Start = 07.12.2022 07:38:16
    MitRead=True, MitComp=False
    16.777.216 Bytes = Puffergröße
    18.069.912.569 Bytes eingelesen
    770 Directories
    15.228 Dateien
    0 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 07:38:27, Dauer = 00:00:11.3
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Start = 07.12.2022 07:39:20
    Vergleichsmethode = SequenceEqual (C# interne Funktion)
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    18.069.912.569 Bytes eingelesen
    770 Directories
    15.228 Dateien
    16.152 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 08:13:19, Dauer = 00:33:59.0
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Start = 07.12.2022 08:14:00
    Vergleichsmethode = MemCmp [DllImport] msvcrt.dll
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    18.069.912.569 Bytes eingelesen
    770 Directories
    15.228 Dateien
    16.152 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 08:14:14, Dauer = 00:00:13.9
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Start = 07.12.2022 08:14:56
    Vergleichsmethode = ForNxt (byte-weiser Vergleich)
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    18.069.912.569 Bytes eingelesen
    770 Directories
    15.228 Dateien
    16.152 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 08:15:38, Dauer = 00:00:41.5
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    Falls es interessiert, hier der Code des Testprogrammes

    using System;
    using System.IO;
    using System.Linq;
    using System.Runtime.InteropServices;
    
    namespace AllFilesInDir // Alle Dateien eines Ordners (Kommandozeile) lesen
    {
        internal class Program
        {
            public static bool MitRead = true;  // Read abschalten TEST TEST TEST
            public static bool MitComp = true;  // Vergleich abschalten TEST TEST TEST
    
            public static int VergleichsMethode = compForNxt;
    
            public static string[] VglMethode =
            {
                "SequenceEqual (C# interne Funktion)",
                "MemCmp [DllImport] msvcrt.dll",
                "ForNxt (byte-weiser Vergleich)"
            };
    
            public const int compSequenceEqual = 0;
            public const int compMemCmp        = 1;
            public const int compForNxt        = 2;
    
            public static Int64 GesBytes   = 0; // Summe  aller gelesenen Bytes
            public static int Datei2Fehlt  = 0; // Anzahl fehlender Dateien in Directory 2
            public static int Pfad2Fehlt   = 0; // Anzahl fehlender Unterpfade in Directory 2
            public static int LenDiff      = 0; // Anzahl Dateien mit unterschiedlicher Länge
            public static int ByteDiff     = 0; // Anzahl Dateien mit inhaltlichen Differenzen
            public static int AnzDir       = 0; // Anzahl aller Directories incl. Start-Dir
            public static int AnzFiles     = 0; // Anzahl aller gelesenen Dateien
            public static int AnzPuff      = 0; // Anzahl aller gelesenen Puffer
            public const int MaxPuffL = 0x01000000; // 16.777.216 Bytes = Pufferlänge
            public static byte[] ByPu1 = new byte[MaxPuffL + 0x1000]; // Eingabepuffer + Reserve
            public static byte[] ByPu2 = new byte[MaxPuffL + 0x1000]; // Eingabepuffer + Reserve
    
            [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
            static extern int memcmp(byte[] b1, byte[] b2, long count);
    
            static void Main(string[] args)
            {
                DateTime dtStart= DateTime.Now;  // Start-Zeitpunkt merken
                Console.WriteLine("Start = "+dtStart.ToString());
                Console.WriteLine("Vergleichsmethode = " + VglMethode[VergleichsMethode]);
                Console.WriteLine("MitRead=" + MitRead + ", MitComp=" + MitComp);
                Console.WriteLine(MaxPuffL.ToString("###,###,###,###,##0") + " Bytes = Puffergröße");
                string StartPfad1 = args[0]; // Pfad 1 aus Kommandozeile holen
                string StartPfad2 = args[1]; // Pfad 2 aus Kommandozeile holen
                PfadVerarbeiten(StartPfad1,StartPfad2);
                Console.WriteLine(GesBytes.ToString("###,###,###,###,##0") + " Bytes eingelesen");
                Console.WriteLine(AnzDir.ToString("###,###,###,###,##0") + " Directories");
                Console.WriteLine(AnzFiles.ToString("###,###,###,###,##0") + " Dateien");
                Console.WriteLine(AnzPuff.ToString("###,###,###,###,##0") + " Puffer");
                Console.WriteLine(Datei2Fehlt.ToString("###,###,###,###,##0") + " fehlende Dateien");
                Console.WriteLine(Pfad2Fehlt.ToString("###,###,###,###,##0") + " fehlende Unterpfade");
                Console.WriteLine(LenDiff.ToString("###,###,###,###,##0") + " Dateien mit unterschiedlicher Länge");
                Console.WriteLine(ByteDiff.ToString("###,###,###,###,##0") + " ungleiche Dateien");
                DateTime dtEnde = DateTime.Now;  // Ende-Zeitpunkt merken
                System.TimeSpan Dauer = dtEnde - dtStart;
                Console.WriteLine("Ende  = " + dtEnde.ToString()+", Dauer = "+Dauer.ToString().Substring(0,10));
                Console.ReadLine(); // Warten auf ENTER
            }
            static void PfadVerarbeiten(string pPfad1, string pPfad2) // wird rekursiv aufgerufen
            {
                AnzDir++; // Anzahl verglichene DIR gesamt
    
                DirectoryInfo di1 = new DirectoryInfo(pPfad1);     // Aktuelles Directory
                FileInfo[] fi1 = di1.GetFiles("*.*");              // Dateien im Directory
                int FilesAnz1 = fi1.Count();                       // Anzahl Dateien im Directory
                DirectoryInfo[] ui1 = di1.GetDirectories("*.*");   // Unterordner im Directory
                int DirAnz1 = ui1.Count();                         // Anzahl Unterordner im Directory
    
                DirectoryInfo di2 = new DirectoryInfo(pPfad2);     // Aktuelles Directory
                FileInfo[] fi2 = di2.GetFiles("*.*");              // Dateien im Directory
                int FilesAnz2 = fi2.Count();                       // Anzahl Dateien im Directory
                DirectoryInfo[] ui2 = di2.GetDirectories("*.*");   // Unterordner im Directory
                int DirAnz2 = ui2.Count();                         // Anzahl Unterordner im Directory
    
                bool gefunden = false;
                string SuchName = "";
    
                for (int i = 0; i < FilesAnz1; i++) // zu allen Dateien aus Pfad1 zugehörige Datei in Pfad2 suchen
                {
                    gefunden = false;
                    SuchName = fi1[i].Name.ToLower(); // ohne Lw:\Pfad
    
                    for (int j = 0; j < FilesAnz2 && !gefunden; j++) 
                    {
                        if (SuchName.CompareTo(fi2[j].Name.ToLower()) == 0) 
                        {
                            gefunden = true;
                            if (fi1[i].Length == fi2[i].Length)
                            {
                                LeseDateien(fi1[i].FullName, fi2[j].FullName, fi1[i].Length);
                            }
                            else
                            { LenDiff++; }
                        }
                    }
                    if (!gefunden) 
                    { Datei2Fehlt++; }
                }
    
                for (int i = 0; i < DirAnz1; i++) // Alle Unterordner verarbeiten
                {
                    gefunden = false;
                    SuchName = ui1[i].Name.ToLower(); // ohne Lw:\Pfad
    
                    for (int j = 0; j < DirAnz2 && !gefunden; j++)
                    {
                        if (SuchName.CompareTo(ui2[j].Name.ToLower()) == 0)
                        {
                            gefunden = true;
                            PfadVerarbeiten(ui1[i].FullName, ui2[i].FullName);  // Rekursiver Aufruf
                        }
                    }
                    if (!gefunden)
                    { Pfad2Fehlt++; }
                }
            }
    
            static void LeseDateien(string pDsn1, string pDsn2, Int64 lenFile) 
            {
                AnzFiles++;
                Int64 RestLen = lenFile;
                FileStream fs1 = new FileStream(pDsn1, FileMode.Open, FileAccess.Read);
                FileStream fs2 = new FileStream(pDsn2, FileMode.Open, FileAccess.Read);
                Int64 LeseLen = Math.Min(RestLen, MaxPuffL);
                while(LeseLen > 0) 
                {
                    if (MitRead) // TEST: soll READ stattfinden ?
                    {
                        GesBytes += LeseLen; // Anzahl gelesene Bytes gesamt
                        fs1.Read(ByPu1, 0, (int)LeseLen);
                        fs2.Read(ByPu2, 0, (int)LeseLen);
    
                        if (MitComp) // TEST: soll verglichen werden ?
                        {
                            AnzPuff++;                 // Anzahl verglichene Puffer
                            switch (VergleichsMethode) // Welche Art des Vergleiches
                            { 
                                case compSequenceEqual: // C# interne Funktion
                                    if (!(ByPu1.SequenceEqual(ByPu2)))
                                    {
                                        ByteDiff++; // es gibt Differenzen
                                        return;     // Vergleich abbrechen
                                    }
                                    break;
                                case compMemCmp: // [DllImport]  msvcrt.dll
                                    if (memcmp(ByPu1, ByPu2, LeseLen) != 0) 
                                    {
                                        ByteDiff++; // es gibt Differenzen
                                        return;     // Vergleich abbrechen
                                    }
                                    break;
                                case compForNxt: // C# interner Byte-für-Byte-Vergleich
                                    for (int b = 0; b < LeseLen; b++) 
                                    { 
                                        if (ByPu1[b] != ByPu2[b]) 
                                        {
                                            ByteDiff++; // es gibt Differenzen
                                            return;     // Vergleich abbrechen
                                        }
                                    }
                                    break;
                            }
                        }
                    }
                    RestLen -= LeseLen;
                    LeseLen = Math.Min(RestLen, MaxPuffL);
                }
                fs1.Close();
                fs2.Close();
            }
        }
    }
    


  • Für den notwendigen Vergleich muss man deshalb immer die zu vergleichende Länge mitgeben, die ist meistens deutlich kleiner, als die Pufferlänge.

    @Jockelx hatte dir doch im anderen Thema den Code dazu gegeben:

    ByPu1.Take(count1).SequenceEqual(ByPu2.Take(count2)

    Aber da diese Methode in .NET 4 noch unoptimiert ist (sondern allgemeingültig für alle Arten von Sequenzen (IEnumerable)), ist sie deswegen für Arrays nicht zu empfehlen.

    Ich habe mal gerade deinen Code in einem .NET 7-Projekt (VS 2022) bei mir ausgeführt:

    Start = 07.12.2022 10:44:53
    Vergleichsmethode = SequenceEqual (C# interne Funktion)
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    5.863.602.359 Bytes eingelesen
    8 Directories
    148 Dateien
    422 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 10:46:38, Dauer = 00:01:44.9
    -----------------------------------------------
    Start = 07.12.2022 10:48:57
    Vergleichsmethode = MemCmp [DllImport] msvcrt.dll
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    5.863.602.359 Bytes eingelesen
    8 Directories
    148 Dateien
    422 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 10:49:06, Dauer = 00:00:08.8
    -----------------------------------------------
    Start = 07.12.2022 10:50:30
    Vergleichsmethode = ForNxt (byte-weiser Vergleich)
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    5.863.602.359 Bytes eingelesen
    8 Directories
    148 Dateien
    422 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 10:50:57, Dauer = 00:00:26.3
    

    SequenceEqual (mit Take) ist zwar immer noch ca. 3-4x langsamer als der byte-weise Vergleich, aber nicht mehr so extrem, wie unter .NET 4.
    Aber memcmp schlägt bei mir die beiden anderen deutlich.

    Ich muß jetzt gleich weg, aber ich werde auch noch mal die unsafe-Methode später testen und berichten.


    PS: Dein Code hat noch zwei Fehler: in Zeile 93 und 115 muß der Index für den 2. Puffer jeweils j lauten (nicht i)! Fällt auf, wenn es sich um unterschiedliche Dateien in beiden Ordnern handelt.
    Außerdem benutzt du mehrfach den (ineffektiveren) Linq-Count(), statt einfach Length.
    Aber für's Iterieren über alle Verzeichnisse und Dateien solltest du sowieso besser die Enumerate... statt der Get...-Methoden benutzen (zumindestens für einen der beiden Ordner).



  • @Th69 sagte in Warum klappt Binary bei mir nicht:

    Dein Code hat noch zwei Fehler

    Danke für den Hinweis, da ich immer einen Ordner mit sich selbst vergleiche, ist das noch nicht aufgefallen.

    @Th69 sagte in Warum klappt Binary bei mir nicht:

    solltest du sowieso besser die Enumerate... statt der Get...-Methoden benutzen

    GET benutze ich doch nur zum einlesen der Dateien, nicht für die Directory-Inhalte (Dateinamen bzw. Unterordner-Namen).

    Ich glaube, der zeitkritische Aufwand besteht im Vergleich der Puffer.
    Das Ermitteln der zu vergleichenden Dateien und Unterordner braucht nur wenige Sekundenbruchteile.
    Auch das Einlesen der Dateien ist zeit-unkritisch. Das sieht man an den Zeiten, wenn COMPARE nicht aktiviert ist. Dann lauft alles so ab, nur ohne Vergleich.

    @Th69,
    jetzt habe ich die Längen mitgegeben

                             case compSequenceEqual: // C# interne Funktion
                                    if (!(ByPu1.Take((int)LeseLen).SequenceEqual(ByPu2.Take((int)LeseLen))))
                                    {
                                        ByteDiff++; // es gibt Differenzen
                                        return;     // Vergleich abbrechen
                                    }
                                    break;
    

    Das Ergebnis ist etwas freundlicher

    Start = 07.12.2022 12:27:52
    Vergleichsmethode = SequenceEqual (C# interne Funktion)
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    18.069.912.569 Bytes eingelesen
    770 Directories
    15.228 Dateien
    16.152 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 12:32:22, Dauer = 00:04:30.3
    

    Etwas über 4 Minuten statt beinahe 34 Minuten, also reichlich 10%.



  • So, ich habe nun die unsafe-Methode eingebaut und getestet:

    	case compUnsafe: // unsafe MemCmp
    		if (!MemCmp(ByPu1, ByPu2, (int)LeseLen))
    		{
    			ByteDiff++; // es gibt Differenzen
    			return;     // Vergleich abbrechen
    		}
    		break;
    
    static unsafe bool MemCmp(byte[] a1, byte[] a2, int length)
    {
    	if (a1 == null || a2 == null || a1.Length < length || a2.Length < length)
    		return false;
    
    	fixed (byte* p1 = a1, p2 = a2)
    	{
    		long* x1 = (long*)p1, x2 = (long*)p2;
    		for (int i = 0; i < length / sizeof(long); i++, x1++, x2++)
    			if (*x1 != *x2)
    				return false;
    
    		if ((length & 4) != 0)
    		{
    			if (*(int*)x1 != *(int*)x2)
    				return false;
    			x1 += 4;
    			x2 += 4;
    		}
    		if ((length & 2) != 0)
    		{
    			if (*(short*)x1 != *(short*)x2)
    				return false;
    			x1 += 2;
    			x2 += 2;
    		}
    		if ((length & 1) != 0)
    			return *x1 == *x2;
    
    		return true;
    	}
    }
    

    (ich selber würde den Code noch etwas schöner gestalten, z.B. Konstanten benutzen, habe ihn nur jetzt auf die Schnelle übernommen und den length Parameter hinzugefügt)

    Start = 07.12.2022 12:53:39
    Vergleichsmethode = unsafe MemCmp
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    5.863.602.359 Bytes eingelesen
    8 Directories
    148 Dateien
    422 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 12:53:49, Dauer = 00:00:10.4
    

    Ist also knapp etwas schlechter als die native memcmp Funktion, aber wie zu erwarten auch deutlich schneller als der byte-weise Vergleich.



  • Was ist mit RtlCompareMemory, hat das schon jemand ausprobiert?



  • @Th69 sagte in Warum klappt Binary bei mir nicht:

    if ((length & 4) != 0)

    Mit diesem Test soll festgestellt werden, ob die Länge ein Vielfaches von 4 ist oder anders gesagt ohne Rest durch 4 teilbar ist.
    Dazu muss man aber prüfen, ob die letzten 2 Bits 00 sind. Das 4er Bit ist ein einzelnes Bit und eine Länge von 7 ergäbe 7 & 4 = 4 != 0

    Es müsste m.E. heissen

    if ((length & 3) == 0)
    

    Damit wird geprüft, ob die letzten 2 Bits = 00 sind, dann ist die Länge ohne Rest durch 4 teilbar
    Das gilt auch für den zweiten Test
    statt

    if ((length & 2) != 0)
    

    richtig ( dann ohne Rest durch 2 teilbar)

    if ((length & 1) == 0)
    

    Oder geht es hier um den Vergleich der evtl. Restlänge, die sich aus der Division ergibt ?

    length / sizeof(long)
    


  • Ich hab den thread nur ueberflogen, aber es gibt neben IEnumerable.SequenceEqual() auch noch MemoryExtensions.SequenceEquals().

    Ich hatte irgendwas bzgl. "copy vermeiden" gelesen, was auch mit Span<T>/ReadOnlySpan<T> durch CollectionsMarshal.AsSpan() moeglich ist.



  • @DocShoe sagte in Warum klappt Binary bei mir nicht:

    Was ist mit RtlCompareMemory, hat das schon jemand ausprobiert

    Ich habe es probiert, ist auch schnell fertig, findet allerdings sehr viele Differenzen und führt deshalb auch viele Vergliche nicht durch. Sicherlich hat mein Code irgendwo einen Fehler.

    Hier die Ergebnisse

    Start = 07.12.2022 07:37:24
    MitRead=False, MitComp=False
    16.777.216 Bytes = Puffergröße
    0 Bytes eingelesen
    770 Directories
    15.228 Dateien
    0 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 07:37:26, Dauer = 00:00:01.2
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Start = 07.12.2022 07:38:16
    MitRead=True, MitComp=False
    16.777.216 Bytes = Puffergröße
    18.069.912.569 Bytes eingelesen
    770 Directories
    15.228 Dateien
    0 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 07:38:27, Dauer = 00:00:11.3
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Start = 07.12.2022 07:39:20
    Vergleichsmethode = SequenceEqual (C# interne Funktion)
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    18.069.912.569 Bytes eingelesen
    770 Directories
    15.228 Dateien
    16.152 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 08:13:19, Dauer = 00:33:59.0
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Start = 07.12.2022 08:14:00
    Vergleichsmethode = MemCmp [DllImport] msvcrt.dll
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    18.069.912.569 Bytes eingelesen
    770 Directories
    15.228 Dateien
    16.152 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 08:14:14, Dauer = 00:00:13.9
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Start = 07.12.2022 08:14:56
    Vergleichsmethode = ForNxt (byte-weiser Vergleich)
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    18.069.912.569 Bytes eingelesen
    770 Directories
    15.228 Dateien
    16.152 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 08:15:38, Dauer = 00:00:41.5
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Start = 07.12.2022 13:53:56
    Vergleichsmethode = unsafe MemCmp von Th69
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    18.069.912.569 Bytes eingelesen
    770 Directories
    15.228 Dateien
    16.152 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 13:54:11, Dauer = 00:00:15.2
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Start = 07.12.2022 13:55:53
    Vergleichsmethode = RtlCompareMemory [DllImport] ntdll.dll
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    2.918.526.970 Bytes eingelesen
    770 Directories
    15.228 Dateien
    15.217 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    15.217 ungleiche Dateien
    Ende  = 07.12.2022 13:56:06, Dauer = 00:00:13.1
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    Und hier der Code

    using System;
    using System.IO;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Security.Policy;
    
    namespace AllFilesInDir // Alle Dateien eines Ordners (Kommandozeile) lesen
    {
        internal class Program
        {
            public static bool MitRead = true;  // Read abschalten TEST TEST TEST
            public static bool MitComp = true;  // Vergleich abschalten TEST TEST TEST
    
            public static int VergleichsMethode = compRtlCompareMemory;
    
            public static string[] VglMethode =
            {
                "SequenceEqual (C# interne Funktion)",
                "MemCmp [DllImport] msvcrt.dll",
                "ForNxt (byte-weiser Vergleich)",
                "unsafe MemCmp von Th69",
                "RtlCompareMemory [DllImport] ntdll.dll"
            };
    
            public const int compSequenceEqual = 0;
            public const int compMemCmp = 1;
            public const int compForNxt = 2;
            public const int compUnsafe = 3;
            public const int compRtlCompareMemory = 4;
    
            public static Int64 GesBytes = 0; // Summe  aller gelesenen Bytes
            public static int Datei2Fehlt = 0; // Anzahl fehlender Dateien in Directory 2
            public static int Pfad2Fehlt = 0; // Anzahl fehlender Unterpfade in Directory 2
            public static int LenDiff = 0; // Anzahl Dateien mit unterschiedlicher Länge
            public static int ByteDiff = 0; // Anzahl Dateien mit inhaltlichen Differenzen
            public static int AnzDir = 0; // Anzahl aller Directories incl. Start-Dir
            public static int AnzFiles = 0; // Anzahl aller gelesenen Dateien
            public static int AnzPuff = 0; // Anzahl aller gelesenen Puffer
    
            public const int MaxPuffL = 0x01000000; // 16.777.216 Bytes = Pufferlänge
    
            public static byte[] ByPu1 = new byte[MaxPuffL + 0x1000]; // Eingabepuffer + Reserve
            public static byte[] ByPu2 = new byte[MaxPuffL + 0x1000]; // Eingabepuffer + Reserve
    
            [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
            static extern int memcmp(byte[] b1, byte[] b2, long count);
    
            [DllImport("ntdll.dll", EntryPoint = "RtlCompareMemory", SetLastError = false)]
            private static extern ulong RtlCompareMemory(IntPtr Source1, IntPtr Source2, ulong length);
    
            static void Main(string[] args)
            {
                DateTime dtStart = DateTime.Now;  // Start-Zeitpunkt merken
                Console.WriteLine("Start = " + dtStart.ToString());
                if (MitComp) Console.WriteLine("Vergleichsmethode = " + VglMethode[VergleichsMethode]);
                Console.WriteLine("MitRead=" + MitRead + ", MitComp=" + MitComp);
                Console.WriteLine(MaxPuffL.ToString("###,###,###,###,##0") + " Bytes = Puffergröße");
                string StartPfad1 = args[0]; // Pfad 1 aus Kommandozeile holen
                string StartPfad2 = args[1]; // Pfad 2 aus Kommandozeile holen
    
                PfadVerarbeiten(StartPfad1, StartPfad2);
    
                Console.WriteLine(GesBytes.ToString("###,###,###,###,##0") + " Bytes eingelesen");
                Console.WriteLine(AnzDir.ToString("###,###,###,###,##0") + " Directories");
                Console.WriteLine(AnzFiles.ToString("###,###,###,###,##0") + " Dateien");
                Console.WriteLine(AnzPuff.ToString("###,###,###,###,##0") + " Puffer");
                Console.WriteLine(Datei2Fehlt.ToString("###,###,###,###,##0") + " fehlende Dateien");
                Console.WriteLine(Pfad2Fehlt.ToString("###,###,###,###,##0") + " fehlende Unterpfade");
                Console.WriteLine(LenDiff.ToString("###,###,###,###,##0") + " Dateien mit unterschiedlicher Länge");
                Console.WriteLine(ByteDiff.ToString("###,###,###,###,##0") + " ungleiche Dateien");
                DateTime dtEnde = DateTime.Now;  // Ende-Zeitpunkt merken
                System.TimeSpan Dauer = dtEnde - dtStart;
                Console.WriteLine("Ende  = " + dtEnde.ToString() + ", Dauer = " + Dauer.ToString().Substring(0, 10));
    
                Console.ReadLine(); // Warten auf ENTER
            }
            static void PfadVerarbeiten(string pPfad1, string pPfad2) // wird rekursiv aufgerufen
            {
                AnzDir++; // Anzahl verglichene DIR gesamt
    
                DirectoryInfo di1 = new DirectoryInfo(pPfad1);     // Aktuelles Directory
                FileInfo[] fi1 = di1.GetFiles("*.*");              // Dateien im Directory
                int FilesAnz1 = fi1.Count();                       // Anzahl Dateien im Directory
                DirectoryInfo[] ui1 = di1.GetDirectories("*.*");   // Unterordner im Directory
                int DirAnz1 = ui1.Count();                         // Anzahl Unterordner im Directory
    
                DirectoryInfo di2 = new DirectoryInfo(pPfad2);     // Aktuelles Directory
                FileInfo[] fi2 = di2.GetFiles("*.*");              // Dateien im Directory
                int FilesAnz2 = fi2.Count();                       // Anzahl Dateien im Directory
                DirectoryInfo[] ui2 = di2.GetDirectories("*.*");   // Unterordner im Directory
                int DirAnz2 = ui2.Count();                         // Anzahl Unterordner im Directory
    
                bool gefunden = false;
                string SuchName = "";
    
                for (int i = 0; i < FilesAnz1; i++) // zu allen Dateien aus Pfad1 zugehörige Datei in Pfad2 suchen
                {
                    gefunden = false;
                    SuchName = fi1[i].Name.ToLower(); // ohne Lw:\Pfad
    
                    for (int j = 0; j < FilesAnz2 && !gefunden; j++)
                    {
                        if (SuchName.CompareTo(fi2[j].Name.ToLower()) == 0)
                        {
                            gefunden = true;
                            if (fi1[i].Length == fi2[j].Length)
                            { LeseDateien(fi1[i].FullName, fi2[j].FullName, fi1[i].Length); }
                            else
                            { LenDiff++; }
                        }
                    }
                    if (!gefunden)
                    { Datei2Fehlt++; }
                }
    
                for (int i = 0; i < DirAnz1; i++) // Alle Unterordner verarbeiten
                {
                    gefunden = false;
                    SuchName = ui1[i].Name.ToLower(); // ohne Lw:\Pfad
    
                    for (int j = 0; j < DirAnz2 && !gefunden; j++)
                    {
                        if (SuchName.CompareTo(ui2[j].Name.ToLower()) == 0)
                        {
                            gefunden = true;
                            PfadVerarbeiten(ui1[i].FullName, ui2[j].FullName);  // Rekursiver Aufruf
                        }
                    }
                    if (!gefunden)
                    { Pfad2Fehlt++; }
                }
            }
    
            static void LeseDateien(string pDsn1, string pDsn2, Int64 lenFile)
            {
                AnzFiles++; // Anzahl Dateien gesamt
                Int64 RestLen = lenFile; // Dateilänge beider Dateien
                FileStream fs1 = new FileStream(pDsn1, FileMode.Open, FileAccess.Read);
                FileStream fs2 = new FileStream(pDsn2, FileMode.Open, FileAccess.Read);
                Int64 LeseLen = Math.Min(RestLen, MaxPuffL);
                while (LeseLen > 0)
                {
                    if (MitRead) // TEST: soll READ stattfinden ?
                    {
                        GesBytes += LeseLen; // Anzahl gelesene Bytes gesamt
                        fs1.Read(ByPu1, 0, (int)LeseLen);
                        fs2.Read(ByPu2, 0, (int)LeseLen);
    
                        if (MitComp) // TEST: soll verglichen werden ?
                        {
                            AnzPuff++;                 // Anzahl verglichene Puffer
                            switch (VergleichsMethode) // Welche Art des Vergleiches
                            {
                                case compSequenceEqual: // C# interne Funktion
                                    if (!(ByPu1.Take((int)LeseLen).SequenceEqual(ByPu2.Take((int)LeseLen))))
                                    {
                                        ByteDiff++; // es gibt Differenzen
                                        return;     // Vergleich abbrechen
                                    }
                                    break;
                                case compMemCmp: // [DllImport]  msvcrt.dll
                                    if (memcmp(ByPu1, ByPu2, LeseLen) != 0)
                                    {
                                        ByteDiff++; // es gibt Differenzen
                                        return;     // Vergleich abbrechen
                                    }
                                    break;
                                case compForNxt: // C# interner Byte-für-Byte-Vergleich
                                    for (int b = 0; b < LeseLen; b++)
                                    {
                                        if (ByPu1[b] != ByPu2[b])
                                        {
                                            ByteDiff++; // es gibt Differenzen
                                            return;     // Vergleich abbrechen
                                        }
                                    }
                                    break;
                                case compUnsafe: // unsafe MemCmp
                                    if (!MemCmp(ByPu1, ByPu2, (int)LeseLen))
                                    {
                                        ByteDiff++; // es gibt Differenzen
                                        return;     // Vergleich abbrechen
                                    }
                                    break;
                                case compRtlCompareMemory: // [DllImport] "ntdll.dll RtlCompareMemory
                                    IntPtr sPtr1 = Marshal.UnsafeAddrOfPinnedArrayElement(ByPu1, 0);
                                    IntPtr sPtr2 = Marshal.UnsafeAddrOfPinnedArrayElement(ByPu2, 0);
    
                                    ulong uLen = Convert.ToUInt64(LeseLen);
                                    ulong okLen = RtlCompareMemory(sPtr1, sPtr2, uLen);
                                    ulong uDiff = uLen - okLen;
    
                                    if (uDiff > 0)
                                    {
                                        ByteDiff++; // es gibt Differenzen
                                        return;     // Vergleich abbrechen
                                    }
                                    break;
                            }
                        }
                    }
                    RestLen -= LeseLen;
                    LeseLen = Math.Min(RestLen, MaxPuffL);
                }
                fs1.Close();
                fs2.Close();
            }
    
            static unsafe bool MemCmp(byte[] a1, byte[] a2, int length)
            {
                if (a1 == null || a2 == null || a1.Length < length || a2.Length < length)
                    return false;
    
                fixed (byte* p1 = a1, p2 = a2)
                {
                    long* x1 = (long*)p1, x2 = (long*)p2;
                    for (int i = 0; i < length / sizeof(long); i++, x1++, x2++)
                        if (*x1 != *x2)
                            return false;
    
                    if ((length & 4) != 0) 
                    {
                        if (*(int*)x1 != *(int*)x2)
                            return false;
                        x1 += 4;
                        x2 += 4;
                    }
                    if ((length & 2) != 0)
                    {
                        if (*(short*)x1 != *(short*)x2)
                            return false;
                        x1 += 2;
                        x2 += 2;
                    }
                    if ((length & 1) != 0) // ????
                        return *x1 == *x2;
    
                    return true;
                }
            }
        }
    }
    
    


  • @DocShoe sagte in Warum klappt Binary bei mir nicht:

    Was ist mit RtlCompareMemory, hat das schon jemand ausprobiert?

    Ist etwas gleich schnell wie memcmp:

    [DllImport("kernel32.dll", CallingConvention = CallingConvention.Cdecl)]
    static extern long RtlCompareMemory(byte[] b1, byte[] b2, long length);
    
    	case compRtl: // RtlCompareMemory
    		if (RtlCompareMemory(ByPu1, ByPu2, LeseLen) != LeseLen)
    		{
    			ByteDiff++; // es gibt Differenzen
    			return;     // Vergleich abbrechen
    		}
    		break;
    
    Start = 07.12.2022 13:46:24
    Vergleichsmethode = RtlCompareMemory [DllImport] kernel32.dll
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    5.863.602.359 Bytes eingelesen
    8 Directories
    148 Dateien
    422 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 13:46:33, Dauer = 00:00:08.9
    

    VS schlägt mir aber noch vor, LibraryImport zu benutzen:

    SYSLIB1054	Mark the method 'memcmp' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time
    

    Nach Umsetzung

    [LibraryImport("msvcrt.dll")]
    public static partial int memcmp(byte[] b1, byte[] b2, long count);
    
    [LibraryImport("kernel32.dll")]
    public static partial long RtlCompareMemory(byte[] b1, byte[] b2, long length);
    

    brachte es aber keine weitere Verbesserung:

    Start = 07.12.2022 14:08:13
    Vergleichsmethode = RtlCompareMemory [DllImport] kernel32.dll
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    5.863.602.359 Bytes eingelesen
    8 Directories
    148 Dateien
    422 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 14:08:22, Dauer = 00:00:08.9
    

    (wahrscheinlich ist das Marshalling hier nicht so kompliziert, als daß es einen Laufzeitunterschied gibt)



  •         /*
            [DllImport("kernel32.dll", CallingConvention = CallingConvention.Cdecl)]
            static extern long RtlCompareMemory(byte[] b1, byte[] b2, long length);
            */
    
            [LibraryImport("kernel32.dll")]
            public static partial long RtlCompareMemory(byte[] b1, byte[] b2, long length);
    

    Das klappt bei mir beides nicht.
    Es wird gemeldet => ist in C# 7.3 nicht verfügbar, verwenden Sie Sprachversion 9.0 oder höher



  • Ja, das ist mir klar, daß es nur in VS 2022 und .NET 7 funktioniert (wollte nur schauen, ob es noch weitere Performanceverbesserungen dazu gibt).
    Ähnliches beim Ordner- und Dateieinlesen: Warum langsamere (und speicherintensivere) Methoden verwenden, wenn es auch besser geht?

    @hkdd sagte in Warum klappt Binary bei mir nicht:

    Oder geht es hier um den Vergleich der evtl. Restlänge, die sich aus der Division ergibt ?

    length / sizeof(long)
    

    Ja genau, es geht um die Restlänge, falls die Länge nicht exakt durch sizeof(long) (= 8) teilbar ist - dann werden noch die restlichen Bytes verglichen (zuerst evtl. int [= 4], dann short [= 2] und zuletzt noch byte [= 1]).



  • @hkdd sagte in Warum klappt Binary bei mir nicht:

    if ((length & 4) != 0)

    Mit diesem Test soll festgestellt werden, ob die Länge ein Vielfaches von 4 ist oder anders gesagt ohne Rest durch 4 teilbar ist.

    Nein. Es wird getestet, ob noch mindestens 4 Bytes da sind, die man testen kann. Bei 7 soll hier also in den Block gesprungen werden, weil noch 4 Bytes am Stück verglichen werden können. Man hätte auch if (remaining_length >= 4) schreiben können (dazu hätte man dann die remaining_length aber immer mitzählen müssen).

    Und direkt vor mir (hatte ich nicht gesehen, als ich die Antwort zu schreiben angefangen habe) hat @Th69 das ja bereits erklärt, sodass meine AW eigentlich überflüssig ist.



  • @wob
    alles klar

    Ich hätte noch eine Frage im Zusammenhang mit [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] oder [LibraryImport("kernel32.dll")].
    Man kann ja auch selbst eine DLL schreiben und importieren.
    Ich habe versucht, den DLL-Namen als Variable anzugeben, z.B.so

    string MyDLL = "msvcrt.dll";
    [DllImport(MyDLL, CallingConvention = CallingConvention.Cdecl)] 
    

    Das wird nicht akzeptiert. Der Compiler will selbst Zugriff auf die DLL haben zum Compilierungszeitpunkt.
    Das Programm selbst greift auch auf die DLL zu zum Ausführungszeitpunkt.
    Dann kann die EXE aber ganz woanders stehen. Wo wird nach der DLL gesucht ?
    Im Ordner der EXE ?
    In einem bestimmten Windows Order ?
    Ich habe auf meiner C: mit Windows 10 mal nach "msvcrt.dll" gesucht und habe eine Menge unterschiedlichster DLL mit diesem Namen gefunden. Auf welche greift die EXE beim Ausführen zu und auf welche der Compiler zum Compilierungszeitpunkt. Man kann den Namen ja ganz ausführlich mit Laufwerk und Ordner angeben.
    Wenn da aber nur "msvcrt.dll" angegeben ist, welche der vielen Dateien wird von wem (Compiler / EXE) benutzt und warum ?
    Genügt es (bei einer eigenen DLL) diese im gleichen Ordner, wie die EXE zu speichern ?
    Wenn man das Programm jemandem übergibt, weiß man doch nicht, wo dieser es abspeichert und von dort ausführt - wie wird da eine DLL gefunden wenn man im Source-Code bereits explizit den Dateinamen angeben muss? In den SYSTEM32 Ordner sollte man es ja auch nicht speichern, zumal man nicht weiß, ob ein anderer Programmierer auch eine Dackel.DLL geschrieben hat, dann wäre dieser Name bereits vorhanden, macht aber etwas ganz anderes.
    Unproblematisch wäre es, wenn man die DLL in die EXE einbinden könnte und von dort benutzen, z.B. als Ressource.

     Datentr„ger in Laufwerk C: ist C(W) RyWin10 Sam 980 m.2
     Volumeseriennummer: 44C6-DD4C
    
     Verzeichnis von C:\#hk\SpieleXP
    
    14.04.2008  05:52           343.040 msvcrt.dll
                   1 Datei(en),        343.040 Bytes
    
     Verzeichnis von C:\#hk\SW\Gws4
    
    24.09.1998  10:03           254.005 MSVCRT.DLL
                   1 Datei(en),        254.005 Bytes
    
     Verzeichnis von C:\Program Files\Pinnacle\Studio 24\programs
    
    26.10.2020  08:02           634.880 msvcrt.dll
                   1 Datei(en),        634.880 Bytes
    
     Verzeichnis von C:\Program Files (x86)\Epson Software\Event Manager
    
    24.12.2003  01:08           254.005 Msvcrt.dll
                   1 Datei(en),        254.005 Bytes
    
     Verzeichnis von C:\Program Files (x86)\Epson Software\Event Manager\Assistants\Scan Assistant
    
    24.12.2003  01:08           254.005 Msvcrt.dll
                   1 Datei(en),        254.005 Bytes
    
     Verzeichnis von C:\Program Files (x86)\Nero\Nero 2021\Nero Burning ROM
    
    19.07.2022  09:53           773.912 msvcrt.dll
                   1 Datei(en),        773.912 Bytes
    
     Verzeichnis von C:\Windows\servicing\LCU\Package_for_RollupFix~31bf3856ad364e35~amd64~~19041.2311.1.11\amd64_microsoft-windows-msvcrt_31bf3856ad364e35_10.0.19041.546_none_af4e7d20fdb56824\f
    
    10.06.2021  05:58             7.722 msvcrt.dll
                   1 Datei(en),          7.722 Bytes
    
     Verzeichnis von C:\Windows\servicing\LCU\Package_for_RollupFix~31bf3856ad364e35~amd64~~19041.2311.1.11\amd64_microsoft-windows-msvcrt_31bf3856ad364e35_10.0.19041.546_none_af4e7d20fdb56824\r
    
    10.06.2021  05:58             7.753 msvcrt.dll
                   1 Datei(en),          7.753 Bytes
    
     Verzeichnis von C:\Windows\servicing\LCU\Package_for_RollupFix~31bf3856ad364e35~amd64~~19041.2311.1.11\wow64_microsoft-windows-msvcrt_31bf3856ad364e35_10.0.19041.546_none_b9a3277332162a1f\f
    
    10.06.2021  05:38             2.920 msvcrt.dll
                   1 Datei(en),          2.920 Bytes
    
     Verzeichnis von C:\Windows\servicing\LCU\Package_for_RollupFix~31bf3856ad364e35~amd64~~19041.2311.1.11\wow64_microsoft-windows-msvcrt_31bf3856ad364e35_10.0.19041.546_none_b9a3277332162a1f\r
    
    10.06.2021  05:38             3.064 msvcrt.dll
                   1 Datei(en),          3.064 Bytes
    
     Verzeichnis von C:\Windows\System32
    
    08.09.2022  04:07           637.360 msvcrt.dll
                   1 Datei(en),        637.360 Bytes
    
     Verzeichnis von C:\Windows\SysWOW64
    
    08.09.2022  04:07           775.256 msvcrt.dll
                   1 Datei(en),        775.256 Bytes
    
     Verzeichnis von C:\Windows\WinSxS\amd64_microsoft-windows-msvcrt_31bf3856ad364e35_10.0.19041.546_none_af4e7d20fdb56824
    
    08.09.2022  04:07           637.360 msvcrt.dll
                   1 Datei(en),        637.360 Bytes
    
     Verzeichnis von C:\Windows\WinSxS\amd64_microsoft-windows-msvcrt_31bf3856ad364e35_10.0.19041.546_none_af4e7d20fdb56824\f
    
    08.09.2022  04:07             7.722 msvcrt.dll
                   1 Datei(en),          7.722 Bytes
    
     Verzeichnis von C:\Windows\WinSxS\amd64_microsoft-windows-msvcrt_31bf3856ad364e35_10.0.19041.546_none_af4e7d20fdb56824\r
    
    08.09.2022  04:07             7.753 msvcrt.dll
                   1 Datei(en),          7.753 Bytes
    
     Verzeichnis von C:\Windows\WinSxS\wow64_microsoft-windows-msvcrt_31bf3856ad364e35_10.0.19041.546_none_b9a3277332162a1f
    
    08.09.2022  04:07           775.256 msvcrt.dll
                   1 Datei(en),        775.256 Bytes
    
     Verzeichnis von C:\Windows\WinSxS\wow64_microsoft-windows-msvcrt_31bf3856ad364e35_10.0.19041.546_none_b9a3277332162a1f\f
    
    08.09.2022  04:07             2.920 msvcrt.dll
                   1 Datei(en),          2.920 Bytes
    
     Verzeichnis von C:\Windows\WinSxS\wow64_microsoft-windows-msvcrt_31bf3856ad364e35_10.0.19041.546_none_b9a3277332162a1f\r
    
    08.09.2022  04:07             3.064 msvcrt.dll
                   1 Datei(en),          3.064 Bytes
    
         Anzahl der angezeigten Dateien:
                  18 Datei(en),      5.381.997 Bytes
                   0 Verzeichnis(se), 738.345.742.336 Bytes frei
    
    


  • @hkdd sagte in Warum klappt Binary bei mir nicht:

    Wo wird nach der DLL gesucht ?

    Siehe:
    https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order

    Ist auch ein mögliches Ziel von Angriffen, wenn falsch gemacht und ein Angreifer Zugriff auf einen Ordner hat, der vor dem von dir geplanten durchsucht wird.



  • @hkdd sagte in Warum klappt Binary bei mir nicht:

    Das wird nicht akzeptiert. Der Compiler will selbst Zugriff auf die DLL haben zum Compilierungszeitpunkt.

    Du kannst eine Konstante anlegen:

    const string MyDLL = "msvcrt.dll";
    [DllImport(MyDLL, CallingConvention = CallingConvention.Cdecl)] 
    

    (bes. sinnvoll, wenn man mehrere Funktionen aus einer DLL ansprechen möchte, anstatt jedesmal wieder das String-Literal anzugeben)

    Und dein 2. Satz davon stimmt nicht, denn so wie @wob verlinkt hat, wird erst zur Laufzeit nach der DLL gesucht.



  • Danke für Euere Erläuterungen. Ich habe eine kleine DLL für den Puffervergleich gemacht, analog der ASM-Routine in der Delphi Version. Die DLL ist mit C++ gemacht und wird mit dem cl-Compiler erstellt.

    //puvgl.cpp  Vergleich zweier Pufferinhalte   07.12.2022 
    
    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]
          MOV   EDI,[b2]
          MOV   ECX,Psize   // Länge in Bytes 
    
          AND   ECX,ECX     // Länge = 0 ?
          JZ    Ende
    
          CMP   ECX,5       // Länge < 5
          JL    CmpBy       // --> kein DW Vergleich
    
          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 
    
         CmpBy:
          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;
    }
    

    Für das Compilieren benutze ich folgenden Batch :

    REM MakePuVglDLL.bat
    cl puvgl.cpp /LD /EHsc
    PAUSE
    

    Und zum Kopieren der DLL in die Ordner, wo sie benutzt wird, habe ich auch einen Batch

    REM CopyPuVglDLL.bat
    copy puvgl.dll U:\#hk\SW
    copy puvgl.dll V:\#PgmHK\VC20xx\HKcs\CompHK\bin\Debug
    copy puvgl.dll V:\#PgmHK\VC20xx\HKcs\CompHK\bin\Release
    copy puvgl.dll V:\#PgmHK\VC20xx\HKcs\#TestPGMe\AllFilesInDir\bin\Debug
    copy puvgl.dll V:\#PgmHK\VC20xx\HKcs\#TestPGMe\AllFilesInDir\bin\Release
    PAUSE
    

    Im C# Programm wird diese Routine folgendermaßen benutzt, puvgl.dll muss im EXE-Ordner stehen

        [DllImport("puvgl.dll", CallingConvention = CallingConvention.Cdecl)]
            static extern int PuVgl(byte[] b1, byte[] b2, int size);
    
    ::::
                            case compPuVgl: // [DllImport] PuVgl.dll von HK
                                    if (PuVgl(ByPu1, ByPu2, (int)LeseLen) != 0)
                                    {
                                        ByteDiff++; // es gibt Differenzen
                                        return;     // Vergleich abbrechen
                                    }
                                    break;
    

    Und so sieht das Ergebnis (Laufzeit) aus:

    Start = 07.12.2022 17:19:49
    Vergleichsmethode = PuVgl
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    18.069.912.569 Bytes eingelesen
    770 Directories
    15.228 Dateien
    16.152 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 07.12.2022 17:19:57, Dauer = 00:00:08.2
    

    Statt der DWORDS (4 Bytes) könnte man auch 8 Bytes mit den aktuellen CPUs benutzen.
    Diese Routine stammt noch aus Turbo Pascal Zeiten, ist aber von der Methode mit der Routine von Th69 (dort 8 Bytes statt 4) ziemlich ähnlich.

    Ich habe gerade in der ASS-DLL noch einen Fehler erkannt für den Fall, dass es sich um eine sehr kleine Datei handelt, im konkreten Fall gibt es bei meinem Testordner eine Datei, die die Länge = 2 Bytes hat, d.h. es darf kein DWORD Vergleich (4 Bytes) stattfinden, das gibt ein falsches Ergebnis - es wird eine Differenz gemeldet, die keine ist.
    Man kann natürlich im übergeordneten Programm kleine Dateien abfangen abfangen. Alle Längen < 8 werden mit der for-Schleife verglichen, die anderen Routinen werden nur für größere Dateien benutzt. Bei Länge == 0 wird kein Vergleich ausgeführt.



  • @Th69 sagte in Warum klappt Binary bei mir nicht:

    Für den notwendigen Vergleich muss man deshalb immer die zu vergleichende Länge mitgeben, die ist meistens deutlich kleiner, als die Pufferlänge.

    @Jockelx hatte dir doch im anderen Thema den Code dazu gegeben:

    ByPu1.Take(count1).SequenceEqual(ByPu2.Take(count2)

    Aber da diese Methode in .NET 4 noch unoptimiert ist (sondern allgemeingültig für alle Arten von Sequenzen (IEnumerable)), ist sie deswegen für Arrays nicht zu empfehlen.

    Daher hätte ich ja Spans vorgeschlagen 😉

    @hkdd

    [DllImport("kernel32.dll", CallingConvention = CallingConvention.Cdecl)]
    static extern IntPtr RtlCompareMemory(byte[] b1, byte[] b2, IntPtr length);
    

    Sollte funktionieren. Zu beachten ist natürlich wie der Rückgabewert funktioniert: das Ding gibt die Anzahl der Bytes zurück die ab Start der Arrays gleich sind.
    Auf jeden Fall besser als sich unnötig selbst eine DLL zu stricken.



  • @hustbaer
    das habe ich so gemacht, bei mir klappt nur dieser DllImport

     [DllImport("ntdll.dll", EntryPoint = "RtlCompareMemory", SetLastError = false)]
            static extern long RtlCompareMemory(byte[] b1, byte[] b2, long length);
    

    Wenn ich den Import aus kernel32.dll benutze, findet die Routine unzutreffender Weise eine Menge ungleiche Dateien.

    Start = 08.12.2022 05:27:02
    Vergleichsmethode = RtlCompareMemory [DllImport] kernel32.dll
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    2.918.526.970 Bytes eingelesen
    770 Directories
    15.228 Dateien
    15.217 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    15.217 ungleiche Dateien
    Ende  = 08.12.2022 05:28:04, Dauer = 00:01:01.8
    

    Im Debugger kann ich nachverfolgen, um welche Dateien es sich Handelt.
    Beide Dateien sind zwingend identisch, da ich ja den Pfad mit sich selbst vergleiche.
    Es sind jpg-, zip- u.a. Dateien.

    Wenn ich den von Dir vorgeschlagenen DllImport

    [DllImport("kernel32.dll", CallingConvention = CallingConvention.Cdecl)]
    static extern IntPtr RtlCompareMemory(byte[] b1, byte[] b2, IntPtr length);
    

    benutze und dazu folgenden Aufruf

      case compRtlCompareMemory: // [DllImport] kernel32.dll RtlCompareMemory
    
        IntPtr len = (IntPtr)LeseLen;
        IntPtr rc = RtlCompareMemory(ByPu1, ByPu2, len);
    
        if ((int) rc != LeseLen)
        {
            ByteDiff++; // es gibt Differenzen
            return;     // Vergleich abbrechen
        }
        break;
    

    erhalte ich folgende Fehlermeldung beim Ausführen:

    System.EntryPointNotFoundException: "Der Einstiegspunkt "RtlCompareMemory" wurde nicht in der DLL "kernel32.dll" gefunden."

    Ich benutze Windo10 Pro 64 bit die aktuelle 22H2 Version.

    @Th69 sagte in Warum klappt Binary bei mir nicht:

    zuletzt noch byte [= 1])

    Dazu habe ich noch eine Frage

     if ((length & 1) != 0) 
       return *x1 == *x2;
    

    Müsste das nicht so lauten ?

     if ((length & 1) != 0) 
      return *(byte*)x1 == *(byte*)x2;
    

    bei *x1 == *x2 würden doch 8 Bytes verglichen, obwohl nur noch eines im Puffer vorhanden ist.


Anmelden zum Antworten