Warum klappt Binary bei mir nicht



  • @hkdd @Th69
    Upps, sorry, hab übersehen dass der Code in der alten Version stand 👍



  • @hustbaer: Es gibt aber immer Unterschiede zwischen dem 1. und weiteren Läufen (ab dem 2. sind die Zeiten dann bis auf 1/10s gleich, beim ersten Lauf sind es aber auch mal einige Sekunden mehr - bei meinen Tests mit ca. 10s Laufzeit.)

    @hkdd: Du kannst es ja erst einmal mit einem kleinen Ordner laufen lassen.



  • @Th69
    Das kann viele Ursachen haben. Falls nicht das gesamte Datenset in deinen File-Cache passt kann es z.B. schonmal daran liegen. Beim Kompilieren fliegen ein paar Teile wieder aus dem File-Cache - die müssen beim 1. Durchlauf dann wieder geladen werden. Der 2. Durchlauf kann daher dann mehr aus dem Cache bekommen -> schneller.



  • Ich hab mir mal den Spass gemacht die Vergleiche in einen Hilfs-Thread auszuladern. Damit bleibt quasi nur mehr die Zeit zum Lesen übrig, die Vergleiche laufen dann Asynchron im 2. Thread:

    Alles sehr Q&D und ich kann nicht garantieren dass kein Bug drinnen ist:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Threading;
    
    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 enum Method
    		{
    			SequenceEqual,
    			MemCmp,
    			ForNxt,
    			Unsafe,
    			RtlCompareMemory,
    		}
    
    		public static Method VergleichsMethode = Method.RtlCompareMemory;
    
    		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 static BufferPairPool Pool = new BufferPairPool();
    		public static AsyncComparer Comparer;
    
    		public const int MaxPuffL = 128 * 1024;
    
    		[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
    		static extern int memcmp(byte[] b1, byte[] b2, IntPtr count);
    
    		[DllImport("ntdll.dll", EntryPoint = "RtlCompareMemory", SetLastError = false)]
    		private static extern IntPtr RtlCompareMemory(byte[] Source1, byte[] Source2, IntPtr length);
    
    		static void Main(string[] args)
    		{
    			using (var comp = new AsyncComparer(Pool))
    			{
    				Comparer = comp;
    				Main2(args);
    				Comparer = null;
    			}
    		}
    
    		static void Main2(string[] args)
    		{
    			DateTime dtStart = DateTime.Now;  // Start-Zeitpunkt merken
    			Console.WriteLine("Start = " + dtStart.ToString());
    			if (MitComp) Console.WriteLine("Vergleichsmethode = " + VergleichsMethode.ToString());
    			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
    			string StartPfad1 = "C:\\temp\\data";
    			string StartPfad2 = "C:\\temp\\data_copy";
    
    			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)
    		{
    			Comparer.Reset();
    
    			AnzFiles++; // Anzahl Dateien gesamt
    			Int64 RestLen = lenFile; // Dateilänge beider Dateien
    			using (var fs1 = new FileStream(pDsn1, FileMode.Open, FileAccess.Read))
    			using (var fs2 = new FileStream(pDsn2, FileMode.Open, FileAccess.Read))
    			{
    				Int64 LeseLen = Math.Min(RestLen, MaxPuffL);
    				while (LeseLen > 0)
    				{
    					if (Comparer.PeekDifferences() > 0)
    						break;
    
    					if (MitRead)
    					{
    						GesBytes += LeseLen;
    
    						var p = Pool.Get();
    
    						p.Length = (int)LeseLen;
    						fs1.Read(p.Buffer1, 0, (int)LeseLen);
    						fs2.Read(p.Buffer2, 0, (int)LeseLen);
    
    						if (MitComp)
    						{
    							AnzPuff++;
    							Comparer.Enqueue(p);
    						}
    					}
    
    					RestLen -= LeseLen;
    					LeseLen = Math.Min(RestLen, MaxPuffL);
    				}
    			}
    
    			if (Comparer.GetDifferences() > 0)
    				ByteDiff++;
    		}
    
    		static unsafe bool MemCmp2(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;
    				long* x0 = x1 + length / sizeof(long);
    				for (; x1 < x0; x1++, x2++)
    					if (*x1 != *x2)
    						return false;
    
    				byte* b1 = (byte*)x1, b2 = (byte*)x2;
    
    				const int SizeInt = sizeof(int);
    				if ((length & SizeInt) != 0)
    				{
    					if (*(int*)b1 != *(int*)b2)
    						return false;
    					b1 += SizeInt;
    					b2 += SizeInt;
    				}
    
    				const int SizeShort = sizeof(short);
    				if ((length & SizeShort) != 0)
    				{
    					if (*(short*)b1 != *(short*)b2)
    						return false;
    					b1 += SizeShort;
    					b2 += SizeShort;
    				}
    
    				if ((length & 1) != 0)
    					return *b1 == *b2;
    
    				return true;
    			}
    		}
    
    		public class BufferPair
    		{
    			public int Length;
    			public byte[] Buffer1 = new byte[MaxPuffL];
    			public byte[] Buffer2 = new byte[MaxPuffL];
    		}
    
    		public class BufferPairPool
    		{
    			public BufferPairPool()
    			{
    			}
    
    			public BufferPair Get()
    			{
    				lock (m_lock)
    				{
    					if (m_pool.Count == 0)
    						return new BufferPair();
    					var p = m_pool[m_pool.Count - 1];
    					m_pool.RemoveAt(m_pool.Count - 1);
    					return p;
    				}
    			}
    
    			public void Return(BufferPair p)
    			{
    				lock (m_lock)
    					m_pool.Add(p);
    			}
    
    			readonly object m_lock = new object();
    			readonly List<BufferPair> m_pool = new List<BufferPair>();
    		}
    
    		public class AsyncComparer : IDisposable
    		{
    			public AsyncComparer(BufferPairPool pool)
    			{
    				m_pool = pool;
    
    				m_comparerThread = new Thread(ThreadFn);
    				m_comparerThread.IsBackground = true;
    				m_comparerThread.Start();
    			}
    
    			public void Dispose()
    			{
    				lock (m_lock)
    				{
    					m_stop = true;
    					Monitor.PulseAll(m_lock);
    				}
    
    				m_comparerThread?.Join();
    				m_comparerThread = null;
    			}
    
    			public void Enqueue(BufferPair p)
    			{
    				lock (m_lock)
    				{
    					if (m_stop)
    						throw new ObjectDisposedException(nameof(AsyncComparer));
    
    					while (m_queue.Count > 1)
    						Monitor.Wait(m_lock);
    
    					m_queue.Enqueue(p);
    					Monitor.PulseAll(m_lock);
    				}
    			}
    
    			public int PeekDifferences()
    			{
    				lock (m_lock)
    				{
    					if (m_stop)
    						throw new ObjectDisposedException(nameof(AsyncComparer));
    
    					return m_differences;
    				}
    			}
    
    			public int GetDifferences()
    			{
    				lock (m_lock)
    				{
    					if (m_stop)
    						throw new ObjectDisposedException(nameof(AsyncComparer));
    
    					while (m_queue.Count > 0)
    						Monitor.Wait(m_lock);
    
    					return m_differences;
    				}
    			}
    
    			public void Reset()
    			{
    				lock (m_lock)
    				{
    					if (m_stop)
    						throw new ObjectDisposedException(nameof(AsyncComparer));
    
    					while (m_queue.Count > 0)
    						Monitor.Wait(m_lock);
    
    					m_differences = 0;
    				}
    			}
    
    			void ThreadFn()
    			{
    				ThreadFn0();
    
    				lock (m_lock)
    				{
    					m_queue.Clear();
    					Monitor.PulseAll(m_lock);
    				}
    			}
    
    			void ThreadFn0()
    			{
    				while (true)
    				{
    					BufferPair p = null;
    
    					lock (m_lock)
    					{
    						while (m_queue.Count == 0 && !m_stop)
    							Monitor.Wait(m_lock);
    
    						if (m_stop)
    							return;
    
    						p = m_queue.Peek();
    					}
    
    					bool result = Compare(p, VergleichsMethode);
    					m_pool.Return(p);
    
    					lock (m_lock)
    					{
    						if (m_stop)
    							return;
    
    						m_queue.Dequeue();
    						if (!result)
    							m_differences++;
    
    						Monitor.PulseAll(m_lock);
    					}
    				}
    			}
    
    			static bool Compare(BufferPair p, Method method)
    			{
    				switch (method)
    				{
    					case Method.SequenceEqual:
    						return p.Buffer1.Take(p.Length).SequenceEqual(p.Buffer2.Take(p.Length));
    
    					case Method.MemCmp:
    						return memcmp(p.Buffer1, p.Buffer2, (IntPtr)p.Length) == 0;
    
    					case Method.ForNxt:
    						int l = p.Length;
    						var b1 = p.Buffer1;
    						var b2 = p.Buffer2;
    						if (l > b1.Length)
    							throw new IndexOutOfRangeException();
    						if (l > b2.Length)
    							throw new IndexOutOfRangeException();
    						for (int b = 0; b < l; b++)
    						{
    							if (b1[b] != b2[b])
    								return false;
    						}
    						return true;
    
    					case Method.Unsafe:
    						return MemCmp2(p.Buffer1, p.Buffer2, p.Length);
    
    					case Method.RtlCompareMemory:
    						var eqCount = RtlCompareMemory(p.Buffer1, p.Buffer2, (IntPtr)p.Length);
    						return eqCount == (IntPtr)p.Length;
    
    					default:
    						throw new ArgumentException(nameof(method));
    				}
    			}
    
    			Thread m_comparerThread;
    			bool m_stop;
    			readonly BufferPairPool m_pool;
    
    			readonly object m_lock = new object();
    			readonly Queue<BufferPair> m_queue = new Queue<BufferPair>();
    			int m_differences;
    		}
    	}
    }
    


  • ps: Puffer-Grösse hab ich runtergesetzt weil kleinere Puffer bei mir schneller sind. Vermutlich auch wieder ne Caching-Sache (CPU Caches).



  • @Th69
    ich habe beide Routinen jeweils zweimal laufen lassen, dabei gibt es kaum einen Unterschied

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    CompCs (09.12.2022)  - Start = 10.12.2022 14:02:37
    Vergleichsmethode = unsafe MemCmp von Th69
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    Pfad1=N:\VideosN\, Pfad2=N:\VideosN\
    208.144.569.612 Bytes eingelesen
    83 Directories
    2.446 Dateien
    14.071 Puffer
    2.446 identische Dateien
    Ende  = 10.12.2022 14:36:56, Dauer = 00:34:18.8
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    CompCs (09.12.2022)  - Start = 10.12.2022 14:53:03
    Vergleichsmethode = unsafe MemCmp von Th69
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    Pfad1=N:\VideosN\, Pfad2=N:\VideosN\
    208.144.569.612 Bytes eingelesen
    83 Directories
    2.446 Dateien
    14.071 Puffer
    2.446 identische Dateien
    Ende  = 10.12.2022 15:25:58, Dauer = 00:32:55.3
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    CompCs (09.12.2022)  - Start = 10.12.2022 15:29:33
    Vergleichsmethode = CompareByteArray (C++ ASM DLL von HK)
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    Pfad1=N:\VideosN\, Pfad2=N:\VideosN\
    208.144.569.612 Bytes eingelesen
    83 Directories
    2.446 Dateien
    14.071 Puffer
    2.446 identische Dateien
    Ende  = 10.12.2022 16:02:58, Dauer = 00:33:24.8
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    CompCs (09.12.2022)  - Start = 10.12.2022 16:04:11
    Vergleichsmethode = CompareByteArray (C++ ASM DLL von HK)
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    Pfad1=N:\VideosN\, Pfad2=N:\VideosN\
    208.144.569.612 Bytes eingelesen
    83 Directories
    2.446 Dateien
    14.071 Puffer
    2.446 identische Dateien
    Ende  = 10.12.2022 16:37:22, Dauer = 00:33:10.8
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    


  • @hustbaer
    ich habe Dein bearbeitetes Programm mit den gleichen Daten laufen lassen - das Ergebnis sieht gut aus

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Start = 10.12.2022 17:02:50
    Vergleichsmethode = RtlCompareMemory
    MitRead=True, MitComp=True
    131.072 Bytes = Puffergröße
    208.144.569.612 Bytes eingelesen
    83 Directories
    2.446 Dateien
    1.589.437 Puffer
    0 fehlende Dateien
    0 fehlende Unterpfade
    0 Dateien mit unterschiedlicher Länge
    0 ungleiche Dateien
    Ende  = 10.12.2022 17:28:33, Dauer = 00:25:43.1
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    


  • @hustbaer
    Ich habe Deine Routine in mein Compareprogramm eingebaut und bekomme eigentlich die gleichen Ergebnisse.
    Mit der kleineren Puffergröße läuft es deutlich schneller, auch die Vergleichsroutine von Th69.
    Hier noch ein paar Ergebnisse

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    CompCs (09.12.2022)  - Start = 10.12.2022 18:22:47
    Vergleichsmethode = RtlCompareMemory [DllImport] kernel32.dll
    MitRead=True, MitComp=True
    131.072 Bytes = Puffergröße
    Pfad1=N:\VideosN\, Pfad2=N:\VideosN\
    208.144.569.612 Bytes eingelesen
    83 Directories
    2.446 Dateien
    1.589.437 Puffer
    2.446 identische Dateien
    Ende  = 10.12.2022 18:48:31, Dauer = 00:25:44.5
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    CompCs (09.12.2022)  - Start = 10.12.2022 18:50:50
    Vergleichsmethode = RtlCompareMemory [DllImport] kernel32.dll
    MitRead=True, MitComp=True
    16.777.216 Bytes = Puffergröße
    Pfad1=N:\VideosN\, Pfad2=N:\VideosN\
    208.144.569.612 Bytes eingelesen
    83 Directories
    2.446 Dateien
    14.071 Puffer
    2.446 identische Dateien
    Ende  = 10.12.2022 19:23:14, Dauer = 00:32:24.0
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    CompCs (09.12.2022)  - Start = 10.12.2022 19:26:05
    Vergleichsmethode = unsafe MemCmp von Th69
    MitRead=True, MitComp=True
    131.072 Bytes = Puffergröße
    Pfad1=N:\VideosN\, Pfad2=N:\VideosN\
    208.144.569.612 Bytes eingelesen
    83 Directories
    2.446 Dateien
    1.589.437 Puffer
    2.446 identische Dateien
    Ende  = 10.12.2022 19:52:05, Dauer = 00:25:59.9
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    Jetzt habe ich das auch in meinem Delphi-Programm getestet, das ist kein bemerkenswerter Laufzeitunterschied bei veränderter Puffergröße festzustellen
    Start 09:12:13 Ende 09:38:37 Dauer 00:26:24 - 16.777.216 Bytes = Puffergröße
    Start 15:19:19 Ende 15:45:34 Dauer 00:26:15 - 131.072 Bytes = Puffergröße

    Das muss mit C# zusammenhängen.



  • @hkdd
    Du könntest auch versuchen beim Öffnen FileOptions.SequentialScan mitzugeben. Mit dem Asynchronen Vergleich wird es vermutlich nichts bringen. Wenn du synchron vergleichst u.U. schon.

    Jetzt habe ich das auch in meinem Delphi-Programm getestet, das ist kein bemerkenswerter Laufzeitunterschied bei veränderter Puffergröße festzustellen
    Start 09:12:13 Ende 09:38:37 Dauer 00:26:24 - 16.777.216 Bytes = Puffergröße
    Start 15:19:19 Ende 15:45:34 Dauer 00:26:15 - 131.072 Bytes = Puffergröße

    Das muss mit C# zusammenhängen.

    Hm. OK. Dann weiss ich nicht woran es liegt 🙂



  • @hustbaer
    mit meiner Delphi-Aussage muss ich mich revidieren. Die Puffer-Größe hat doch eine entscheidende Auswirkung.
    Hier ein paar meiner Mess-Ergebnisse.

    Puffergröße     Laufzeit in 
      hex            Min:Sek
    0x010000           2:04
    0x020000           1:14  <= 128 * 1024
    0x030000           1:00
    0x040000           0:56
    0x050000           0:51
    0x060000           0:47
    0x070000           0:43
    0x080000           0:41
    0x090000           0:44
    0x0A0000           0:43
    0x0B0000           0:40
    0x0C0000           0:42
    0x0D0000           0:40
    0x0E0000           0:40
    0x0F0000           0:40
    0x100000           0:37
    0x110000           0:39
    0x120000           0:38
    0x130000           0:41
    0x140000           0:37
    0x150000           0:37
    0x160000           0:38
    0x170000           0:28
    0x180000           0:25 = 1536 * 1024
    0x200000           0:38
    0x280000           0:35
    0x300000           0:33
    

    Ich habe mich jetzt für 0x180000 Bytes entschieden.
    Das ist gut in Delphi und in C#.

    Evtl. hängt das aber auch von der Hardware ab (CPU, RAM, GDD, SSD, m.2 u.a.m.).

    @hustbaer sagte in Warum klappt Binary bei mir nicht:

    Mit dem Asynchronen Vergleich wird es vermutlich nichts bringen. Wenn du synchron vergleichst u.U. schon.

    Was meinst Du damit ?
    Ich lese Datei1 in Puffer1 und Datei2 in Puffer , danach Vergleich Puffer 1 mit 2, und danach wieder Datei1 in Puffer1 usw.

    Hier die Ergebnisse der C#- und der Delphi-Version

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    CompHKw - Dateien Vergleichen - (C) 12.12.2022 Hartmut aus Dresden
    -------------------------------------------------------------------
    Parameter: "N:\VideosN" "N:\VideosN" "/U" "/VN" "/F" "/M:200" "/A"
    -------------------------------------------------------------------
    Vergleich: N:\VideosN\     D E L P H I - V E R S I O N
          mit: N:\VideosN\
    ~~~~~~~~~~~~~~~~~~~~~~~
    2.446.Datei: N:\VideosN\Youtube-HK\...den 1971\zz09 So scheid ich denn (Finale).mp4
    =================================================================
         82 Unter-Pfade gefunden   - identisch.
      2.446 Dateien     gefunden   - und verglichen...
      2.446 Dateien     verglichen - identisch.
          Es wurden 208.144.569.612 Bytes = 203.266.181 K-Bytes verglichen
          Start 18:51:31  Ende 19:18:00  Dauer 00:26:29
    ========================================================<Ende>===
    Linke MausTaste / ESC = Schließen   Rechte MausTaste / F1 = Info
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    CompCs - Dateien vergleichen - (C) 12.12.2022 Hartmut aus Dresden
    ---------------------------------------------------------------
    Parameter: N:\VideosN  N:\VideosN  
    ---------------------------------------------------------------
    Vergleich: N:\VideosN\          C # - V E R S I O N
          mit: N:\VideosN\          Th69 Vergleichs-Methode
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ============================================================
        82 Unterordner gefunden
        2.446 Dateien     gefunden
    	- davon 2.446 identische Dateien verglichen.
        Es wurden 208.144.569.612 Bytes = 203.266.181 K-Bytes verglichen.
        133.902 Puffer gelesen, Pufferlänge: 1.572.864 Bytes.
        Start 19:24:40  Ende 19:50:57  Dauer 00:26:17.5
    ===================================================<Ende>===
      ESC = Programm beenden
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    


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

    @hustbaer
    mit meiner Delphi-Aussage muss ich mich revidieren. Die Puffer-Größe hat doch eine entscheidende Auswirkung.
    Hier ein paar meiner Mess-Ergebnisse.

    Puffergröße     Laufzeit in 
      hex            Min:Sek
    0x010000           2:04
    0x020000           1:14  <= 128 * 1024
    0x030000           1:00
    0x040000           0:56
    0x050000           0:51
    0x060000           0:47
    0x070000           0:43
    0x080000           0:41
    0x090000           0:44
    0x0A0000           0:43
    0x0B0000           0:40
    0x0C0000           0:42
    0x0D0000           0:40
    0x0E0000           0:40
    0x0F0000           0:40
    0x100000           0:37
    0x110000           0:39
    0x120000           0:38
    0x130000           0:41
    0x140000           0:37
    0x150000           0:37
    0x160000           0:38
    0x170000           0:28
    0x180000           0:25 = 1536 * 1024
    0x200000           0:38
    0x280000           0:35
    0x300000           0:33
    

    Ich habe mich jetzt für 0x180000 Bytes entschieden.
    Das ist gut in Delphi und in C#.

    Evtl. hängt das aber auch von der Hardware ab (CPU, RAM, GDD, SSD, m.2 u.a.m.).

    Ja, vermutlich. Was auch mit reinspielt ist ob du zwei Verzeichnisse auf dem selben Datenträger vergleichst oder zwei Verzeichnisse auf verschiedenen Datenträgern. Speziell wenn beide Verzeichnisse auf der selben HDD steht werden sehr grosse Puffer vermutlich viel bringen. Wenn es dagegen verschiedene Datenträger sind oder die Daten von einer SSD kommen sollte eine Puffergrösse zwischen 64k und 1 MB ausreichend sein.

    @hustbaer sagte in Warum klappt Binary bei mir nicht:

    Mit dem Asynchronen Vergleich wird es vermutlich nichts bringen. Wenn du synchron vergleichst u.U. schon.

    Was meinst Du damit ?
    Ich lese Datei1 in Puffer1 und Datei2 in Puffer , danach Vergleich Puffer 1 mit 2, und danach wieder Datei1 in Puffer1 usw.

    Mit FileOptions.SequentialScan teilst du dem OS mit dass du vor hast die Datei linear zu lesen. Das OS sollte dann dafür sorgen dass die nächsten Stücke der Datei bereits im Hintergrund gelesen werden, bevor dein Programm sie anfordert ("read ahead"). Wenn du wie in der von mir veränderten Version den Vergleich asynchron in einem eigenen Thread machst, dann wird das nicht viel bringen. Weil ja der Haupt-Thread sofort das nächste Datenstück anfordert. Wenn du dagegen den Vergleich im selben Thread machst wie das Lesen kann das viel bringen. Weil das OS dann eben während dein Programm den Vergleich ausführt bereits das nächste Datenstück von der HDD/SSD anfordert.

    Allerdings sollte das OS auch schlau genug sein zu erkennen dass du die Datei linear liest, ohne dass du FileOptions.SequentialScan verwendest. Das kann aber wiederrum abhängig von der Puffergrösse sein. Auf Linux z.B. bekommst du AFAIK read-ahead per Default nur mit Puffergrössen bis max. 128 kB.



  • Wenn die Verzeichnisse auf unterschiedlichen Datenträgern liegen könnte folgende Änderung nochmal einiges bringen:

    		using (var fs1 = new FileStream(pDsn1, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan))
    		using (var fs2 = new FileStream(pDsn2, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan))
    		{
    				// ...
    				// ...
    				p.Length = (int)LeseLen;
    				var r1 = fs1.ReadAsync(p.Buffer1, 0, (int)LeseLen);
    				fs2.Read(p.Buffer2, 0, (int)LeseLen);
    				r1.Wait();
    
    				if (MitComp)
    				// ...
    

    Mit kleinen Puffern macht das die Sache bei mir langsamer, aber mit ~1MB Puffern bringt es bei mir nochmal ne deutliche Beschleunigung.



  • Jetzt müsste man nur noch eine Funktion finden, welche automatisch für die lokale Gegebenheit die beste Puffergröße ermittelt.



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

    using (var fs2 = new FileStream(pDsn2, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan))

    Wenn ich diese Parameter hinzufüge ( 4 und 5)

      FileStream fs2 = new FileStream(pDsn2, FileMode.Open, FileAccess.Read, 4096, FileOptions.SequentialScan);
    

    dann wird für (4) = 4096 angezeigt: CS1503: Konvertierung von int in System.IO.FileShare nicht möglich.
    und für (5) = FileOptions.SequentialScan wird angezeigt:
    CS1503: Konvertierung von System.IO.FileOptions in int nicht möglich.

    Mit ALT+Eingabe wird das Statement folgendermaßen korrigiert

    FileStream fs2 = new FileStream(pDsn2, FileMode.Open, FileAccess.Read, (FileShare)4096, (int)FileOptions.SequentialScan);
    

    Beim Ausführen erhalte ich allerdings einen Fehler:
    System.ArgumentOutOfRangeException: "Der Enumerationswert lag außerhalb des gültigen Bereichs. Parametername: share"

    Ich benutze allerdings kein USING.



  • @hkdd
    Du hast FileShare.Read unterschlagen.



  • @hustbaer
    JA, das war es - trotz meiner neuen Linsen in den Augen habe ich das übersehen.
    Was bedeuten diese 4096, ist das der Lesepuffer oder nimmt Read dann die angegebene Anzahl Bytes zum lesen ?



  • @hkdd

    Was bedeuten diese 4096, ist das der Lesepuffer oder nimmt Read dann die angegebene Anzahl Bytes zum lesen ?

    Das ist die Grösse für den internen Puffer der File-Streams. Wenn kleinere Lese bzw. Schreib-Operationen gemacht werden, dann gehen die über den Puffer. Oft liest bzw. schreibt man Dateien ja in kleinen Stücken, da bringt das dann einiges.

    Lese bzw. Schreib-Operationen die grösser als der interne Puffer sind werden allerdings direkt an die OS Funktionen weitergeleitet. Für dein Programm ist der Puffer also irrelevant. Alle Konstruktoren wo man FileOptions angeben kann erfordern allerdings dass man die Puffergrösse angibt. Also hab ich einfach den Default-Wert von 4096 genommen.



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

    Ich benutze allerdings kein USING.

    Macht in dem Fall keinen Unterschied. Bloss: wieso verwendest du es nicht?



  • @hustbaer
    Ein wenig schneller scheint es zu sein. Das erste Ergebnis ist von gestern und das zweite von heute

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ============================================================
        82 Unterordner gefunden
        2.446 Dateien     gefunden
    	- davon 2.446 identische Dateien verglichen.
        Es wurden 208.144.569.612 Bytes = 203.266.181 K-Bytes verglichen.
        133.902 Puffer gelesen, Pufferlänge: 1.572.864 Bytes.
        Start 19:24:40  Ende 19:50:57  Dauer 00:26:17.5
    ===================================================<Ende>===
      ESC = Programm beenden
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    CompCs - Dateien vergleichen - (C) 12.12.2022 Hartmut aus Dresden
    ---------------------------------------------------------------
    Parameter: N:\VideosN  N:\VideosN  
    ---------------------------------------------------------------
    Vergleich: N:\VideosN\
          mit: N:\VideosN\
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ============================================================
        82 Unterordner gefunden
        2.446 Dateien     gefunden
    	- davon 2.446 identische Dateien verglichen.
        Es wurden 208.144.569.612 Bytes = 203.266.181 K-Bytes verglichen.
        133.902 Puffer gelesen, Pufferlänge: 1.572.864 Bytes.
        Start 16:08:58  Ende 16:34:20  Dauer 00:25:22.0
    ===================================================<Ende>===
      ESC = Programm beenden
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    Ich habe versucht bei den beiden FileStream Anweisungen USING davor zu schreiben und nach dem zweiten Using den Rest mit {...} einzuklammern - das klappt aber nicht. Ohne USING - alles ist OK.

            //=================================================
            // Datei 1 und 2 lesen und vergleichen
            //=================================================
            private bool VergleicheDateien(string pDsn1, string pDsn2, Int64 lenFile, string pPfad)
            {
                bool rc;
                Int64 RestLen = lenFile; // Dateilänge beider Dateien
                Int64 LeseLen = Math.Min(RestLen, MaxPuffL);
    
                FileStream fs1 = new FileStream(pDsn1, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan);
                FileStream fs2 = new FileStream(pDsn2, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan);
    
                while (LeseLen > 0)
                {
                    Application.DoEvents();
                    if (Abbruch > 0) { return false; }
    
                    GesBytes += LeseLen; // Anzahl gelesene Bytes gesamt
    
                    this.Text = "CompHk " + GesBytes.ToString("###,###,###,###,##0") + " Bytes " + pPfad;
    
                    fs1.Read(ByPu1, 0, (int)LeseLen);
                    fs2.Read(ByPu2, 0, (int)LeseLen);
    
                    AnzPuff++;                 // Anzahl verglichene Puffer
    
                    if (LeseLen < 9)
                    {
                        for (int b = 0; b < LeseLen; b++)
                        {
                            if (ByPu1[b] != ByPu2[b])
                            {
                                goto VerglErr;     // Vergleich abbrechen
                            }
                        }
                        break;
                    }
    
                    switch (VergleichsMethode) // Welche Art des Vergleiches
                    {
                        case compSequenceEqual: // C# interne Funktion
                            if (!(ByPu1.Take((int)LeseLen).SequenceEqual(ByPu2.Take((int)LeseLen))))
                            {
                                goto VerglErr;     // Vergleich abbrechen
                            }
                            break;
                        case compMemCmp: // [DllImport]  msvcrt.dll
                            if (memcmp(ByPu1, ByPu2, LeseLen) != 0)
                            {
                                goto VerglErr;     // Vergleich abbrechen
                            }
                            break;
                        case compForNxt: // C# interner Byte-für-Byte-Vergleich
                            for (int b = 0; b < LeseLen; b++)
                            {
                                if (ByPu1[b] != ByPu2[b])
                                {
                                    goto VerglErr;     // Vergleich abbrechen
                                }
                            }
                            break;
                        case compUnsafe: // unsafe MemCmp
                            if (!MemCmp(ByPu1, ByPu2, (int)LeseLen))
                            {
                                goto VerglErr;     // Vergleich abbrechen
                            }
                            break;
                        case compRtlCompareMemory: // [DllImport] kernel32.dll RtlCompareMemory
    
                            IntPtr len = (IntPtr)LeseLen;
                            //IntPtr rc = RtlCompareMemory(ByPu1, ByPu2, len);
                            var rCod = RtlCompareMemory(ByPu1, ByPu2, len);
    
                            if (rCod != len)
                            {
                                goto VerglErr;     // Vergleich abbrechen
                            }
                            break;
                        case compCompareByteArray: // [DllImport] CompareByteArray.dll von HK
                            if (CompareByteArray(ByPu1, ByPu2, (int)LeseLen) != 0)
                            {
                                goto VerglErr;     // Vergleich abbrechen
                            }
                            break;
                    } // switch (VergleichsMethode)
    
                    RestLen -= LeseLen;
                    LeseLen = Math.Min(RestLen, MaxPuffL);
    
                } //   while (LeseLen > 0)
            
    
            AnzFilesOK++;
                rc = true;
                goto UpEnde;     // Vergleich abbrechen
    
            VerglErr:
                ByteDiff++; // es gibt Differenzen
                rc = false;
    
            UpEnde:
                fs1.Close();
                fs2.Close();
    
                return rc;
    
            } // static bool VergleicheDateien(string pDsn1, string pDsn2, Int64 lenFile)
    


  • @hkdd
    Hab's nicht ausprobiert, aber ich nehme an der Grund für die Fehler ist dass du gotos verwendest. Ich würde empfehlen auf goto zu verzichten. Weiters wäre es praktisch die eigentliche Vergleichs-Funktion vom Rest des Programms zu entkoppeln. z.B. von der Aktualisierung des User-Interface und dem Test ob abgebrochen werden soll. Könnte dann z.B. so aussehen:

            private bool VergleicheDateien(string pDsn1, string pDsn2, Int64 lenFile, string pPfad)
            {
                int res = VergleicheDateien0(pDsn1, pDsn2, lenFile, leseLen =>
                {
                    Application.DoEvents();
                    if (Abbruch > 0) { return false; }
    
                    GesBytes += leseLen; // Anzahl gelesene Bytes gesamt
                    AnzPuff++;           // Anzahl verglichene Puffer
    
                    this.Text = "CompHk " + GesBytes.ToString("###,###,###,###,##0") + " Bytes " + pPfad;
                    return true;
                });
    
                switch (res)
                {
                    case 0:
                        ByteDiff++;
                        return false;
                    case 1:
                        AnzFilesOK++;
                        return true;
                    default:
                        return false;
                }
            }
    
            private int VergleicheDateien0(string pDsn1, string pDsn2, Int64 lenFile, Func<long, bool> fortschrittsCallback)
            {
                Int64 RestLen = lenFile; // Dateilänge beider Dateien
                Int64 LeseLen = Math.Min(RestLen, MaxPuffL);
    
                using (FileStream fs1 = new FileStream(pDsn1, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan))
                using (FileStream fs2 = new FileStream(pDsn2, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan))
                {
                    while (LeseLen > 0)
                    {
                        if (!fortschrittsCallback(LeseLen)) { return -1; }
    
                        fs1.Read(ByPu1, 0, (int)LeseLen);
                        fs2.Read(ByPu2, 0, (int)LeseLen);
    
                        if (LeseLen < 9)
                        {
                            for (int b = 0; b < LeseLen; b++)
                            {
                                if (ByPu1[b] != ByPu2[b])
                                {
                                    return 0;
                                }
                            }
                            break;
                        }
    
                        switch (VergleichsMethode) // Welche Art des Vergleiches
                        {
                            case compSequenceEqual: // C# interne Funktion
                                if (!(ByPu1.Take((int)LeseLen).SequenceEqual(ByPu2.Take((int)LeseLen))))
                                {
                                    return 0;
                                }
                                break;
                            case compMemCmp: // [DllImport]  msvcrt.dll
                                if (memcmp(ByPu1, ByPu2, LeseLen) != 0)
                                {
                                    return 0;
                                }
                                break;
                            case compForNxt: // C# interner Byte-für-Byte-Vergleich
                                for (int b = 0; b < LeseLen; b++)
                                {
                                    if (ByPu1[b] != ByPu2[b])
                                    {
                                        return 0;
                                    }
                                }
                                break;
                            case compUnsafe: // unsafe MemCmp
                                if (!MemCmp(ByPu1, ByPu2, (int)LeseLen))
                                {
                                    return 0;
                                }
                                break;
                            case compRtlCompareMemory: // [DllImport] kernel32.dll RtlCompareMemory
    
                                IntPtr len = (IntPtr)LeseLen;
                                //IntPtr rc = RtlCompareMemory(ByPu1, ByPu2, len);
                                var rCod = RtlCompareMemory(ByPu1, ByPu2, len);
    
                                if (rCod != len)
                                {
                                    return 0;
                                }
                                break;
                            case compCompareByteArray: // [DllImport] CompareByteArray.dll von HK
                                if (CompareByteArray(ByPu1, ByPu2, (int)LeseLen) != 0)
                                {
                                    return 0;
                                }
                                break;
                        } // switch (VergleichsMethode)
    
                        RestLen -= LeseLen;
                        LeseLen = Math.Min(RestLen, MaxPuffL);
    
                    } //   while (LeseLen > 0)
    
                    return 1;
                }
            }
    

    Die neue Funktion VergleicheDateien0 ist jetzt völlig unabhängig vom User-Interface. Was viele Vorteile hat, z.B. dass man sie dadurch unabhängig vom User-Interface testen kann.


Anmelden zum Antworten