c# - 为什么 List.Sum() 与 foreach 相比表现不佳?
问题描述
问题:为什么在以下场景中的执行时间Sum()
比a长得多?foreach()
public void TestMethod4()
{
List<int> numbers = new List<int>();
for (int i = 0; i < 1000000000; i++)
{
numbers.Add(i);
}
Stopwatch sw = Stopwatch.StartNew();
long totalCount = numbers.Sum(num => true ? 1 : 0); // simulating a dummy true condition
sw.Stop();
Console.WriteLine("Time taken Sum() : {0}ms", sw.Elapsed.TotalMilliseconds);
sw = Stopwatch.StartNew();
totalCount = 0;
foreach (var num in numbers)
{
totalCount += true ? 1 : 0; // simulating a dummy true condition
}
sw.Stop();
Console.WriteLine("Time taken foreach() : {0}ms", sw.Elapsed.TotalMilliseconds);
}
样品运行1
Time taken Sum() : 21443.8093ms
Time taken foreach() : 4251.9795ms
解决方案
TL;DR:时间差异是由 CLR 在第二种情况下应用两个单独的优化引起的,但不是第一种情况:
- Linq
Sum
在 上运行IEnumerable<T>
,而不是List<T>
。- CLR/JIT确实
foreach
对with进行了特殊情况优化List<T>
,但如果 aList<T>
作为 传递,则没有IEnumerable<T>
。- 这意味着它正在使用
IEnumerator<T>
并产生与此相关的所有虚拟呼叫的成本。 - 而
List<T>
直接使用使用静态调用(实例方法调用仍然是“静态”调用,只要它们不是虚拟的)。
- 这意味着它正在使用
- CLR/JIT确实
- Linq
Sum
接受委托Func<T,Int64>
。- 作为委托传递的函数
Func<T,Int64>
不是内联的,即使使用MethodImplOptions.AggressiveInline
. - 委托调用的成本略高于虚拟调用。
- 作为委托传递的函数
我已经SUM
使用您在此处访问的各种不同方法重新实现了您的程序:https ://gist.github.com/Jehoel/1a4fcd2e70374d3694c3a105061a6d1c
我的基准测试结果(发布版本、x64、.NET Core 5、i7-7700HQ):
方法 | 时间(毫秒) |
---|---|
Test_Sum_Delegate | 118毫秒 |
Test_MySum_DirectFunc_IEnum | 112毫秒 |
Test_MySum_IndirectFunc_IEnum | 114 毫秒 |
Test_MySum_DirectCall_IEnum | 89 毫秒 |
Test_MySum_DirectFunc_List | 58毫秒 |
Test_MySum_IndirectFunc_List | 58毫秒 |
Test_MySum_DirectCall_List | 37毫秒 |
Test_Sum_DelegateLambda | 109 毫秒 |
Test_For_Inline | 4毫秒 |
Test_For_Delegate | 3ms |
Test_ForUnrolled_Inline | 4毫秒 |
Test_ForUnrolled_Delegate | 4毫秒 |
Test_ForEach_Inline | 38ms |
Test_ForEach_Delegate | 37毫秒 |
我们可以通过一次更改一件事来隔离不同的行为(例如foreach
vs for
、IEnumerable<T>
vs List<T>
、Func<T>
vs 直接函数调用)。
System.Linq.Enumerable.Sum
方法(Test_Sum_Delegate
)与Test_MySum_IndirectFunc_IEnum
(忽略它们之间的差异4ms
)相同。这两种方法都遍历List<T>
using IEnumerable<T>
。
将方法更改为传递List<T>
aList<T>
而不是IEnumerable<T>
(in Test_MySum_IndirectFunc_List
) 可以消除使用虚拟调用,foreach
这IEnumerator<T>
会导致从~114ms
to58ms
减少,时间已经减少了 50%。
然后Func<Int64,Int64>
将对 func 的(委托)调用更改为对(如)GetValue
的“静态”调用将时间缩短到- 与. 这种方法与您的手写循环相同。GetValue
Test_MySum_DirectCall_List
37ms
Test_ForEach_Delegate
foreach
获得更快性能的唯一方法是使用for
没有任何虚拟调用的循环。(在调试版本中,Unrolled 循环甚至比正常for
循环更快,但在发布版本中没有观察到差异)。
推荐阅读
- reactjs - NextJS - 仅为几个特定页面添加提供程序
- asp.net - 如果存在匹配,则在 GridView A 和 B 和 GridView A 中查找匹配日期和自动勾选复选框 - VB.Net
- javascript - JS 等待执行然后放置
- ios - 在应用程序包中找不到图像的问题
- swift - SwiftUI Preview 不支持绑定
- reactjs - 如何在 Embedded React App 中的页面之间路由?
- api - Rest API延迟响应减慢系统
- r - 网页抓取表
- flutter - Flutter Firestore 分页(添加/删除项目)
- kubernetes - kubernetes:如何在内存限制阈值上重新启动 pod