【发布时间】:2013-05-07 18:18:09
【问题描述】:
我正在努力针对大量 POCOs 集合改进 linq 过滤器的性能,但本地测试表明存在 CPU 瓶颈。
我最初尝试这样做是为了减少 SQL 服务器上的负载,方法是检索大型结果集并将其加载到单独处理服务器上的内存中,然后在 .Net 中过滤此结果集。
这里是演示代码:
public class CustomClass
{
public int Id { get; set; }
public int OtherId { get; set;}
public DateTime Date { get; set; }
}
public void DoStuff()
{
// approx 800,000 items
List<CustomClass> allItems = _repo.GetCustomClassItemsFromDatabase();
foreach (OtherCustomClass foo in _bar)
{
// original linq-to-entities query,
// get most recent Ids that apply to OtherId
List<CustomClass> filteredItems = (
from item in allItems
where item.OtherId == foo.OtherId && item.Date <= foo.Date
group item by item.Id into groupItems
select groupItems.OrderByDescending(i => i.Date).First()).ToList();
DoOtherStuff(filteredItems);
}
}
这会使我的 4 个内核在 1 分 30 秒内达到 100% CPU,这对于生产系统来说是不可行的。我在 VS2012 中运行了性能分析器,30% 的时间是 get 调用 item.OtherId。
我开始将 linq 重写为纯代码,看看是否可以提高速度,但到目前为止我还没有运气。这是纯代码重写:
private List<CustomClass> FilterCustomClassByIdAndDate(
List<CustomClass> items, int id, DateTime date)
{
var mostRecentCustomClass = new Dictionary<int, CustomClass>();
foreach (CustomClass item in items)
{
if (item.Id != id || item.Date > date) { continue; }
CustomClass mostRecent;
if (mostRecentCustomClass.TryGetValue(item.Id, out mostRecent) &&
mostRecent.Date >= item.Date)
{ continue; }
mostRecentCustomClass[item.Id] = item;
}
var filteredItems = new List<CustomClass>();
foreach (KeyValuePair<int, CustomClass> pair in mostRecentCustomClass)
{
filteredItems.Add(pair.Value);
}
return filteredItems;
}
这仍然达到 100% CPU 和 30% 的 item.OrderId 调用。过去有没有人遇到过类似的问题,或者对如何改进这个问题有一些想法?
编辑:代码显示出巨大的改进
感谢@FastAl,这段代码不到一秒就通过了_bar -> DoOtherStuff(filteredItems) 循环:
public void DoStuff()
{
// approx 800,000 items
List<CustomClass> allItems = _repo.GetCustomClassItemsFromDatabase();
var indexedItems = new Dictionary<int, List<CustomClass>>();
foreach (CustomClass item in allItems)
{
List<CustomClass> allByOtherId;
if (!indexedItems.TryGetValue(item.OtherId, out allByOtherId))
{
allByOtherId = new List<CustomClass>();
indexedItems[item.OtherId] = allByOtherId;
}
allByOtherId.Add(item);
}
foreach (OtherCustomClass foo in _bar)
{
List<CustomClass> filteredItems;
if (!indexedItems.ContainsKey(foo.OtherId))
{
filteredItems = new List<CustomClass>();
}
else
{
List<CustomClass> filteredItems = (
from item in indexedItems[foo.OtherId]
where item.Date <= foo.Date
group item by item.Id into groupItems
select groupItems.OrderByDescending(i => i.Date).First())
.ToList();
}
DoOtherStuff(filteredItems);
}
}
【问题讨论】:
-
您是否将 800,000 个项目加载到内存中?根据你的例子,你只有在桌子上,但这是真的吗?
-
我知道这不是您确切问题的答案,但有什么方法可以将其卸载到 SQL 服务器上的存储过程中?通过将有问题的 LINQ 移动到存储过程并创建适当的索引,我注意到了巨大的性能提升。
-
如果您使用数据库,为什么要将所有数据提取到列表中,然后过滤列表? linq 的全部意义在于简化在数据库中 的过滤。这里的主要问题似乎是列表的使用。
-
@Matt 记录一下,我们很少有“存储过程”的好处 - 现在存储过程和参数化 tsql 之间几乎没有任何明显的性能差异。常规的 tsql 查询可以很好地完成这项工作。
-
高效过滤是数据库的作用;利用这一点。我怀疑您是否通过使其提供所有 800,000 条记录而不是您真正感兴趣的少数记录来为数据库服务器带来任何好处。我的经验是 I/O 和网络操作比 CPU 使用率更频繁地造成数据库瓶颈.
标签: c# .net performance linq poco