.NET遍历二维数组-先行/先列哪个更快?

更快,NET,数组,先行/先列 · 浏览次数 : 725

小编点评

**先遍历行比先遍历列更快的原因:** * **CPU 缓存层次结构:**当使用先行后列遍历时,数据可以从 L1/L2/L3 缓存中读取,大大提高数据读取的效率。 * **内存布局:**二维数组的内存布局可能是按行存储的,更容易利用内存的连续性,使数据读取更加顺畅。 **代码示例:** ```csharp // 先遍历行 int sum = 0; for (int i = 0; i < Size; i++) { for (int j = 0; j < Size; j++) { sum += _array[i, j]; } } // 先遍历列 int sum = 0; for (int j = 0; j < Size; j++) { for (int i = 0; i < Size; i++) { sum += _array[i, j]; } } ``` **结论:** 在 .NET 7.0 中,先遍历行比先遍历列更快,因为 CPU 缓存层次结构和内存布局等因素使先行后列遍历更有效。

正文

上周在.NET性能优化群里面有一个很有意思的讨论,讨论的问题如下所示:

请教大佬:2D数组,用C#先遍历行再遍历列,或者先遍历列再遍历行,两种方式在性能上有区别吗?
据我所知,Julia或者python的 pandas,一般建议先遍历列,再遍历行

在群里面引发了很多大佬的讨论,总的来说观点分为以下三种:

  • 应该不会有什么差别
  • 先遍历列会比先遍历行更快
  • 先遍历行会比先遍历列更快

看了群里面激烈的讨论,刚好今天有时间,我们就来看看真实情况是怎么样的?实践出真知,我们编写一个Benchmark一测便知。

测试

在下面的代码中,我们创建了一个 ArrayBenchmark 类,它包含了两个方法:RowFirstColumnFirst。这两个方法分别代表了先行后列和先列后行两种遍历方式。每次测试时,数组的大小将使用参数(Size)设置。在 Main 方法中,我们调用 BenchmarkRunner.Run 方法来运行测试。

using System;
using System.Diagnostics;
using BenchmarkDotNet.Attributes;

namespace TwoDimensionalArrayBenchmark
{
    public class ArrayBenchmark
    {
        private int[,] _array;

        [Params(1000, 2000, 4000, 8000, 16000)]
        public int Size { get; set; }

        [GlobalSetup]
        public void Setup()
        {
            _array = new int[Size, Size];
            var rnd = new Random();
            for (int i = 0; i < Size; i++)
            {
                for (int j = 0; j < Size; j++)
                {
                    _array[i, j] = rnd.Next();
                }
            }
        }

        [Benchmark]
        public int RowFirst()
        {
            // 先遍历一整行
            int sum = 0;
            for (int i = 0; i < Size; i++)
            {
                for (int j = 0; j < Size; j++)
                {
                    sum += _array[i, j];
                }
            }
            return sum;
        }

        [Benchmark]
        public int ColumnFirst()
        {
            // 先遍历一整列
            int sum = 0;
            for (int j = 0; j < Size; j++)
            {
                for (int i = 0; i < Size; i++)
                {
                    sum += _array[i, j];
                }
            }
            return sum;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run<ArrayBenchmark>();
            Console.ReadKey();
        }
    }
}

得出的结果如下所示,从结果中我们可以看到,在.NET7.0中先遍历行远远快于先遍历列,随着数据量的增大有着近10倍的差距:

关于为什么先行后列的性能比先列后行高,猜测主要有以下两个原因:

  1. CPU 缓存层次结构:当遍历二维数组时,先行后列方式更适合利用 CPU 的缓存层次结构。每次访问二维数组中的一行数据时,这一整行的数据都可以从 L1/L2/L3 缓存中读取,这样就可以大大提高数据读取的效率。

  2. 内存布局:二维数组的内存布局可能是按行存储的,也就是说一整行的数据在内存中是连续的。因此,先行后列的方式更容易利用内存的连续性,使数据读取更加顺畅。

我们可以通过简单的代码来验证一下.NET中二维数组的存储格式,使用Unsafe.AsPointer可以获取引用对象的指针,然后将其强转为long类型即可获得它的地址。

下面使用的是先行后列的遍历方式:

由于一个int类型占用4字节的空间,所以我们可以发现在使用先行后列的方式时刚好就是顺序顺序递增的。

也就是说C#在逻辑上虽然是二维数组,实际上存储是按每一行连续存储的,如下图所示:

CPU的缓存也是按照这个顺序进行缓存的,所以当我们先行后列遍历的时候整行数据都可能在CPU缓存中,可以最大化的利用好CPU缓存。

如果按照先列后行的遍历,那么对缓存就很不友好,需要多次从内存中读取数据。

总结

这就是本文的全部了,目前看来在C# .NET中遍历二维数组是先行快于先列,不过这也不是绝对的事情,因为在编译器和即时编译器中,是可以自动的去做一些优化,让程序更快的访问数据。比如在群里大佬们比较了在VC中的差异,结果是发现DEBUG模式确实行快于列,但是Release两者差别几乎可以忽略不计,当然这不在本文的讨论范围中。

