先說結論
linq和直接遍歷的效能差異點主要還是迭代方式,陣列和字串這類的foreach都是專門有最佳化的,而linq都是通用的泛型呼叫迭代器,如果剛好遇到這類資料又需要高效能就儘量不用linq,其他型別或效能要求不高的還是linq香。(ps:linq寫的不好不能怪linq)
背景
起初是看到有人寫的部落格說***不推薦使用Linq!( https://mp.weixin.qq.com/s/FuF1Wp2eMxbamY5-uMErVQ ),還被多個wx訂閱號轉載,本能地覺得離譜呀!
what?!
unbelievable!
自測
仔細檢視,發現這程式碼寫的有問題呀,然後自己重寫了一版
部分程式碼
[Benchmark]
public void SumWithLinq()
{
int sum = _row.Sum(c => c == ',' ? 0 : c-'0');
}
[Benchmark(Baseline = true)]
public void SumWithLoop()
{
int sum = 0;
for (int i = 0; i < _row.Length; i++)
{
var c= _row[i];
if (c!= ',')
sum += c-'0';
}
}
測試結果
分析
雖然比那個博主的資料好了很多,但是linq確實比直接使用迴圈慢得多。檢視Sum的內部實現,意料之中是用的迭代器,於是繼續新增測試
再測
新增foreach測試程式碼
[Benchmark]
public void SumWithForeach()
{
int sum = 0;
foreach (var c in _row)
{
if (c != ',')
checked
{
sum += int.CreateChecked(c-'0');
}
}
}
測試結果
分析
foreach和for居然沒有什麼區別,那差距是在哪裡呢?突然意識到sum的呼叫方是IEnumerable
再測
新增測試程式碼
[Benchmark]
public void SumWithEnumerable()
{
int sum = 0;
foreach (var c in _row as IEnumerable<char>)
{
if (c != ',')
checked
{
sum += int.CreateChecked(c-'0');
}
}
}
測試結果
分析
直接對string做foreach和將string轉為可迭代物件再foreach居然差這麼多,string的foreach底層實現可能有最佳化
檢視ILSpy
直接foreach:
呼叫迭代器:
深入瞭解foreach
參閱文件:.NET 本質論 - 瞭解 C# foreach 的內部工作原理和使用 yield 的自定義迭代器
https://learn.microsoft.com/zh-cn/archive/msdn-magazine/2017/april/essential-net-understanding-csharp-foreach-internals-and-custom-iterators-with-yield
附完整程式碼
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
internal class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<LinqTest>();
}
}
[MemoryDiagnoser, MemoryRandomization]
public class LinqTest
{
private static readonly string _row = "1,2,3,4,5,6,7,8,9,10";
[Benchmark]
public void SumWithLinq()
{
int sum = _row.Sum(c => c == ',' ? 0 : c-'0');
}
[Benchmark]
public void SumWithEnumerable()
{
int sum = 0;
foreach (var c in _row as IEnumerable<char>)
{
if (c != ',')
checked
{
sum += int.CreateChecked(c-'0');
}
}
}
[Benchmark]
public void SumWithForeach()
{
int sum = 0;
foreach (var c in _row)
{
if (c != ',')
checked
{
sum += int.CreateChecked(c-'0');
}
}
}
[Benchmark(Baseline = true)]
public void SumWithLoop()
{
int sum = 0;
for (int i = 0; i < _row.Length; i++)
{
var c= _row[i];
if (c!= ',')
sum += c-'0';
}
}
}