C# FileInfo.Length ist zu langsam!

roker002

Commander
Registriert
Dez. 2007
Beiträge
2.103
Ich habe ein Problem. Eine meiner API soll so gegen 100k Dateien auswerten. Ich brauche die namen und die Größe der Dateien.

Code:
foreach(FileInfo fi in DirectoryInfo(path).GetFiles())
{
    ...
}

fi.Name ist einfach einzulesen. Für rund 95.000 Dateien braucht der 1 Sekunde. Wenn ich aber fi.Length versuche zuzugreifen, braucht der für die gleiche Menge 20 Sekunden.

Ich habe herumgesucht und habe eine Win32 Api gefunden. Sieh auf der MS Seite!

Code:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GetFileSizeEx(IntPtr hFile, ref long lpFileSize);

Dann führe ich

Code:
IntPtr ptr = System.Runtime.InteropServices.Marshal.StringToHGlobalAuto(fi.FullName);
var handle = new Microsoft.Win32.SafeHandles.SafeFileHandle(ptr, true);
var res = FileSystem.GetFileSizeEx(ptr, ref mylong); //oder halt mit dem Handler

Ich kann den verdamten Wert nicht rausbekommen!

Kann jemand vielleicht sagen was ich falsch mache? Wie gesagt, FileInfo.Length ist extrem langsam!
 
Zuletzt bearbeitet:
Hallo!

Versuche doch mal die Methode GetFiles aus dem foreach herauszunehmen...

Code:
FileInfo[] fiArray = DirectoryInfo(path).GetFiles();
foreach(FileInfo fi in fiArray)
{
    ...
}

Das könnte möglicherweise schon helfen.

lG,
Thomas
 
@DarkEye
sorry aber es hat nichts damit zutun, wenn du eine extra variable deklariest oder direkt übergibst. Es wird direkt auf dem Rückgabewert itteriert.

Wenn ich die schleife laufen lasse und nur den Dateinamen mitnehme, braucht das ganze nur 1 Sekunde. Sobald ich die Dateigröße abfrage läuft dann auch alles viel langsammer.

Wie gesagt, ich habe getestet und es liegt nur an dem FileInfo.Length.
Das ganze zu parallelisieren bringt auch wenig was. Ich denke FileInfo.Length versucht von direkt den start und endsektor zu ermitteln und daraus die länge zu berechnen (wieso den sonst so langsam).

@stevke
GetFileSizeEx ist ein externer API! Es gehört nicht zu der Standardbibliothek!
 
@DarkEye
sorry aber es hat nichts damit zutun, wenn du eine extra variable deklariest oder direkt übergibst. Es wird direkt auf dem Rückgabewert itteriert.

Im Framework 1.1 gab das damals einen gewaltigen Geschwindigkeitsschub. Kann schon sein, dass (der Fehler) jetzt schon behoben ist.

Anyway... hier solltest du alles finden, was du zu GetFileSizeEx brauchst:
http://www.pinvoke.net/default.aspx/kernel32.GetFileSizeEx

Die API für CreateFile findest du auch auf der Seite.
http://www.pinvoke.net/default.aspx/kernel32.CreateFile


lG,
Thomas
 
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace GetFileSizeEx
{
  class Program
  {
    [DllImport( "kernel32.dll" )]
    static extern bool GetFileSizeEx( IntPtr hFile, out long lpFileSize );

    [DllImport( "kernel32.dll", SetLastError = true, CharSet = CharSet.Auto )]
    public static extern IntPtr CreateFile(
       string lpFileName,
       EFileAccess dwDesiredAccess,
       EFileShare dwShareMode,
       IntPtr lpSecurityAttributes,
       ECreationDisposition dwCreationDisposition,
       EFileAttributes dwFlagsAndAttributes,
       IntPtr hTemplateFile );

    [Flags]
    public enum EFileAccess : uint
    {
      /// <summary>
      ///
      /// </summary>
      GenericRead = 0x80000000,
      /// <summary>
      ///
      /// </summary>
      GenericWrite = 0x40000000,
      /// <summary>
      ///
      /// </summary>
      GenericExecute = 0x20000000,
      /// <summary>
      ///
      /// </summary>
      GenericAll = 0x10000000
    }

    [Flags]
    public enum EFileShare : uint
    {
      /// <summary>
      ///
      /// </summary>
      None = 0x00000000,
      /// <summary>
      /// Enables subsequent open operations on an object to request read access.
      /// Otherwise, other processes cannot open the object if they request read access.
      /// If this flag is not specified, but the object has been opened for read access, the function fails.
      /// </summary>
      Read = 0x00000001,
      /// <summary>
      /// Enables subsequent open operations on an object to request write access.
      /// Otherwise, other processes cannot open the object if they request write access.
      /// If this flag is not specified, but the object has been opened for write access, the function fails.
      /// </summary>
      Write = 0x00000002,
      /// <summary>
      /// Enables subsequent open operations on an object to request delete access.
      /// Otherwise, other processes cannot open the object if they request delete access.
      /// If this flag is not specified, but the object has been opened for delete access, the function fails.
      /// </summary>
      Delete = 0x00000004
    }

    public enum ECreationDisposition : uint
    {
      /// <summary>
      /// Creates a new file. The function fails if a specified file exists.
      /// </summary>
      New = 1,
      /// <summary>
      /// Creates a new file, always.
      /// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes,
      /// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies.
      /// </summary>
      CreateAlways = 2,
      /// <summary>
      /// Opens a file. The function fails if the file does not exist.
      /// </summary>
      OpenExisting = 3,
      /// <summary>
      /// Opens a file, always.
      /// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW.
      /// </summary>
      OpenAlways = 4,
      /// <summary>
      /// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist.
      /// The calling process must open the file with the GENERIC_WRITE access right.
      /// </summary>
      TruncateExisting = 5
    }

    [Flags]
    public enum EFileAttributes : uint
    {
      Readonly = 0x00000001,
      Hidden = 0x00000002,
      System = 0x00000004,
      Directory = 0x00000010,
      Archive = 0x00000020,
      Device = 0x00000040,
      Normal = 0x00000080,
      Temporary = 0x00000100,
      SparseFile = 0x00000200,
      ReparsePoint = 0x00000400,
      Compressed = 0x00000800,
      Offline = 0x00001000,
      NotContentIndexed = 0x00002000,
      Encrypted = 0x00004000,
      Write_Through = 0x80000000,
      Overlapped = 0x40000000,
      NoBuffering = 0x20000000,
      RandomAccess = 0x10000000,
      SequentialScan = 0x08000000,
      DeleteOnClose = 0x04000000,
      BackupSemantics = 0x02000000,
      PosixSemantics = 0x01000000,
      OpenReparsePoint = 0x00200000,
      OpenNoRecall = 0x00100000,
      FirstPipeInstance = 0x00080000
    }

    static void Main( string[] args )
    {
      long l = 0;
      IntPtr f = CreateFile( @"W:\Movies\Pearl Harbor.mkv",
        EFileAccess.GenericRead,
        EFileShare.Read,
        IntPtr.Zero,
        ECreationDisposition.OpenExisting,
        EFileAttributes.Readonly,
        IntPtr.Zero );
      if( GetFileSizeEx( f, out l ) )
      {
        Console.WriteLine( l.ToString() + " byte" );
        Console.WriteLine( (l / 1024.0).ToString() + " kb" );
        Console.WriteLine( (l / 1024.0 / 1024.0).ToString() + " mb" );
        Console.WriteLine( (l / 1024.0 / 1024.0 / 1024.0).ToString() + " gb" );
        Console.WriteLine( (l / 1024.0 / 1024.0 / 1024.0 / 1024.0).ToString() + " tb" );
      }
      else Console.WriteLine( "Error" );
      Console.ReadLine();
    }
  }
}
Funktioniert wunderbar!