.NET性能优化交流群

相信大家在开发中经常会遇到一些性能问题,苦于没有有效的工具去发现性能瓶颈,或者是发现瓶颈以后不知道该如何优化。之前一直有读者朋友询问有没有技术交流群,但是由于各种原因一直都没创建,现在很高兴的在这里宣布,我创建了一个专门交流.NET性能优化经验的群组,主题包括但不限于:

  • 如何找到.NET性能瓶颈,如使用APM、dotnet tools等工具
  • .NET框架底层原理的实现,如垃圾回收器、JIT等等
  • 如何编写高性能的.NET代码,哪些地方存在性能陷阱

希望能有更多志同道合朋友加入,分享一些工作中遇到的.NET性能问题和宝贵的性能分析优化经验。目前一群已满,现在开放二群。
如果提示已经达到200人,可以加我微信,我拉你进群: ls1075
另外也创建了QQ群,群号: 687779078,欢迎大家加入。

与.NET遍历二维数组-先行/先列哪个更快?相似的内容:

.NET遍历二维数组-先行/先列哪个更快?

上周在.NET性能优化群里面有一个很有意思的讨论,讨论的问题如下所示: 请教大佬:2D数组,用C#先遍历行再遍历列,或者先遍历列再遍历行,两种方式在性能上有区别吗? 据我所知,Julia或者python的 pandas,一般建议先遍历列,再遍历行 在群里面引发了很多大佬的讨论,总的来说观点分为以下三

Keil一键添加.c文件和头文件路径脚本--可遍历添加整个文件夹

最近想移植个LVGL玩玩,发现文件实在是太多了,加的手疼都没搞完,实在不想搞了就去找脚本和工具,基本没找到一个。。。。。。 主要是自己也懒得去研究写脚本,偶然搜到了一个博主写的脚本,原博客地址:https://blog.csdn.net/riyue2044/article/details/13942

.NET周刊【6月第5期 2024-06-30】

国内文章 呼吁改正《上海市卫生健康信息技术应用创新白皮书》 C# 被认定为A 组件 的 错误认知 https://www.cnblogs.com/shanyou/p/18264292 近日,《上海市卫生健康“信息技术应用创新”白皮书》发布,提到医疗信创核心应用适配方法及公立医院信息系统。文章中对C#

记一次 .NET某网络边缘计算系统 卡死分析

一:背景 1. 讲故事 早就听说过有什么 网络边缘计算,这次还真给遇到了,有点意思,问了下 chatgpt 这是干嘛的 ? 网络边缘计算是一种计算模型,它将计算能力和数据存储位置从传统的集中式数据中心向网络边缘的用户设备、传感器和其他物联网设备移动。这种模型的目的是在接近数据生成源头的地方提供更快速

无业游民写的最后一个.net有关项目框架

理想很丰满,现实往往很残酷。 一种按照ddd的方式,根据业务来把自己需要的模块一个一个写出来,再按照模块把需要的接口一个一个的写出来,堆砌一些中间件,以及解耦的command,handler等等 ,一个项目就这么成型了。上面的项目有一个非常清晰的特点,就是按需开发,不需要去可以定义业务相关的公共的模

.NET周刊【6月第4期 2024-06-23】

国内文章 C#.Net筑基-集合知识全解 https://www.cnblogs.com/anding/p/18229596 .Net中提供了数组、列表、字典等多种集合类型,分为泛型和非泛型集合。泛型集合具有更好的性能和类型安全性。集合的基础接口包括IEnumerator、IEnumerable、I

.net入行三年的感想回顾

从21年毕业到现在,还差几天就三年了 工作后才知道,工作年限分为1年以下 、3~5年、5~10年、晋升老板,每段都有每段的故事和总结 回顾下我的前三年工作心路,思考下未来发展之路(emmm,我是觉得我是干不了一辈子程序员的 我的工作地点不在大城市,因为我爸不让我出去,家里也不是很缺钱,所以薪资不会辣

C#/.NET这些实用的技巧和知识点你都知道吗?

前言 今天大姚给大家分享一些C#/.NET中的实用的技巧和知识点,它们可以帮助我们提升代码质量和编程效率,希望可以帮助到有需要的同学。 .NET使用CsvHelper快速读取和写入CSV文件 本文主要讲解.NET中如何使用CsvHelper这个开源库快速实现CSV文件读取和写入。 https://m

一款.NET开源的i茅台自动预约小助手

前言 今天大姚给大家分享一款.NET开源、基于WPF实现的i茅台APP接口自动化每日自动预约(抢茅台)小助手:HyggeImaotai。 项目介绍 该项目通过接口自动化模拟i茅台APP实现每日自动预约茅台酒的功能,软件会在指定时间开始对管理的用户进行批量预约。 项目功能 用户管理 预约项目 店铺管理

C# 13(.Net 9) 中的新特性 - 扩展类型

C# 13 即 .Net 9 按照计划会在2024年11月发布,目前一些新特性已经定型,今天让我们来预览一个比较大型比较重要的新特性: 扩展类型 extension types