Laden...

Performant von vielen Dateien die Größe bestimmen

Erstellt von thetruedon vor 10 Jahren Letzter Beitrag vor 10 Jahren 6.032 Views
thetruedon Themenstarter:in
111 Beiträge seit 2011
vor 10 Jahren
Performant von vielen Dateien die Größe bestimmen

Hallo Leute,
ich habe zur Zeit ein kleines Problem zwecks Performance.
Ich habe etwa 1,6 Millionen Dateien in fast 13000 Unterordnern und will von diesen unter anderem die Dateigröße bestimmen. (Dateien um die 400KB)

Nun durchlaufe ich mit Directory.EnumerateFiles die Verzeichnisse.
Die gefundenen Dateien werden dann mittels

var filesize = new FileInfo("pfad").Length;

geprüft. Das Funktioniert zwar ist aber elendig Langsam. (zum Vergleich alle Dateipfade in eine Datenbank zu schreiben dauert rund 10 Minuten ohne Filesize und 2 Stunden mit filesize)
Das alles bei einem i7, SSD und 8GB RAM und einer GBit Leitung.

Fällt euch eine Möglichkeit ein nur die Größe einer Datei noch schneller zu bestimmen? Oder muss ich in die Wartezeit einfach ertragen?

Kommt ein Mann in die Wirtschaft und sagt zum Wirt: 16 Bit!
Sagt der Wirt: Das ist ein Wort!

2.078 Beiträge seit 2012
vor 10 Jahren

Ich wüsste keine schnellere Möglichkeit, aber ich glaube nicht, dass die SSD oder CPU zu langsam ist.

Versuche doch, ganz am Anfang die Ordner in einzelne Tasks aufzuteilen und auch die Folgeordner in eigene Tasks zu legen. Geh nur nicht zu weit, sonst hast du Unmengen Tasks.

Edit:

Brauchst du überhaupt alle Dateigrößen?
Du hast was von einer Datenbank gesagt. Wenn du doch den Dateipfad darin liegen hast, dann lasse die Größe doch raus und ließ sie erst aus, wenn du sie brauchst.

1.696 Beiträge seit 2006
vor 10 Jahren

Hallo,

mit WinAPI-Funktion GetFileSizeEx geht es mit Sicherheit schneller

Grüße

Ich bin verantwortlich für das, was ich sage, nicht für das, was du verstehst.

**:::

16.807 Beiträge seit 2008
vor 10 Jahren

Also ich hab am Sonntag nen Open Source (MS-PL) Projekt begonnen, das via Win32 API die Win32 FIND DATA (Pfad und Größe und Datum) zurückgeben wird.
Das ist die schnellste, unabhängige Möglichkeit an diese Informationen zu kommen (die Informationen (Pfad, Größe, Datum) wird immer im Dreier-Pack geliefert; sparsamer geht also nicht).

Der Faktor der Geschwindigkeit kann bei ca. 1000 sein (schneller als System.IO). Siehe ein ähnliches, älteres Projekt: A Faster Directory Enumerator
Ähnliches Prinzip verwende ich in einigen Projekten (das kann und darf ich aber nicht raus geben). Darunter eines, das einen Datenpool mit 600GB und fast 1 Mio Dateien hat. Ist in ca. 40 Sekunden komplett durchlaufen.
Ebenso werden UNC Pfade (bis 32767 Zeichen unterstützt).
Letzter Stand ist noch nicht hochgeladen und der Code ist auch noch nicht sonderlich dokumentiert.

Wird in 1-2 Wochen fertig sein wenn Interesse besteht; Erweiterungen / Forks natürlich gestattet.
http://fastio.codeplex.com
Es soll aber nur eine Erweiterung, kein Ersatz sein. Sprich eben gerade für rekursives Durchsuchen etc.

Das Problem bei System.IO ist, dass sofort alle Informationen inkl Attribute und Co angefragt werden. Das heisst, dass pro Datei und Ordner mindestens 3 (bis 5) Abfrage an das Dateisystem gesendet wird; das macht alles ungemein langsam. Über das Netzwerk und das SMB Protokoll wird die Performance dann völlig in den Keller gehen (je kleiner die Dateien desto höher der prozentuale Anteil des SMB Headers (Overhead) => langsam)

