C#锯齿数组迭代速度存在差异的原因是什么?
问题描述
我编写了以下用于测量执行速度的测试代码:
using System.Diagnostics; namespace Jagged_array_test { internal class Program { static void Main(string[] args) { Console.WriteLine("Jagged array test."); var width = 1024; var height = 512; // create a 2D jagged array using "Array" of "Array"-s and fill in with data var jagged = Array.CreateInstance(typeof(Array), height); for (var i = 0; i < jagged.GetLength(0); i++) { var row = Array.CreateInstance(typeof(byte), width); for (var j = 0; j < row.Length; j++) row.SetValue((byte)0xFF, j); jagged.SetValue(row, i); } // flatten the jagged array to an 1D byte array using 2 slightly different methods var data = new byte[width * height]; var N = 10; // number of tests var c = 0; var sw = Stopwatch.StartNew(); // Method 1 var m1 = new List<double>(); for (var i = 0; i < N; i++) { c = 0; sw.Restart(); foreach (Array row in jagged) foreach (byte col in row) data[c++] = col; sw.Stop(); m1.Add(sw.Elapsed.TotalMilliseconds); } Console.WriteLine($"Method 1: {m1.Average()} ms"); // Method 2 var m2 = new List<double>(); for (var i = 0; i < N; i++) { c = 0; sw.Restart(); foreach (var row in (Array[])jagged) foreach (var col in (byte[])row) data[c++] = col; sw.Stop(); m2.Add(sw.Elapsed.TotalMilliseconds); } Console.WriteLine($"Method 2: {m2.Average()} ms"); Console.WriteLine($"Difference: {(m1.Average() / m2.Average()):F3}x"); } } }
测试发现,Method 2的锯齿数组迭代速度比Method 1快至少10倍,请问背后的原因是什么?
原因解析
核心差异在于非泛型集合的类型转换/拆箱开销,具体分两层来看:
外层遍历的类型处理
- Method 1中,
jagged是通过Array.CreateInstance(typeof(Array), height)创建的非泛型Array实例,它的元素以object类型存储(所有非泛型数组的元素默认都是object)。用foreach (Array row in jagged)遍历的时候,每次迭代都要把object类型的元素强制转换为Array——虽然是引用类型转换,但相比直接遍历强类型数组,仍有额外的类型检查和转换开销。 - Method 2中,先把
jagged强制转换为Array[]强类型数组,后续遍历foreach (var row in (Array[])jagged)时,直接从强类型数组中取出Array元素,不需要每次迭代都做类型转换,开销大幅降低。
- Method 1中,
内层遍历的拆箱开销
- Method 1的内层遍历
foreach (byte col in row)中,row是Array非泛型类型,元素同样以object存储。每次取出元素时,都需要把object实例拆箱为byte值类型——拆箱需要从托管堆提取值并做类型检查,这是非常昂贵的操作,尤其当遍历元素数量极大(这里是1024*512=524288个元素)时,累积开销会非常显著。 - Method 2的内层遍历
foreach (var col in (byte[])row)中,row被强制转换为byte[]强类型值数组,遍历强类型值数组时,CLR可以直接从数组内存中读取byte值,完全不需要拆箱操作,速度自然快很多。
- Method 1的内层遍历
简单来说,Method 1全程处理非泛型数组的object元素,每一次元素访问都伴随类型转换或拆箱;而Method 2通过提前强制转换为强类型数组,把一次性开销前置,后续遍历全程操作强类型元素,无额外开销,因此速度差了一个数量级。
内容的提问来源于stack exchange,提问作者Ladislav
相关产品推荐
相关产品推荐

