.NET遍历二维数组-先行/先列哪个更快?
上周在.NET性能优化群里面有一个很有意思的讨论,讨论的问题如下所示:
请教大佬:2D数组,用C#先遍历行再遍历列,或者先遍历列再遍历行,两种方式在性能上有区别吗?
据我所知,Julia或者python的 pandas,一般建议先遍历列,再遍历行
在群里面引发了很多大佬的讨论,总的来说观点分为以下三种:
- 应该不会有什么差别
- 先遍历列会比先遍历行更快
- 先遍历行会比先遍历列更快
看了群里面激烈的讨论,刚好今天有时间,我们就来看看真实情况是怎么样的?实践出真知,我们编写一个Benchmark一测便知。
测试
在下面的代码中,我们创建了一个 ArrayBenchmark
类,它包含了两个方法:RowFirst
和 ColumnFirst
。这两个方法分别代表了先行后列和先列后行两种遍历方式。每次测试时,数组的大小将使用参数(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倍的差距:
关于为什么先行后列的性能比先列后行高,猜测主要有以下两个原因:
CPU 缓存层次结构:当遍历二维数组时,先行后列方式更适合利用 CPU 的缓存层次结构。每次访问二维数组中的一行数据时,这一整行的数据都可以从 L1/L2/L3 缓存中读取,这样就可以大大提高数据读取的效率。
内存布局:二维数组的内存布局可能是按行存储的,也就是说一整行的数据在内存中是连续的。因此,先行后列的方式更容易利用内存的连续性,使数据读取更加顺畅。
我们可以通过简单的代码来验证一下.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遍历二维数组-先行/先列哪个更快?的更多相关文章
- C/C++遍历二维数组,列优先(column-major)比行优先(row-major)慢,why?
C/C++遍历二维数组,列优先(column-major)比行优先(row-major)慢,why? 简单粗暴的答案:存在Cache机制! 稍微啰嗦一点:CPU访问内存(读/写,遍历数组的话主要是读) ...
- PHP 距离我最近排序+二维数组按指定列排序
思路: 1.获取我的位置,即:我的经纬度 2.各站点须有位置 即:排序对象有位置经纬度 3.查询要排序的站点列表 4.循环遍历计算 与我的距离 5.二维数组按 指定列(距离)排序 具体如下: ...
- for里面是采用setInterval遍历二维数组,for循环到最后一个数的时候,才执行setInterval的问题解决
点击播放看效果 <!doctype html> <html lang="en"> <head> <meta charset="U ...
- C:指针遍历二维数组
C 指针遍历二维数组 http://blog.csdn.net/lcxandsfy/article/details/55000033 C++ 字符串指针与字符串数组 https://www.cnblo ...
- 三重for循环实现对二维数组的按列排序(JavaScript)
由C语言联想到的:三重for循环实现对二维数组的按列排序,并且牵扯到数据结构. 自己写的,水平有限,本文属于原创,可能存在错误,忘指正~ function circle() { var a = [ [ ...
- php中遍历二维数组并以表格的形式输出
一.索引数组 <?php //使用array()语句结构将联系人列表中所有数据声明为一个二维数组,默认下标是顺序数字索引 $contact1 = array( //定义外层数组 array(1, ...
- java基础之二维数组不定义列数
有一种特殊的二维数组,它的行数确定,但是每行的列数不确定.这样的的数组实现方法:先创建制定行数,列数缺省的二维数组,然后对数组的每一行重新初始化.举例如下: package day5; //第二种定义 ...
- PHP二维数组--去除指定列含有重复项的数组
给定二维数组: $arr = array( '0' => array('张三',2,3,4), '1' => array('李四',2,3,4), '2' => array('张三' ...
- php中foreach循环遍历二维数组
最近在用tp3.2框架,在查询的时候用到了select(),这条语句返回的是二维数组,所以在对返回的数据做处理时,遇到了些麻烦,百度了下foreach,终于用foreach解决了数据的筛选问题 (因为 ...
- 计算机二级-C语言-程序设计题-190119记录-求出一个二维数组每一列的最小值。
//编写一个函数:tt指向一个M行N列的二维数组,求出二维数组每列中最小的元素,并依次放入pp所指的一维数组中.二维数组中的数在主函数中赋予. //重难点:求出的是每一列的最小值,这里要注意,学会简化 ...
随机推荐
- Linux网络通信(线程池和线程池版本的服务器代码)
线程池 介绍 线程池: 一种线程使用模式.线程过多会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务.这避免了在处理短时间任务时创建与销毁线程的 ...
- 嵌入式-C语言基础:指针
指针就是地址,变量的值可以通过两种方式访问,一个是通过变量名,一个是通过地址访问. 从而引出一个问题,即什么是指针变量?整型(字符)变量就是存放整形(字符)的变量,指针变量就是存放指针的变量,也就是存 ...
- 备考CISP-PTE之文件上传
upload-labs 直接下载放到phpstudy打开即可. 第一关 查看源码,可以看到js代码定义了一个checkFile函数来对上传的文件进行后缀检查,只允许上传jpg.png.gif文件. f ...
- phpmyadmin 数据库导出数据到excel(图文版)
查询到想要的数据后,点击上方或下方的"导出"按钮 格式选择"CSV for MS Excel" 如果快速导出的数据乱码,可以选择"导出方式" ...
- 快速构建一个简单的Springboot-web项目
web项目基本的核心成分 数据落地 MYSQL数据库 登录标识 JWT :{Java web token } 记录有效登录状态 以及缓存常用数据: Redis 数据库与JAVA实体的快速自动映射ORM ...
- 卸载virtualbox中linux虚拟机的增强工具
报错信息 vboxclient:the virtualbox kernel service is not running 前言 我由virtualbox换到vmware 遇到了这个问题,很烦每次都通知 ...
- php+apache环境搭建
[先安装apache] apache快速安装:https://www.cnblogs.com/brad93/p/16718104.html PHP安装教程参考:https://www.cnblogs. ...
- 数据结构高阶--AVL(平衡二叉树)(图解+实现)
AVL树(平衡二叉树) 概念 二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下.因此为了解决这个问题,两位俄罗斯的数学家发明 ...
- 【kafka】JDBC connector进行表数据增量同步过程中的源表与目标表时间不一致问题解决
〇.参考资料 一.现象 1.Oracle源表数据 2.PG同步后的表数据 3.现象 时间不一致,差了8个小时 4.查看对应的connector信息 (1)source { "connecto ...
- requests模块和openpyxl模块
第三方模块的下载和使用 1,第三方模块就是别人大神们已经写好的模块,功能特别强大.我们如果像使用第三方模块就先要进行下载.下载完成后 才可以在python中直接调用 2.下载方式一:pip工具 pip ...