.NET中遍历1000-30万条记录,哪种循环性能更优?
循环性能测试异常原因解析
我之前以为for循环比foreach和LINQ更快,于是写了个控制台程序测试——传入含空值的字符串列表,分别用for、foreach、Parallel.ForEach、Parallel.For遍历找出空值索引并追加到字符串里。结果计时器统计发现foreach速度远快于其他循环,就算把List换成数组结果也一样,想搞懂这是为什么。
测试代码
static void Main(string[] args) { List<string> value = new List<string>() { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", " ", "", "", "", "", "", "", " " }; string ColName = "EMPNO"; var timer = new Stopwatch(); timer.Start(); string a = BlankDataInColumn(value, ColName); timer.Stop(); TimeSpan timeTaken = timer.Elapsed; string foo = "Time taken: " + timeTaken.ToString(@"m\:ss\.fff"); Console.WriteLine(foo); var timer1 = new Stopwatch(); timer1.Start(); string b = BlankDataInColumnforeach(value, ColName); timer1.Stop(); TimeSpan timeTaken1 = timer1.Elapsed; string foo1 = "Time taken: " + timeTaken1.ToString(@"m\:ss\.fff"); Console.WriteLine(foo1); var timer12 = new Stopwatch(); timer12.Start(); string c = BlankDataInColumnforeachParallel(value, ColName); timer12.Stop(); TimeSpan timeTaken12 = timer12.Elapsed; string foo12 = "Time taken: " + timeTaken12.ToString(@"m\:ss\.fff"); Console.WriteLine(foo12); var timer123 = new Stopwatch(); timer123.Start(); string d = BlankDataInColumnforParallel(value, ColName); timer123.Stop(); TimeSpan timeTaken123 = timer123.Elapsed; string foo123 = "Time taken: " + timeTaken123.ToString(@"m\:ss\.fff"); Console.WriteLine(foo123); Console.ReadLine(); } public static string BlankDataInColumn(List<string> Column, string ColumnName) { bool isBlank = false; StringBuilder rowNumber = new StringBuilder(); for (int i = 0; i < Column.Count(); i++) { if (Column[i].HasNothing()) { rowNumber.Append($"{i + 1},"); isBlank = true; } } string BlankDataExist = isBlank ? $"The {ColumnName} have Blank Values in the following row number {rowNumber}" : null; return BlankDataExist; } public static string BlankDataInColumnforeach(List<string> Column, string ColumnName) { bool isBlank = false; StringBuilder rowNumber = new StringBuilder(); int i = 0; foreach (string col in Column) { i++; if (col.HasNothing()) { rowNumber.Append($"{i},"); isBlank = true; } } string BlankDataExist = isBlank ? $"The {ColumnName} have Blank Values in the following row number {rowNumber}" : null; return BlankDataExist; } public static string BlankDataInColumnforeachParallel(List<string> Column, string ColumnName) { bool isBlank = false; StringBuilder rowNumber = new StringBuilder(); int i = 0; Parallel.ForEach(Column, col => { i++; if (col.HasNothing()) { rowNumber.Append($"{i},"); isBlank = true; } }); string BlankDataExist = isBlank ? $"The {ColumnName} have Blank Values in the following row number {rowNumber}" : null; return BlankDataExist; } public static string BlankDataInColumnforParallel(List<string> Column, string ColumnName) { bool isBlank = false; StringBuilder rowNumber = new StringBuilder(); Parallel.For(0, Column.Count(), i => { if (Column[i].HasNothing()) { rowNumber.Append($"{i + 1},"); isBlank = true; } }); string BlankDataExist = isBlank ? $"The {ColumnName} have Blank Values in the following row number {rowNumber}" : null; return BlankDataExist; }
原因解析
1. for循环的额外开销
你的for循环条件里用了Column.Count(),这是LINQ的扩展方法,而非直接访问List<T>的Count属性。虽然LINQ对ICollection<T>做了优化,但每次循环调用这个方法都会多一层包装开销。如果改成i < Column.Count(直接访问属性),for循环的性能会和foreach基本持平。
2. 并行循环的线程开销
Parallel.ForEach和Parallel.For的优势是处理大规模、计算密集型任务,你的测试数据量仅不到100个元素,线程创建、上下文切换的开销远远超过并行执行带来的收益。另外:
StringBuilder不是线程安全的,并行场景下调用Append会引发线程竞争,额外增加同步开销,甚至可能生成错误的结果;BlankDataInColumnforeachParallel里的i++是线程不安全操作,多个线程同时修改会导致计数错误,这也是并行版本的隐性问题。
3. foreach的底层优化
.NET对foreach遍历List<T>做了深度优化:编译后会直接获取List的内部数组和长度,避免了IEnumerator的装箱操作(针对值类型枚举器),执行效率几乎和直接遍历数组的for循环一致。你的foreach实现没有多余开销,自然表现最优。
内容的提问来源于stack exchange,提问作者silver spark
相关产品推荐
相关产品推荐