http://pinvoke.net/default.aspx/kernel32/GetFileSizeEx.html
http://pinvoke.net/default.aspx/kernel32/CreateFile.html
 
roker002 schrieb:
fi.Name ist einfach einzulesen. Für rund 95.000 Dateien braucht der 1 Sekunde. Wenn ich aber fi.Length versuche zuzugreifen, braucht der für die gleiche Menge 20 Sekunden.

Das kannst du mit managed Code nicht groß beschleunigen, da FileInfo.Length intern noch einmal FindFirstFile() nur für diese Datei aufruft, um die Länge (nebst anderen Informationen) von Windows zu erfragen (grade mal im .NET reference quellcode nachgeschaut).

Du kannst aber die von dir erwähnte Win32-API GetFileSizeEx auch aufrufen, ohne CreateFile auch noch nutzen zu müssen:
Einfach nen FileStream mit dem Dateinamen kreieren und dann dessen Handle nehmen: fs.SafeFileHandle.DangerousGetHandle() - das liefert genauso ein Filehandle zurück wie CreateFile.

Nach dem Länge Auslesen aber das Schließen nicht vergessen ;-)
 
@Yuuri und @schlzber

Danke für die Antwort! Ja mit dem Beispiel von schlzber gehts, wenn man den SafeHandler vom Stream liest. Das Problem ist wieder die Performance. Man muss extrem viele Dateien aufmachen. Es ist sogar langsamer als mit FileInfo.Length.

Wie kann man sonst noch den SafeHandler oder IntPtr bekommen? Ich habe versucht den Pfad in den IntPtr umzuwandeln aber es hat nicht funtkioniert!
 
Hmm.. habe was sehr interesantes gefunden!

Hier ist ein Link für die Klasse mit nativen Methoden von Win. 83k Dateien in 5 Sekunden geknackt :D

hm, mit .NET 4.0 kann man viel schneller DirectoryInfo ausführen. Es liegt unter 1.5 Sekunden.

EDIT

FindFirst ist effektiver als .NET 3.5 FileInfo.Length, aber dennoch viel zu langsam.

EDIT 2

GetFileAttributesEx ist extrem Langsam. Der war nicht mal nach 2 minuten mit der Hälfte der Daten fertig! Eventuell macht der den FileStream auf um den Handler zu bekommen.

Edit 3

Es hat sich herausgestellt, das die .NET 4.0 doch um einiges schneller geworden ist. Ich habe paar Änderungen gelesen. Die haben wirklich was am Datei System Paket geschraubt.

Gibt es eine Möglichkeit einen .NET 4.0 Projekt für MS SQL aufzuspielen?



(Text geändert, tja war wohl gestern zuuuu müde)
 
Zuletzt bearbeitet:
Boah rocker.

Das kann man sich ja nich antuen was du hier schreibst.
Rechtschreibtechnisch und Ausdruck ne glatte 6.

Sammle dich bitte nochmal und formuliere deine Frage mal ordentlich.
 
@toeffi
sorry aber wenn ich keine Zeit habe zu RS zu überprüfen passiert mir sowas oft!

Also, kann man .NET 4.0 Projekt irgendwie für SQL 2008 "aufspielen" (deploy)?
Ich habe im Internet gesucht aber es steht überall, "jaja .NET 4.0 gibt es für CLR". Wieso kann ich dann die Assembly nicht einbinden?

Ah es steht was interesantes im Link.
Tja, für MS SQL 2008 kann man keine .NET 4.0 Projekte aufspielen! so ein Mist!
 
Zurück
Oben