【问题标题】:Faster way to get multiple FileInfo's?获取多个 FileInfo 的更快方法?
【发布时间】:2010-12-04 09:13:33
【问题描述】:

这是一个长镜头,但有没有更快的方法来获取多个文件的大小、上次访问时间、上次创建时间等?

我有一个很长的文件路径列表(所以我不需要枚举)并且需要尽快查找这些信息。并行创建 FileInfo 可能无济于事,因为瓶颈应该是磁盘。

不幸的是,NTFS 日志只保留文件名,否则那太好了,我猜操作系统不会在某处存储元信息?

如果存在静态或 Win32 调用(但文件方法只允许我一次获取一条信息)方法,则可能会进行其他优化,该方法获取信息而不是创建一堆 FileInfo 对象

无论如何,很高兴有人知道可能有帮助的东西,不幸的是我必须在这里进行微优化,并且没有“使用数据库”不是一个可行的答案;)

【问题讨论】:

  • 查看通过 NuGet 提供的 FluentPath。 weblogs.asp.net/bleroy/archive/2010/11/19/fluentpath-1-0.aspx
  • 据我了解,它是一个更好的库,用于处理文件路径和 linq 样式操作,并且与引擎盖下的文件元数据没有太大关系
  • 如果没有,您是否可以在应用程序生命周期开始时缓存信息?这将使您可以在 RAM 中使用信息,但如果您有一个长时间运行的应用程序,则不会是最新的..

标签: c# file-io winapi


【解决方案1】:

System.IO.File 上有静态方法可以得到你想要的。这是一个微优化,但它可能是您需要的:GetLastAccessTimeGetCreationTime

编辑

我将留下上面的文字,因为您特别要求静态方法。但是,我认为你最好使用FileInfo(你应该测量一下以确保)。 File 和 FileInfo 都在 File 上使用一个名为 FillAttributeInfo 的内部方法来获取您需要的数据。对于您需要的属性,FileInfo 将需要调用此方法一次。 File 必须在每次调用时调用它,因为属性信息对象在方法完成时被丢弃(因为它是静态的)。

所以我的直觉是,当您需要多个属性时,每个文件的FileInfo 会更快。但是在性能情况下,您应该始终测量!面对这个问题,我会尝试上述两种托管选项,并在串行和并行运行时进行基准测试。然后决定它是否足够快。

如果速度不够快,则需要直接调用 Win32 API。在参考资料中查看File.FileAttributeInfo 并想出类似的东西并不难。

第二次编辑

事实上,如果你真的需要它,这是直接调用 Win32 API 所需的代码,使用与File 的内部代码相同的方法,但使用一个操作系统调用来获取所有属性。我认为您应该仅在确实需要时使用。您必须自己将 FILETIME 解析为可用的日期时间,等等,因此您需要手动完成更多工作。

static class FastFile
{
    private const int MAX_PATH = 260;
    private const int MAX_ALTERNATE = 14;

    public static WIN32_FIND_DATA GetFileData(string fileName)
    {
        WIN32_FIND_DATA data;
        IntPtr handle = FindFirstFile(fileName, out data);
        if (handle == IntPtr.Zero)
            throw new IOException("FindFirstFile failed");
        FindClose(handle);
        return data;
    }

    [DllImport("kernel32")]
    private static extern IntPtr FindFirstFile(string fileName, out WIN32_FIND_DATA data);

    [DllImport("kernel32")]
    private static extern bool FindClose(IntPtr hFindFile);


    [StructLayout(LayoutKind.Sequential)]
    public struct FILETIME
    {
        public uint dwLowDateTime;
        public uint dwHighDateTime;
    }
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct WIN32_FIND_DATA
    {
        public FileAttributes dwFileAttributes;
        public FILETIME ftCreationTime;
        public FILETIME ftLastAccessTime;
        public FILETIME ftLastWriteTime;
        public int nFileSizeHigh;
        public int nFileSizeLow;
        public int dwReserved0;
        public int dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ALTERNATE)]
        public string cAlternate;
    }
}