⚠ Die Idee mit Tasks und Parallelität ist eine ganz arg schlechte Idee und kontraproduktiv.
Bei einer SSD wird oft der Controller durch die Anfragen überfordert (vor allem die Cache-Tabelle) und HDD-Platten springen auf der Disk dauern hin und her. Fazit: es wird langsamer.

thetruedon Themenstarter:in
111 Beiträge seit 2011
vor 10 Jahren

Danke schon mal für die Tipps ich werde mir die Sachen die Tag mal genauer ansehen.
Das wird erst mal weiterhelfen wenn ich eine konkrete Lösung habe poste ich die dann noch.

@Palladin007 Die gesamten Dateigrößen sind wichtig in der Datenbank deshalb kann ich das leider nicht nur bei bedarf abfragen.
@Abt Das klingt erst mal genau nach der gesuchten Problemlösung 😃 ich werde schauen wie ich da jetzt weiter komme

Bittedankeschön

Kommt ein Mann in die Wirtschaft und sagt zum Wirt: 16 Bit!
Sagt der Wirt: Das ist ein Wort!

M
24 Beiträge seit 2006
vor 10 Jahren

Es gibt Programme, die auf einem NTFS-Filesystem aus der MFT (Master File Table) Dateiinfos anzeigen können und das sehr schnell, weil nicht auf die Datei selbst zugegriffen wird, sondern eben nur auf das Inhaltsverzeichnis.
Es werden deshalb auch nur die Infos angezeigt, die dort abgelegt sind.

Eins dieser Programme, das ich häufig verwende, ist everything

Ob die Dateigröße in der MFT wirklich zur Verfügung steht weiss ich nicht sicher, everything zeigt sie aber mit an, das spricht dafür.

