Warum klappt Binary bei mir nicht
-
Bist du dir sicher, daß du System.Data.Linq, also das Linq to SQL, verwenden möchtest - und nicht eher einfach System.Linq, also den Standard-Linq Namensbereich (dafür brauchst du dann auch keinen weiteren externen Assemblyverweis)?
-
@Th69
using System.Data.Linq; ist erforderlich, wenn man Binary benutzen möchte. Ohne diese Using-Anweisung wird Binary nicht akzeptiert. Mit SQL habe ich eigentlich nichts im Sinn. System.Linq war ja standardmäßig vorhanden, das hat aber mit Binary nichts zu tun. Ich habe also System.Data.Linq hinzugefügt, aber beim nächsten Projekt musste es erneut hinzugefügt werden.
Ich dachte, das Hinzufügen gilt für VS, und nicht nur für das jeweils konkrete Projekt.
-
@hkdd sagte in Warum klappt Binary bei mir nicht:
@hustbaer
es kommen zwei Meldungen
(1) IDE0005: Using-Direktive ist unnötig
(2) CS0234: Der Typ- oder Namespacename "Linq" ist im Namespace "System.Data" nicht vorhanden. (Möglicherweise fehlt ein Assemblyverweis)
Mögliche Korrekturen anzeigen (Alt+Eingabe oder Strg+.)(1) ist offensichtlich falsch, da für die Nutzung von Binary diese Using-Anweisung benötigt wird.
Nö, die ist nicht falsch, nur verwirrend. Ohne den Verweis auf die Assembly wo
Binary
drinnen ist kann er es ja nicht finden. Und da er es nicht findet, kann es auch nicht in dem File verwendet werden. Und da nichts aus dem Namespace in dem File verwendet wird, ist die Using-Direktive erstmal unnötig.Wenn du dann den Verweis auf die Assembly hinzufügst, dann findet er es. Und dann ist die Using-Direktive nicht mehr unnötig
zu (2):
Ich habe den Verweis bei dem betreffenden Testprogramm hinzugefügt => System.Data.Linq
Wenn ich nun ein anderes oder neues Projekt bearbeite, kommt immer wieder diese Meldung.
Muss man diesen Verweis für jedes Projekt immer wieder hinzufügen ?Ja. Und das ist auch gut so.
Warum gilt das nicht permanent für die installierte VS2022 Version auf meinem PC ?
Weil das in den Projekt-Files gespeichert wird, und nicht global für deinen PC. Wäre mMn. ziemlich doof, wenn es vom PC abhängig wäre welche Assemblies man verwenden kann und welche nicht. Dann müsste man auf jedem PC wo man das Projekt kompilieren will erstmal nen Haufen Einstellungen machen.
Gut, was er theoretisch machen könnte wäre sich zusätzlich irgendwo global zu merken welche Assemblies du jemals zu irgend einem Projekt hinzugefügt hast. Und diese dann zu jedem neu erstellten Projekt auch automatisch hinzuzufügen. Das mag zwar vielleicht praktisch klingen, wäre mMn. aber auch doof. Da würde nämlich mit der Zeit einiges zusammenkommen. Neu erstellte Projekte hätten dann erstmal nen Haufen Müll drinnen. Der bremst dann beim Kompilieren (die Assemblies müssen ja alle geladen werden). Und man würde schnell den Überblick verlieren was man überhaupt in einem Projekt braucht/verwendet.
-
@hkdd sagte in Warum klappt Binary bei mir nicht:
@Th69
using System.Data.Linq; ist erforderlich, wenn man Binary benutzen möchte. Ohne diese Using-Anweisung wird Binary nicht akzeptiert. Mit SQL habe ich eigentlich nichts im Sinn.Wozu willst du
System.Data.Linq.Binary
denn verwenden wenn du nicht mit Linq to SQL arbeitest? Also was geht damit, was mitbyte[]
bzw.List<byte>
nicht geht?
-
@hustbaer
ich wollte probieren, damit einen schnellen internen Vergleich von zwei byte[] Arrays zu realisieren.Binary b1 = new Binary(ByPu1); Binary b2 = new Binary(ByPu2); if(b1.Equals(b2)) { isDiff = 0; } // es gibt keine Differenzen else { isDiff = 1; } // es gibt Differenzen
-
Ich habe mir mal den
Binary
-Code mit ILSpy angeschaut.
Zum einen wird das Array kopiert (sowie ein Hash berechnet):public Binary(byte[] value) { if (value == null) bytes = new byte[0]; else { bytes = new byte[value.Length]; Array.Copy(value, bytes, value.Length); } ComputeHash(); } private void ComputeHash() { int num = 314; int num2 = 159; hashCode = 0; for (int i = 0; i < bytes.Length; i++) { hashCode = hashCode * num + bytes[i]; num *= num2; } }
und dann wird intern beim Vergleich zuerst der Hash überprüft, und nur bei Gleichheit per Arrayzugriff die einzelnen Bytes verglichen:
private bool EqualsTo(Binary binary) { if ((object)this == binary) return true; if ((object)binary == null) return false; if (bytes.Length != binary.bytes.Length) return false; if (GetHashCode() != binary.GetHashCode()) return false; int i = 0; for (int num = bytes.Length; i < num; i++) { if (bytes[i] != binary.bytes[i]) return false; } return true; }
Dies kann aber niemals schneller sein, als ein direkter Vergleich auf zwei Byte-Arrays (ohne zu kopieren).
Ich hatte dir ja auch schon mal den Hinweis auf
Enumerator.SequenceEqual
gegeben (kannst es auch direkt bei Arrays anwenden, ohneReadOnlySpan<T>
).
Oder du verwendest imunsafe
Modus Zeiger: Unsicherer Code, Zeigertypen und Funktionszeiger
-
Eigentlich ziemlich seltsam/peinlich dass .NET keine Array-Compare Funktion hat. Klar, braucht man jetzt nicht dauernd, aber für manche Sachen halt doch. Und dann is schon irgendwie doof dass man nur sub-optimale Möglichkeiten hat:
- Sowas wie
SequenceEqual
und hoffen dass der JIT es gut optimieren wird - Nen einfachen byte-für-byte Loop machen und hoffen dass der JIT es gut optimieren wird
- PInvoke verwenden um etwas ala
memcmp
aufzurufen - Grausamen Code mit unrolled Loops etc. selbst schreiben (bzw. sich von wo kopieren)
- Sowas wie
-
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 vonmemcmp
sowie dieunsafe
-Methode (mit jeweils 8 Byte Vergleich, d.h. so wie auchmemcmp
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-L1634D.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
(mitTake
) ist zwar immer noch ca. 3-4x langsamer als der byte-weise Vergleich, aber nicht mehr so extrem, wie unter .NET 4.
Abermemcmp
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 (nichti
)! Fällt auf, wenn es sich um unterschiedliche Dateien in beiden Ordnern handelt.
Außerdem benutzt du mehrfach den (ineffektiveren) Linq-Count()
, statt einfachLength
.
Aber für's Iterieren über alle Verzeichnisse und Dateien solltest du sowieso besser dieEnumerate...
statt derGet...
-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 mitgegebencase 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 != 0Es 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
stattif ((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 nochMemoryExtensions.SequenceEquals()
.Ich hatte irgendwas bzgl. "copy vermeiden" gelesen, was auch mit
Span<T>
/ReadOnlySpan<T>
durchCollectionsMarshal.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
], dannshort
[=2
] und zuletzt nochbyte
[=1
]).