【讨论】:

  • 是的,但是我必须做 4 个单独的调用,如果它必须为每个调用做磁盘 io,那应该不会那么好。我只是在猜测,FileInfo 可能只是这样做......试图看看源代码
  • @MattiasK,您假设需要磁盘 io。操作系统很可能已经在内存中缓存了大量元数据,这使得它可以响应您的请求而不会碰到磁盘。您说过您确实必须进行微优化,您是否通过实际确定不优化需要多长时间来验证这一点? =)
  • 实际上我在 ClearCase 动态视图(网络驱动器)上使用了 GetLastWriteTime,它非常慢。似乎 GetLastWriteTime 调用 Win32 CreateFile 来检索句柄以获取文件信息,这也导致病毒扫描程序启动。但是,通过使用 FindFirstFile ClearCase 仅返回缓存的文件信息,这快了 10 倍。也感谢您提供可复制粘贴的代码!
  • FWIW,FindFirstFileExFIND_FIRST_EX_LARGE_FETCH 对于 UNC 上的 1000 多个文件,比 Directory.EnumerateFiles + GetLastWriteTimeUtc + GetShortPathName 快 628.54 倍。 EnumerateFiles: 4,085.535 ms,err 33.3309 ms,stdev 31.1777 ms FindFirstFileEx: 13.761 ms,err 0.1786 ms,stdev 0.1670 ms FindFirstFileEx (FIND_FIRST_EX_LARGE_FETCH): 6.500 毫秒,错误 0.1274 毫秒,标准差 0.1467 毫秒
【解决方案2】:

.NET 的 DirectoryInfo 和 FileInfo 类在这方面速度非常慢,尤其是与网络共享一起使用时。

如果要“扫描”的许多文件位于同一目录中,则使用 Win32 API 的 FindFirstFile、FindNextFile 和 FindClose 函数可以获得更快的结果(取决于具体情况:按维度更快)。如果您必须询问您实际需要的更多信息(例如,如果您询问目录中的所有“.log”文件,而您只需要其中的 75%),这甚至是正确的。

实际上,.NET 的 info 类也在内部使用这些 Win32 API 函数。但他们只“记住”文件名。当询问有关一堆文件(例如 LastModified)的更多信息时,会为每个文件发出一个单独的(网络)请求,这会占用时间。

【讨论】:

  • 这很有趣,想知道在本地文件目录中使用 findfirstfile/findnextfile 读取顺序文件是否有任何优化。知道操作系统是否缓存这样的元数据也会很有趣
  • 在 4.0 中使用 DirectoryInfo.EnumerateFiles/Directories() 修复
【解决方案3】:

是否可以使用 DirectoryInfo 类?

 DirectoryInfo d = new DirectoryInfo(@"c:\\Temp");
 FileInfo[] f= d.GetFiles()

【讨论】:

  • 我已经有了文件路径,再次枚举它们似乎是一种浪费,我怀疑这种方法比简单地创建文件信息更快
  • 比我想的,你需要去本地 win api 调用,或者更好的是你可以在 .net 的未来版本中请求这个功能?
  • 谢谢。似乎比为每个文件单独执行要快。
【解决方案4】:

我认为您正在寻找 GetFileAttributesEx 函数 (pinvoke.net link)。但是,FileInfo 类(或者更确切地说,它的基类)无论如何都会在内部使用它,所以我怀疑你会看到任何性能改进。

【讨论】:

    【解决方案5】:

    如果文件系统是远程的,那么并行性可能会有所帮助,因为网络可能是瓶颈。

    此测试用例显示,使用 8 个线程处理 50k 个文件时改进了约 5 倍(52 秒 => 11 秒)。避免 lock() 也很重要,因为调用它 50k 会产生很大的影响。计时是在不运行调试器的情况下进行的。

    这也说明了在访问 FileInfo.Length 之前,不会执行获取文件长度的工作。并行段后再次访问 Length 是瞬时的。这可能有点过于依赖实现了。

    // ~4s
    //
    List<string> files = Directory.EnumerateFileSystemEntries(directory, "*", SearchOption.AllDirectories)
        .ToList();
    
    // ~0s
    // 
    Dictionary<string, FileInfo> fileMap = files.Select(file => new
    {
        file,
        info = new FileInfo(file)
    })
    .ToDictionary(f => f.file, f => f.info);
    
    // ~10s
    //
    Int64 totalSize = fileMap.Where(kv => kv.Value != null)
        .AsParallel() // ~50s w/o this 
        .Select(kv =>
        {
            try
            {
                return kv.Value.Length;
            }
            catch (FileNotFoundException)  // a transient file or directory
            {
            }
            catch (UnauthorizedAccessException)
            {
            }
            return 0;
        })
        .Sum();
    

    【讨论】:

      猜你喜欢
      • 2018-09-08
      • 2017-05-23
      • 1970-01-01
      • 2011-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多