上周在.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遍历二维数组-先行/先列哪个更快?的更多相关文章

  1. C/C++遍历二维数组,列优先(column-major)比行优先(row-major)慢,why?

    C/C++遍历二维数组,列优先(column-major)比行优先(row-major)慢,why? 简单粗暴的答案:存在Cache机制! 稍微啰嗦一点:CPU访问内存(读/写,遍历数组的话主要是读) ...

  2. PHP 距离我最近排序+二维数组按指定列排序

    思路: 1.获取我的位置,即:我的经纬度 2.各站点须有位置     即:排序对象有位置经纬度 3.查询要排序的站点列表 4.循环遍历计算  与我的距离 5.二维数组按 指定列(距离)排序 具体如下: ...

  3. for里面是采用setInterval遍历二维数组,for循环到最后一个数的时候,才执行setInterval的问题解决

    点击播放看效果 <!doctype html> <html lang="en"> <head> <meta charset="U ...

  4. C:指针遍历二维数组

    C 指针遍历二维数组 http://blog.csdn.net/lcxandsfy/article/details/55000033 C++ 字符串指针与字符串数组 https://www.cnblo ...

  5. 三重for循环实现对二维数组的按列排序(JavaScript)

    由C语言联想到的:三重for循环实现对二维数组的按列排序,并且牵扯到数据结构. 自己写的,水平有限,本文属于原创,可能存在错误,忘指正~ function circle() { var a = [ [ ...

  6. php中遍历二维数组并以表格的形式输出

    一.索引数组 <?php //使用array()语句结构将联系人列表中所有数据声明为一个二维数组,默认下标是顺序数字索引 $contact1 = array( //定义外层数组 array(1, ...

  7. java基础之二维数组不定义列数

    有一种特殊的二维数组,它的行数确定,但是每行的列数不确定.这样的的数组实现方法:先创建制定行数,列数缺省的二维数组,然后对数组的每一行重新初始化.举例如下: package day5; //第二种定义 ...

  8. PHP二维数组--去除指定列含有重复项的数组

    给定二维数组: $arr = array( '0' => array('张三',2,3,4), '1' => array('李四',2,3,4), '2' => array('张三' ...

  9. php中foreach循环遍历二维数组

    最近在用tp3.2框架,在查询的时候用到了select(),这条语句返回的是二维数组,所以在对返回的数据做处理时,遇到了些麻烦,百度了下foreach,终于用foreach解决了数据的筛选问题 (因为 ...

  10. 计算机二级-C语言-程序设计题-190119记录-求出一个二维数组每一列的最小值。

    //编写一个函数:tt指向一个M行N列的二维数组,求出二维数组每列中最小的元素,并依次放入pp所指的一维数组中.二维数组中的数在主函数中赋予. //重难点:求出的是每一列的最小值,这里要注意,学会简化 ...

随机推荐

  1. C#与Halcon联合编程之用PictureBox控件替代HWindowControl控件

    在使用HALCON和C#联合编程,有时候要使用halcon的HWindowControl控件,但是我发现,HWindowControl的图片显示控件,不能使用GDI+绘制ROI,不知道为什么,反正我测 ...

  2. 2022春每日一题:Day 28

    题目:最大上升子序列和 就是最长上升子序列的改版,贡献由1改为a[i]其他全部不变 代码: #include <cstdio> #include <cstdlib> #incl ...

  3. mysql 多个结构不同表查询 返回相同字段名

    ( select ID,数据库原字段名1 AS 统一字段名1,数据库原字段名2 AS 统一字段名2 from 第一个表名 WHERE 1) UNION(联合表查询)( select ID,数据库原字段 ...

  4. Tauri+Rust+Vue 跨平台桌面应用简明教程(1)环境创建+系统事件+自定义菜单

    作者:小牛呼噜噜 | https://xiaoniuhululu.com 计算机内功.JAVA底层.面试相关资料等更多精彩文章在公众号「小牛呼噜噜 」 Tauri简介 Tauri 是一个工具包,可以帮 ...

  5. Android 按钮自定义背景后点击没有动画效果

    只需要在按钮中添加属性就可以了 android:foreground="?selectableItemBackground"

  6. 模拟Promise的功能

    模拟Promise的功能,  按照下面的步骤,一步一步 1. 新建是个构造函数 2. 传入一个可执行函数 函数的入参第一个为 fullFill函数 第二个为 reject函数: 函数立即执行, 参数函 ...

  7. mysql-DuplicateUpdate和java的threadpool的"死锁"

    大家千万不要被文章的标题给迷惑了,他两在本篇文章是没有关系的, 今天给大家讲讲最近2个有意思的issue,分享一下我学到的 mysql DuplicateUpdate的用法要注意的点 java的thr ...

  8. 【Linux】个人笔记本安装Centos并开放22端口供外网连接

    〇.参考资料 一.配置及安装内容 (一)配置 [today]低配笔记本(装centos):4+500G 个人电脑(装三个节点的虚拟机集群环境):8+628G (二)所装软件 Linux系统(可选cen ...

  9. 4.4:Sqoop数据导入实验

    〇.概述 1.拓扑结构 2.目标 使用sqoop工具将数据从mysql数据库导入到HDFS和Hbase 一.配置免密登录hdfs 三.导入到hdfs中 sqoop import --connect j ...

  10. Redis如何模糊匹配Key值

    Redis模糊匹配Key值 使用Redis的scan代替Keys指令: public Set<String> scan(String matchKey) { Set<String&g ...