You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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倍,请问背后的原因是什么?


原因解析

核心差异在于非泛型集合的类型转换/拆箱开销,具体分两层来看:

  1. 外层遍历的类型处理

    • 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元素,不需要每次迭代都做类型转换,开销大幅降低。
  2. 内层遍历的拆箱开销

    • 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全程处理非泛型数组的object元素,每一次元素访问都伴随类型转换或拆箱;而Method 2通过提前强制转换为强类型数组,把一次性开销前置,后续遍历全程操作强类型元素,无额外开销,因此速度差了一个数量级。


内容的提问来源于stack exchange,提问作者Ladislav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 08:25:22