C# 第一次从 SSD 驱动器顺序读取文本文件非常慢

Pat*_*eam 2 .net c# caching file-read

我在 Windows 会话期间第一次从 SSD 连续读取文本文件时遇到非常慢的情况。然后,第二次和连续读取速度快了 60 倍以上(在应用程序的第二次和后续运行中,这意味着不同的 Windows 进程)。我很确定这是系统和/或 SSD 缓存优化的正常结果,但我在这里提出一个问题,以便与社区仔细检查这个猜测,因为我没有通过谷歌搜索找到任何内容。

问:有什么办法可以让第一次阅读更快吗?


回答:

我更新了这个问题,因为感谢 @Alois Kraus 的回答,我们发现罪魁祸首是 Windows Defender 文件读取时的文件检查!

可以通过“开始”>“设置”>“更新和安全”>“Windows 安全”>“病毒和威胁防护”禁用此 Windows Defender 慢速检查。在“病毒和威胁防护设置”下,选择“管理设置”,然后在“排除项”下,选择“添加排除项”。我可以通过文件夹排除(它在文件夹下递归地工作)或文件类型来解决这个特殊问题,并将文件扩展名放在其上以禁用慢速检查。

现在剩下的问题是:

    1. 如何以编程方式检测 Windows Defender 正在减慢我们的代码,因此我们可以向用户发出警告并解释该怎么做
    1. 以编程方式禁用 Windows Defender 慢速检查(我确信这是不可能的,否则这将为病毒打开一扇门!)

Windows Defender 排除


细节:

我在两台不同的机器上重现了该问题,其中一台配备 SAMSUNG SSD MZVLB1T0HALR 1TB(最大读取速度 3200MB/秒),另一台配备 SAMSUNG SSD MZVL21T0HCLR 1TB(最大读取速度 7000MB/秒)。

通过经典File.ReadAllText(filePathStr)调用,我第一次得到 4.737 个文件的 33.555 秒(来自 dotTrace 的屏幕截图)。所有 4.7K 文本文件在磁盘上的累计重量为 28MB,这低于每秒 1MB 的读取速度。 在此输入图像描述

然后第二次和随后的时间快了 60 倍以上,540 毫秒而不是 33 秒,大约 60MB 读取/秒(距离宣布的 SSD 最大读取速度 3200MB/秒还很远,但我们读取了 4.7K 个文件,而不是仅仅一个)。 在此输入图像描述


我知道File.ReadAllText()有一些限制(比如假设文本文件是 UTF8 编码的,这在我们的上下文中是可以的)所以我尝试了类似的方法:

FileInfo fileInfo = new FileInfo(filePathStr);
long length = fileInfo.Length;
int bufferSize = length >= int.MaxValue ? int.MaxValue : (int)length;
using (FileStream fs = File.Open(filePathStr, FileMode.Open, FileAccess.Read))
using (StreamReader streamReader = new StreamReader(
          fs, Encoding.UTF8, true, bufferSize, false)) {
   content = streamReader.ReadToEnd();
   failureReason = null;
   return true;
}
Run Code Online (Sandbox Code Playgroud)

...第一次具有相同的超慢性能,第二次及后续时间性能较慢(853 毫秒而不是 540 毫秒):

在此输入图像描述


最后我用这段代码做了测试:

byte[] bytes = File.ReadAllBytes(filePathStr);
string content = File.ReadAllText(filePathStr);
Run Code Online (Sandbox Code Playgroud)

在第一次运行时,我测量了这一点,这表明这与文本读取无关,而仅与第一次文件读取有关:

在此输入图像描述


我对使用 Roslyn 读取的文件进行了更多测试SourceReferenceResolver.ReadText(string resolvedPath):SourceText,得到了完全相同的结果:读取 4.700 个源文件时速度慢 60 倍到 80 倍,具体取决于 Windows 启动后第一次执行还是第二次或后续执行(在强大的新机器上从 23 秒到 270 毫秒) !)。我想如果有一种方法可以实现更好的性能,Roslyn 开发人员会实施它,因为他们都是专家并且非常关心性能。


有些人建议异步,所以我尝试了类似的方法:

var tasks = new List<Task<string>>();
foreach (string path in paths) {
      var task = File.ReadAllTextAsync(path);
      tasks.Add(task);
}
foreach(var task in tasks) {
   string str = await task;
}
Run Code Online (Sandbox Code Playgroud)

但结果与使用相同的File.ReadAllText(path);:Wnd 会话中第一次相同的持续时间,Wnd 会话中第二次和后续时间相同的持续时间。