Der Entwickler liefert auch ein "SDK" (DLL und Binding für C#).

Getestet habe ich das nicht, schneller geht aber IMHO nicht.

5.657 Beiträge seit 2006
vor 10 Jahren

Es gibt Programme, die auf einem NTFS-Filesystem aus der MFT (Master File Table) Dateiinfos anzeigen können

Das ist ein interessanter Hinweis. Ich hab dazu noch einige Infos gefunden:

Das ist sicherlich der performanteste Weg, um nach Dateien zu suchen.

Christian

Weeks of programming can save you hours of planning

C
1.214 Beiträge seit 2006
vor 10 Jahren

Siehe auch Grundlagenforschung des Windows Explorers bei der Ordneranalyse (Schnittstellen)

Anscheinend ist Abt hier schon über Grundlagenforschung hinaus und will das in die Praxis umsetzen 😉

16.807 Beiträge seit 2008
vor 10 Jahren

Die Praxis ist schon ewig umgesetzt. Shell ist leider nicht immer verfügbar; daher eben wegen der Breite Win32 API.
Ich setz das ganze nur jetzt mal auf Open Source Projekt um. 😁

Dazu sei korrigiert: EnumerateFiles ist sehr schnell, sehr sehr schnell.
Es ist als Iterator aufgebaut und verwendet ebenfalls Win32 PInvokes via unsafe Context. Da ran zu kommt ist ziemlich schwer.
Was bei System.IO hier langsam ist, ist FileInfo, das zusätzliche Informationen lädt.
Deswegen wird die Suche selbst ungemein schnell ausgeführt, aber ein Summieren der Bytes lässt die Geschwindigkeit in den Keller fahren.

2.078 Beiträge seit 2012
vor 10 Jahren

Ok, verschiedene Tasks sind eine schlechte Idee. Jetzt wo du es erklärst, klingt das auch logisch und ich hab wieder was gelernt. ^^

Zu deinem Projekt, Abt:

Du sagst, du möchtest zu Dateien grundlegende Informationen wie Name, Datum und Größe sehr schnell auslesen lassen.
Kannst du das eventuell auch größer aus legen und daraus ein kleines Framework bauen, das grundlegende Funktionen im Umgang mit Dateien unterstützt?
Für ein zukünftiges Projekt, dessen Idee schon lange existiert, wäre das eine große Hilfe, besonders wenn das so deutlich schneller arbeitet.

16.807 Beiträge seit 2008
vor 10 Jahren

Das Projekt ist als als einfache Erweiterung gedacht. Sprich Auslesen und UNC-Unterstützung.
Grundlegende Dinge werden beschleunigt aber nicht gezaubert. Das reine Auslesen von Pfaden über EnumerateFiles wird sicherlich nicht erreicht. Die .NET Implementierung ab 4.0 ist hier schon sehr gut (läuft über unsafe und Co); Managed kommt man da nicht ran. Aber sobald FileInfo im Spiel ist, sinkt die Geschwindigkeit leider.

Ich mach daraus kein Lebenswerk aber man kann ja einen Fork erstellen und selbst Dinge daraus weiter entwickeln.

2.078 Beiträge seit 2012
vor 10 Jahren

Nagut, dann freue ich mich auf die Fertigstellung und vielleicht versteh ich sogar, was du da gemacht hast und versuche, das dann möglichst ohne FileInfo zu erweitern 😄

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo thetruedon,

mal abgesehen vom Ausweichen auf Win32 noch ein Wort zu System.IO: Neben Directory.EnumerateFiles gibt es auch noch Directory**Info**.EnumerateFiles, welches gleich FilesInfos liefert und daher sicher besser und schneller ist, als für (nahezu) jede Datei einzeln new FileInfo aufzurufen. Um welchen Faktor schneller kann ich allerdings nicht sagen und hängt sicher auch von dem Umständen ab (Plattentechnik, Netzwerkzugriffe usw.). Also keine Ahnung, ob das an Abts Vorschlag herankommt. Aber die Codeumstellung ist sehr einfach und daher kannst du einfach mal einen Testlauf machen und schauen, was es in deinem konkreten Fall bringt (und die Zahlen vielleicht auch hier veröffentlichen, dann müssen wir nicht spekulieren).

herbivore

16.807 Beiträge seit 2008
vor 10 Jahren

Mal Anhaltspunkte:

  • ~~Directory.EnumerateFiles ist die schnellste Variante, wenn man reine Pfade haben will (unterstützt jedoch keine UNC Pfade - Pfadlänge bis 32767). Kann man so auch nicht toppen sage ich (aber das heisst nichts, wenn ich was sag 😉 )[/s].* DirectoryInfo.EnumerateFiles() ist in herbivore's agesprochenem Fall schneller, da jeder FileHandle nur ein mal angefasst wird; aber nur, wenn DirectoryInfo bereits existiert!. Ansonsten Directory.EnumerateFiles verwenden. Das Abrufen der Daten (und FileInfo macht nicht nur eine Anfrage) ist der Flaschenhals, nicht das Suchen und Anfassen des Handles.
  • Sobald FileInfo im Spiel ist (und der Enumerator komplett durchlaufen wird (zB beim Summieren der Bytes)), ist der direkte Angriff auf Win32 ca. 50% schneller ((meine Tests mit meinem aktuellen Code)
  • FileInfo.Length = Int32 und damit maximale Anzeigbare Größe je Datei = 2GB (einfacher Umweg: File.OpenRead(path).Length = Int64) (Win32 selbst liefert ein UInt64; das wiederum lässt sich via LINQ aber nicht summieren) [/list]
thetruedon Themenstarter:in
111 Beiträge seit 2011
vor 10 Jahren

@herbivore
DirectoryInfo.EnumerateFiles ist zwar vielleicht schneller aber in meinem Projekt so nicht Anwendbar.

Ich nutze Directory.EnumerateFiles um Dateien in den Verzeichnissen zu suchen diese öffne und Parse ich dann um Dateipfade die zuhauf in diesen Dateien stehen zu extrahieren.
Von diesen extrahierten Dateipfaden will ich ich dann die Dateigröße.

Hätte ich eher sagen müssen aber der Flaschenhals ist sowieso das öffnen der extrahierten Dateien und und nicht der Durchiterierten.

Das sind erstmal alles gute Anhaltspunkte ich werde mich dahinterklemmen damit ich eine Lösung posten kann.

Kommt ein Mann in die Wirtschaft und sagt zum Wirt: 16 Bit!
Sagt der Wirt: Das ist ein Wort!

16.807 Beiträge seit 2008
vor 10 Jahren

Dann wird

File.OpenRead(path).Length

für Dich die wohl performanteste Variante sein (abgesehen von Win32).

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo thetruedon,

eine wichtige Information, die du da "unterschlagen" hast. Diese Information im Blick, kommt es darauf an, wieviele von den ausgelesenen Dateienpfade im Schnitt den gleichen Beginn haben, also im gleichen Verzeichnis liegen und wieviele andere Dateien es im Schnitt in diesem Verzeichnis gibt. Je mehr Dateien es sind und je weniger andere Dateien es in diesem Verzeichnis gibt, desto sinnvoller ist es, alle FileInfos des Verzeichnisses per DirectoryInfo.EnumerateFiles/GetFiles auf einen Rutsch auszulesen statt für jede Datei einzeln die FileInfos zu ermitteln.

Wenn du z.B. folgende Dateipfade ausgelesen hast:

c:\dir1\subdir1\file1.txt
c:\dir1\subdir1\file2.txt
c:\dir1\subdir1\file3.txt
c:\dir1\subdir1\file4.txt

und es sonst nur noch die Datei file5.txt in c:\dir1\subdir1 gibt, dann könnte es durchaus Sinn machen alle FileInfos von c:\dir1\subdir1 per DirectoryInfo.EnumerateFiles auszulesen (und dann den einzelnen Dateienpfaden zuzuordnen).

herbivore

16.807 Beiträge seit 2008
vor 10 Jahren

Hallo herbivore,

kannst Du mal drauf eingehen, was das nutzen soll?
Ich sehe nur Nachteile in diesem Vorgehen (im Hinblick wie FindFirstFile / FindNextFile) und keine Vorteile. Überseh ich etwas?

DirectoryInfo.EnumerateFiles ruft durch sein * Verhalten auch System-Ordner auf (. / ..) oder potenzielle Unterordner (FindFirstFile("c:\dir1\subdir1\*", win32FindData). Dadurch erhöhst Du den Overhead, sodass Du gewisse Handles doppelt und dreifach aufrufst. Zudem fällt auch noch die Überprüfung der Attribute an, um Dateien von Ordnern unterscheiden zu können.

File.OpenRead(path).Length hingegen nutzt Win32 CreateFile (mit entsprechenden FileAccess GenericRead, ShareMode Read, FileOption OpenExisting) und liefert durch das Handle direkt einen FileStream, dessen Length Win32 GetFileSize nutzt; direkter geht es nicht.

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Abt,

DirectoryInfo.EnumerateFiles ruft durch sein * Verhalten auch System-Ordner auf (. / ..) oder potenzielle Unterordner

ich habe mit keinem Wort gesagt, dass man SearchOption.AllDirectories verwenden soll. Außerdem würde man bei meinem Vorschlag, für jedes Verzeichnis maximal einmal EnumerateFiles aufrufen.

kannst Du mal drauf eingehen, was das nutzen soll?

Ich hatte schon oben geschrieben, dass ich bewusst nur auf den direkten Unterschied von Directory.EnumerateFiles+new FileInfo und DirectoryInfo.EnumerateFiles eingehe (ohne damit die Win32-Alternativen in Frage zu stellen). Meinen Vorschlag habe ich jetzt lediglich an die veränderte Situation angepasst. Ich behaupte nicht, dass es insgesamt die schnellste Lösung ist.

File.OpenRead(path).Length hingegen nutzt Win32 CreateFile [...] direkter geht es nicht.

Ich könnte mir durchaus vorstellen, dass das Öffnen der Datei ein unnötiger Overhead ist, weil es (technisch) reichen würde, die Information aus dem Dicrectory(eintrag) auszulesen, ohne die Datei selbst anzufassen.

herbivore

16.807 Beiträge seit 2008
vor 10 Jahren

Zuerst einmal: ich lag doch falsch mit Directory.EnumerateFiles.
Ich hab nicht darauf geachtet, dass .NET hier einfach nur einen Enumerator erstellt, ohne diesen zu materialisieren.

Anbei mal ein Vergleich mit den .NET Implementierungen und mit meiner Umsetzung.
Gewisse Schwankungen sind nie völlig auszugleichen.