C#中foreach循环的性能优化
本文关键字:性能 优化 循环 foreach | 更新日期: 2023-09-27 17:59:57
我有一个方法:
IList<string> pages = new List<string>();
foreach (var node in nodes)
{
try
{
string temp = DoSomeComplicatedModificationOnNode(node);
if (temp.ToLower().Contains(path))
{
pages.Add(node.Title);
}
}
catch (Exception)
{
continue;
}
}
DoSomeComplicatedModificationOnNode()在某些情况下会出现异常,这就是使用try{}catch块的原因-我可以跳过出现异常的项。节点的数量包含数千个项,一个项具有多个属性。如何优化此循环?我在考虑Parallel.Foreach,但下面的代码给了我一个错误"缺少当前主体":
IList<string> pages = new List<string>();
Parallel.ForEach(pageNodes, node =>
{
try
{
string temp = DoSomeComplicatedModificationOnNode(node);
if (temp.ToLower().Contains(path))
{
pages.Add(node.Title);
}
}
catch (Exception)
{
}
});
在C#中,泛型列表不是线程安全的,因此不能在并行循环中添加项。
我建议使用另一个类,如ConcurrentBag、ConcurrentStack或ConcurrentQueue。
var pages = new ConcurrentBag<string>();
Parallel.ForEach(pageNodes, node =>
{
try
{
string temp = DoSomeComplicatedModificationOnNode(node);
if (temp.ToLower().Contains(path))
pages.Add(node.Title);
}
catch (Exception)
{
throw;
}
});
记住,并行任务是无序的,如果你想要一个顺序,你必须在并行中使用索引。列表仅供阅读。
System.Threading.Tasks.Parallel.For(0, pageNodes.Count, index =>
{
string node = pageNodes[index];
try
{
string temp = DoSomeComplicatedModificationOnNode(node);
if (temp.ToLower().Contains(path))
pages.Add(MyPage(index, node.Title));
}
catch (Exception)
{
throw;
}
});
我建议将PLINQ用于此类目的。并行LINQ是LINQ的并行实现,具有相同的操作集。使用PLINQ编写的代码遵循函数式规则——并没有任何更新,只是在并行模式下映射当前列表。它可以通过在不同的线程中运行映射器,然后在一个"数据集"中收集结果,来提高您的案例的性能。当然,只有在CPU内核很少的情况下,它才能提高性能(但现在我们都很少内核)。
以下是的示例
private static void Main(string[] args)
{
var result =
GenerateList()
.AsParallel()
.Select(MapToString)
.Where(x => !String.IsNullOrWhiteSpace(x))
.ToList();
Console.ReadKey();
}
private const string Path = "1";
private static string MapToString( int node)
{
//Console.WriteLine("Thread id: {0}", Thread.CurrentThread.ManagedThreadId);
try
{
string temp = DoSomeComplicatedModificationOnNode(node);
if (temp.ToLower().Contains(Path))
{
return temp;
}
}
catch (Exception)
{
return null;
}
return null;
}
private static IEnumerable<int> GenerateList()
{
for (var i=0; i <= 10000; i++)
yield return i;
}
private static string DoSomeComplicatedModificationOnNode(int node)
{
return node.ToString(CultureInfo.InvariantCulture);
}
List<T>在大多数情况下不是线程安全的。看看线程安全集合,例如ConcurrentBag<T>。
真正的性能问题是您正在捕获异常,只需尝试使用变量通知结果。
对于长时间运行的方法,应该使用异步(asyc/await)方法。
小心并行操作(在LINQ用户AsParallel()中),因为你的资源有限,而且你可能在最意想不到的时候没有内存。你的代码必须是线程安全的(List不是线程安全的)。
我敢打赌,你不会有比以下代码更好的性能:
var pages = nodes.Select(x => new { Status = DoSomeComplicatedModificationOnNode(x), Node = x })
.Select(x => x?.Result)
.Where(x => x.Status.IsCorrect && x.Status.ToLowerInvariant().Contains(path))
.Select(x => x.Node.Title)
.ToList();
或者使用异步+LINQ:
var pages = nodes.Select(async x =>
{
return new { Status = await DoSomeComplicatedModificationOnNode(x), Node = x };
})
.Select(x => x?.Result)
.Where(x => x.Status.IsCorrect && x.Status.ToLowerInvariant().Contains(path))
.Select(x => x.Node.Title)
.ToList();
在调用ToList
之前,不会执行整个查询。
不,使用多个线程可能不会使循环更快。您的代码中有三个明显的错误,使其运行速度变慢。
pages
列表将被多次重新分配- 对
ToLower()
的调用会在每次运行时创建一个临时字符串 DoSomeComplicatedModificationOnNode
可能会抛出
请参阅下面的固定版本。
// Give the list an initial capacity (best guess), avoiding re-allocations.
var pages = new List<string>(nodes.Length);
foreach (var node in nodes)
{
string temp = DoSomeComplicatedModificationOnNode(node, out var error);
if (error != null)
{
continue;
}
// IndexOf() allows for allocation-free case insensitive string search.
if (temp.IndexOf(path, StringComparison.CurrentCultureIgnoreCase) >= 0)
{
pages.Add(node.Title);
}
}