我在服务器上运行一个 C# 进程(服务),负责持续解析 HTML 页面,它依赖于 HTMLAgilityPack。症状是随着时间的推移,速度越来越慢。
当我启动这个进程时,它可以处理 n 个页面/秒。几个小时后,速度降至约为 n/2 个页面/秒。几天后,它甚至可能降到 n/10。这种现象已经被观察到很多次,并且相当确定。每次重启进程后,一切就恢复正常了。
非常重要的是:我可以在同一个进程中运行其他计算,它们不会变慢:我随时可以使用任何其他内容占用 100% 的 CPU。只有 HTML 解析变慢。
我可以用最少的代码复现这个问题(实际上,在原始服务中的行为更极端,但仍然能够复现此行为)。
使用这个简洁的代码,它将显示随着时间(自进程启动以来经过的小时数)变化的速度(每秒页面数):
这里是图片: 明显的原因都已排除:
- HTML页面更大或不同(在最小代码中是相同的页面) - 内存已满:进程在32 GB上使用约500 MB - 其他进程使用CPU或RAM
可能与内存和内存分配有关。我知道HTMLAgilityPack会进行许多小对象的内存分配(HTML节点和字符串)。很明显,内存分配和多线程并不协作良好。但我不明白为什么进程会变得越来越慢。
你是否了解CLR或Windows中有什么可能导致需要大量内存(许多分配)的处理变得越来越慢?例如惩罚执行某种内存分配的线程?
当我启动这个进程时,它可以处理 n 个页面/秒。几个小时后,速度降至约为 n/2 个页面/秒。几天后,它甚至可能降到 n/10。这种现象已经被观察到很多次,并且相当确定。每次重启进程后,一切就恢复正常了。
非常重要的是:我可以在同一个进程中运行其他计算,它们不会变慢:我随时可以使用任何其他内容占用 100% 的 CPU。只有 HTML 解析变慢。
我可以用最少的代码复现这个问题(实际上,在原始服务中的行为更极端,但仍然能够复现此行为)。
public static void Main(string[] args) {
string url = "https://en.wikipedia.org/wiki/History_of_Texas_A%26M_University";
string html = new HtmlWeb().Load(url).DocumentNode.OuterHtml;
while (true) {
//Processing
Stopwatch sw = new Stopwatch();
sw.Start();
Parallel.For(0, 10000, i => new HtmlDocument().LoadHtml(html));
sw.Stop();
//Logging
using(var writer = File.AppendText("c:\\parsing.log")) {
string text = DateTime.Now.ToString() + ";" + (int) sw.Elapsed.TotalSeconds;
writer.WriteLine(text);
Console.WriteLine(text);
}
}
}
使用这个简洁的代码,它将显示随着时间(自进程启动以来经过的小时数)变化的速度(每秒页面数):
这里是图片: 明显的原因都已排除:
- HTML页面更大或不同(在最小代码中是相同的页面) - 内存已满:进程在32 GB上使用约500 MB - 其他进程使用CPU或RAM
可能与内存和内存分配有关。我知道HTMLAgilityPack会进行许多小对象的内存分配(HTML节点和字符串)。很明显,内存分配和多线程并不协作良好。但我不明白为什么进程会变得越来越慢。
你是否了解CLR或Windows中有什么可能导致需要大量内存(许多分配)的处理变得越来越慢?例如惩罚执行某种内存分配的线程?