Alo*_*aus 6

您需要进行简介。但考虑到速度大幅下降,很可能是 AV 扫描仪的开销。AV 扫描仪倾向于拦截文件 IO 并扫描文件内容,这需要时间。为了证明您需要在提升的命令 shell 中启动

wpr start CPU -start DiskIO -start FileIO
Run your test application or execute the use case from your already running application
wpr -stop c:\temp\SlowFileRead.etl
Run Code Online (Sandbox Code Playgroud)

您可以再次对快速情况重复此操作,并相应地命名文件,例如FastSecondRead.etl

Wpr 是 Windows 的一部分,自 Win 11 起可靠运行。对于较旧的操作系统,您需要从 Windows SDK 安装 Windows Performance Toolkit ( https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/ Win 11 WPT 也适用于 Windows 10)。

然后你可以下载 ETWAnalyzer (我写的)https://github.com/Siemens-Healthineers/ETWAnalyzer/releases并执行

ETWAnalyzer -extract all -fd c:\temp\SlowFileRead.etl -symserver ms
cd c:\temp\Extract
ETWAnalyzer -dump CPU -pn YourAppName.exe -stacktags virus
Run Code Online (Sandbox Code Playgroud)

下面是一个示例图像: 在此输入图像描述

有关更多查询,请参阅 ETWAnalyzer 的帮助和此博客文章:

我有一个小型测试应用程序,它创建 5K 文件并写入它们,这也非常慢(有时),启用 ETW 分析以显示影响性能的因素:

同样,这绝对与 SSD 或文件系统缓存无关。要在 33 秒内读取 28 MB,您最终会得到 0.8 MB/s,这与任何 SSD 性能限制相差几个数量级。

当 ETWAnalyzer 显示测试过程中防御者等待时间较长时,您就发现了问题。如果没有,那么您需要转储方法并按等待时间排序

ETWAnalyzer -dump CPU -pn YourAppName.exe -methods *  -SortBy Wait -topNMethods 150
ETWAnalyzer -dump CPU -pn YourAppName.exe -methods *.sys;*.dll  -SortBy Wait -smi
Run Code Online (Sandbox Code Playgroud)

第二个查询将显示所有没有符号的设备驱动程序和 dll,所有 AV 扫描仪都不提供这些符号,这是快速识别其他供应商的新 AV 驱动程序的好方法。

-csv waitTimes.csv如果需要,您还可以通过添加将数据导出为 csv ,并在 Excel 中继续分析。我会检查 ReadFile/CreateFile/CloseHandle 等待时间和具有类似等待时间的方法,这些方法很可能是调用堆栈的一部分。然后你会发现有问题的拦截驱动程序或其他东西。如果您要检查特定的驱动程序版本,您可以添加这是快速在每行上显示模块版本的-smi快捷方式。-ShowModuleVersion

防病毒软件通常将大部分时间花在 CreateFile 上,因此如果您找到一个文件系统过滤器驱动程序以相似的时间拦截您的调用,我将首先开始查找那里。

要回答您可以做什么的问题:

如果您的应用程序运行提升,您可以添加排除规则。对于例如 Defender,您可以通过 powershell 界面添加排除规则:

Add-MpPreference -ExclusionPath "C:\temp\Test"
Run Code Online (Sandbox Code Playgroud)

您还可以要求用户为您要读取的数据文件添加 AV 排除项,以加快读取速度。无论如何,这是一件好事,因为如果 AV 扫描器运行在构建目录上,那么构建时间就会缩短,在该目录中,经常会弹出新的可执行文件并消失,这会吸引 AV 扫描器,就像沙漠中的死马吸引秃鹰一样。

更新你能做什么

一种可能性是使用已知数据集进行自我速度测试。例如,您可以在目标目录中创建一个文件夹,在其中填充 5000 个文件并测量需要多长时间。这不会测试磁盘速度,因为只要修改的字节低于特定阈值,写入操作就会由操作系统异步写入。如果您比预期慢得多,您可以发出警告。下一步是检查 C:\Windows\System32\drivers 中已安装的驱动程序。ETWAnalyzer 几乎了解其列表中的所有 AV 驱动程序:https://github.com/Siemens-Healthineers/ETWAnalyzer/blob/main/ETWAnalyzer/Configuration/WellKnownDrivers.json,您还可以使用它来精确定位用户,为其创建排除项视音频解决方案。问题是驱动因素被用在不同的产品中,公司倾向于聚合和合并,但驱动因素仍然存在。