CPU数据按行和按列读取性能差异浅析

改了一行代码,数组遍历耗时从10.3秒降到了0.5秒!

两个简单的测试程序

定义一个同样大小的二维数组,然后循环遍历,对数组元素赋值。

array1.c 对数组按行进行访问

• array2.c 对数组按列进行访问、

编译运行,并用time命令统计一下运行时间:

array1用时0.528秒,array2用时10.310秒。array2耗时居然是array1的将近20倍!

有没有被这个结果震惊到?为什么会有如此之大的性能差异呢?

重要说明

要想真正理解这个问题,必须要先补充一些关于现代计算机存储系统相关的背景知识,这也是理解这个问题的关键所在。为方便大家理解,我会尽量以白话的形式进行讲解,尽可能避免枯燥无味的纯理论描述。

存储金字塔

相信不少人都听过“存储金字塔”这个词,或者至少见过类似下面这张图:

这张图很直观地描述了现代计算机系统的分级存储模型。

可以认为CPU就位于金字塔的顶点上,越靠近塔顶,离CPU越近,访问速度越快,但生产成本越高,相应的容量也就越小。在所有存储器中,寄存器直接内嵌在CPU中,访问速度最快,容量也最小,一般CPU上也就最多几十个通用寄存器供应用程序使用。

反之,越往下靠近塔底,访问速度越慢,生产成本越低,相应的容量也就越大。比如图中最底部的网络存储设备,相对其他存储设备而言是访问速度最慢的,但其容量却几乎可以认为是无限制的。

那么,这种金字塔式结构中,不同层级的存储设备之间究竟是如何协调工作的呢?

用一句话概括:

高一级的存储设备,往往是作为低一级存储设备的缓存来使用的。

简单来说,系统运行时,为了提升数据访问效率,把程序中最近最经常访问的数据,尽可能放到访问速度更快的高一级存储器中。这样一来,每次访问数据时,从金字塔的顶端开始,都先尝试在高一级存储器中查找,如果被访问的数据存在且有效,则直接访问,否则,就逐级到更低级的存储器中去查找。

这种金字塔式的分级存储模型之所以能够以近乎完美的方式运行,实际上都是建立在现代计算机科学中的一个非常重要的理论基础之上:程序的局部性原理

局部性原理

一个程序的局部性,包含两个维度:时间局部性和空间局部性。

  • • 时间局部性。如果一个数据在某个时间点被CPU访问了,那么在接下来很短的一段时间内,这个数据很有可能会再次被CPU访问到。

  • • 空间局部性。如果一个数据在某个时间点被CPU访问了,那么与这个数据临近的其他数据,很有可能也会很快被CPU访问到。

高速缓存 - Cache

根据常识,我们知道,程序要想被CPU正常执行,必须要先被加载到内存中。

但是,内存的访问速度与CPU运行速度相比,要慢好几个数量级,可以想象一下,如果CPU每次都直接从内存中存取数据,会造成大量的计算资源浪费,程序性能严重受损,假如真是这样的话,你还能像现在这样愉快的吃鸡吗?

为了解决CPU和内存之间速度严重不匹配的问题,在CPU和内存之间设计了高速缓存,即Cache。

目前,主流CPU一般都有三级(或更多级)的Cache,依次是L1 Cache、L2 Cache、L3 Cache。其中L1 Cache速度最快,几乎可以和内嵌在CPU中的寄存器媲美,容量也最小,而L3 Cache速度最慢(但仍然比内存快很多),但容量最大。

CPU读取数据时,会在L1、L2、L3Cache中逐级查找,如果找到,就从Cache直接读取,找不到再从内存读取,并且把数据存放到Cache中,以便提高下次访问的效率。

在这个过程中,如果在Cache中找到所需数据,称为Cache命中(Cache Hit), 找不到称为Cache未命中(Cache Miss)

不难看出,L1 Cache命中的时候,读取数据最快,性能最好,而当L1、L2、L3 Cache全部未命中时,就必须要直接从内存中读取数据,性能最差!

Cache Line

Cache Line 是 Cache和内存之间进行数据传输的最小单位。

根据上文讲解的程序的局部性原理,如果一个数据被CPU访问了,那么这个数据相邻的其他数据也很快会被访问到。因此,为了提高内存数据的读取效率,并且最大化利用CPU资源,数据在Cache和内存之间传输时,不是一个字节一个字节进行传输的,而是以缓存行(Cache Line)为单位进行传输的。

不同CPU的Cache line大小可能不同,典型的CPU Cache line大小是64个字节。

我们通过下面一个简单的例子,加深一下理解。

Cache Line 实例讲解

在一个Cache Line大小为64字节的机器上,定义个数组:

int a[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};

我们假设数组a的起始地址是Cache Line对齐的,可以简单理解为a的地址能被64整除。假设,数组a还从来没有被访问过,如果此时需要访问a中的一个元素a[5],如:

int x = a[5];

由于在此之前数组a没有被访问过,所以理论上讲,数组a应该只存在于内存中,并未被加载到Cache中。因此,此时CPU在Cache中找不到a[5],触发Cache Miss,然后需要从内存中读取数据,并更加载到Cache中。前面提到,Cache和内存之间是以Cache Line为单位进行数据传输的,因此,这里会把一个Cache line大小(64字节)的数据从内存读取出来加载到Cache中。由于a的起始地址恰巧是Cache line对齐的,所以CPU会把整个数组(64个字节,刚好一个Cache Line)都加载到Cache中。

紧接着,如果再访问数组a的元素,如:

int y = a[10];

此时,整个数组都在Cache中,所以CPU在访问时,触发Cache Hit,直接从Cache读取数据即可,不需要再从内存中读取。

了解了Cache的背景知识,现在来分析下array1.c和array2.c为什么会存在这么巨大的性能差异。

按行、列访问的真正差异 - Cache

首先,必须要知道一点:C语言的数组,所有元素是存放在地址连续的内存中的,此外,C语言的多维数组,是按行进行存储的

array1.c按行对数组进行访问,也就是从数组起始地址开始,一直连续访问到数组的最后一个元素的地址处。第一次访问一个Cache Line的首个元素时,触发Cache Miss,与该元素临近的几个元素会组成一个Cache Line,被一起加载到Cache中。如此,在访问下一个元素的时候,就会Cache Hit,直接从Cache读取数据即可。

而array2.c按列对数组进行访问,因此并不是按照连续地址进行访问的,而是每次间隔10240 * 4个字节进行访问。第一次访问一个Cache Line的首个元素时,假设地址为x,尽管该元素临近的一个Cache Line大小的元素也会被一起加载进Cache中,但是程序接下来访问的并不是临近的元素,而是地址为x + 10240 * 4处的元素,因此会再次触发Cache Miss。而当程序回过头来访问x + 4地址处的元素时,这个Cache Line可能已经被其他数据冲刷掉了。因为,尽管Cache会尽量缓存最近访问过的数据,但毕竟大小有限,当Cache被占满时,一些旧的数据就会被冲刷替换掉。

可以看出,无论是时间局部性还是空间局部性,array1.c都要比array2.c好很多!相比array1.c,array2.c会触发大量的Cache Miss,这也是为什么array2的性能会如此之差!

下面我们用perf来对比下这两个程序的性能指标。

用perf观测性能指标

perf是Linux提供的一个功能强大的实用性能调优工具,它可以用来观测几乎所有CPU相关的性能指标,但perf工具本身,不是本文重点,以后会有专门文章详细介绍perf的使用。

先获取array1.c的性能指标:

再看下array2.c的性能指标:

这里,我们只关注一个最重要的性能指标:L1 Data Cache的Miss率。

对比一下,array1.c的L1-dcache-load-misses率只有3.10%, 而按列访问的array2.c,则达到了惊人的93.03%!这就是两者性能差异如此巨大的真相!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/335976.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[小程序]页面的构建

一、视图容器组件 ①View 视图区域组件&#xff0c;类似于HTML中的div&#xff0c;可以用来按块实现页面布局效果&#xff0c;具体实例如下&#xff1a; <view class"dock"><view>A</view><view>B</view><view>C</view> &…

《Linux高性能服务器编程》笔记01

Linux高性能服务器编程 本文是读书笔记&#xff0c;如有侵权&#xff0c;请联系删除。 参考 Linux高性能服务器编程源码: https://github.com/raichen/LinuxServerCodes 豆瓣: Linux高性能服务器编程 文章目录 Linux高性能服务器编程第05章 Linux网络编程基础API5.1 socket…

JOSEF约瑟 零序过流继电器LGL-110/AC AC220V 0.01~9.99A 柜内安装

LGY 、LGL零序过电压继电器 系列型号 LGY-110零序过电压继电器&#xff1b; LGL-110零序过电压继电器&#xff1b; LGL-110/AC零序过电压继电器&#xff1b; LGL-110静态零序过电流继电器 &#xff11; 应用 LGL-110 型零序过电流继电器用作线路和电力设备的零序过电流保护。…

【Arduino】无法上传程序到开发板,报错 avrdude: ser_open(): can‘t set com-state for “\\.\COM6“

问题描述 在尝试将项目上传到Arduino板子时&#xff0c;尽管开发板已被正确连接&#xff0c;并且IDE中能够正常读取到开发板信息&#xff0c;但是上传过程中仍然出现了问题。 下面是IDE中显示的开发板信息&#xff1a; 当尝试上传程序时&#xff0c;控制台报错信息如下&#…

【迅搜19】扩展(二)TNTSearch和JiebaPHP方案

扩展&#xff08;二&#xff09;TNTSearch和JiebaPHP方案 搜索引擎系列的最后一篇了。既然是最后一篇&#xff0c;那么我们也轻松一点&#xff0c;直接来看一套非常有意思的纯 PHP 实现的搜索引擎及分词方案吧。这一套方案由两个组件组成&#xff0c;一个叫 TNTSearch &#xf…

【大数据Hive】hive 行列转换使用详解

目录 一、前言 二、使用场景介绍 2.1 使用场景1 2.2 使用场景2 三、多行转多列 3.1 case when 函数 语法一 语法二 操作演示 3.2 多行转多列操作演示 四、多行转单列 4.1 concat函数 语法 4.2 concat_ws函数 语法 4.3 collect_list函数 语法 4.4 collect_set函…

获取域控的方法

在域渗透中、作为渗透测试人员&#xff0c;获取域控的权限基本上可以获取整个内网的权限 1.高权限读取本地密码 当域管理员在域成员机器上登录进行工作的时候&#xff0c;会将明文密码保存在本地进行的lsass.exe&#xff0c;可以通过 mimikatz来读取到本地的明文密码。 priv…

MySQL基础笔记(9)事务

一.简介 所谓事务&#xff0c;是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或者撤销操作请求&#xff0c;即&#xff0c;这些操作要么同时成功&#xff0c;或者同时失败——OS中有原语不可分割的概念&…

蓝桥杯、编程考级、NOC、全国青少年信息素养大赛—scratch列表考点

1、小小情报员&#xff08;202309scratch四级24题&#xff09; 1.准备工作 &#xff08;1&#xff09;选择背景 Colorful City&#xff1b; &#xff08;2&#xff09;保留角色小猫&#xff0c;选择角色Ballerina。 2.功能实现 &#xff08;1&#xff09;角色小猫初始位置…

灵活扩展:深入理解MyBatis插件机制

第1章&#xff1a;MyBatis插件的重要性 大家好&#xff0c;我是小黑&#xff0c;咱们今天要聊的是MyBatis插件&#xff0c;MyBatis&#xff0c;大家都不陌生&#xff0c;它是一个ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;让咱们在操作数据库时能更加优雅。但今…

vulnhub通关-1 DC-1(含靶场资源)

一、环境搭建 1.环境描述 描述 描述&#xff1a; DC-1 is a purposely built vulnerable lab for the purpose of gaining experience in the world of penetration testing. Dc-1是一个专门构建的易受攻击的实验室&#xff0c;目的是获得渗透测试领域的经验。 It was design…

怎么移除WordPress后台工具栏的查看站点子菜单?如何改为一级菜单?

默认情况下&#xff0c;我们在WordPress后台想要访问前端网站&#xff0c;需要将鼠标移动到左上角的站点名称&#xff0c;然后点击下拉菜单中的“查看站点”才行&#xff0c;而且还不是新窗口打开。那么有没有办法将这个“查看站点”子菜单变成一级菜单并显示在顶部管理工具栏中…

Docker进阶篇-安装MySQL主从复制

一、MySQL主服务器 1、新建主服务器容器实例3307 docker run -p 3307:3306 \--name mysql-master \--privilegedtrue \-v /mydata/mysql-master/log:/var/log/mysql \-v /mydata/mysql-master/data:/var/lib/mysql \-v /mydata/mysql-master/conf:/etc/mysql \-e MYSQL_ROOT_…

超声波清洗机清洗眼镜有用吗?值得入手洗眼镜超声波清洗机推荐

眼镜党朋友长时间佩戴眼镜避免不了受到灰尘、污垢和细菌的侵扰&#xff0c;不清洗的话我们的视线就会被有所阻碍&#xff0c;为了保证我们眼镜的干净同时也是为了注意个人卫生&#xff0c;建议我们定期清洗一下眼镜&#xff0c;给眼镜洗个澡顺便消消毒&#xff0c;从一开始用水…

多分支机构大型企业如何高效运维管理?向日葵x金地商置案例分享

对于下设多个分支机构的&#xff0c;跨地区经营的大型企业来说&#xff0c;如何高效安全的实施IT运维是一个重要的课题&#xff1b;同时&#xff0c;分支机构之间如何实现高效的异地协同办公&#xff0c;并且在这一需求的基础上进一步强化管理&#xff0c;也是企业管理者需要认…

Ubuntu 22.04 安装MySql

MySQL是非常常用的关系型数据库,无论是大厂还是小厂,都有它的身影。最大的优点是免费,安装起来也比较简单。 MySQL的架构 画了个简图,描述了下MySQL的架构。 其中的比较有趣的点在于连接池和存储引擎。连接池缓存了数据库和客户端的TCP连接,以减少建立连接的开销。存储引…

第35集《佛法修学概要》

己四 、 精进度 分三&#xff1a;庚一、 精进自性。庚 二、趣入修习精进方便。 庚三、修习精进差别内容 请大家打开讲义第九十四页&#xff0c;我们看己四&#xff0c;精进度。 当我们从人天乘一个好人的阶段提升到一种菩萨道的修学&#xff0c;我们就要注意两个重点了。在我…

实验五 PLSQL编程

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

Cmake 之Android库编译

一 检测库和执行程序能否在Android上用 1.1 我们知道Cmake不止能编译Linux库程序&#xff0c;也能编译出其它系统的库&#xff0c;如windows&#xff0c;ios和android等&#xff0c;那么上一篇生成的Linux的库程序能否直接用于Android上呢&#xff0c;下面先来做个测试。 1.2…

实验算法设计

文章目录 Unettransformer整体网络架构 Unet 可以用双线性差值替换&#xff0c;效果差不多&#xff0c;参数更少。 from typing import Dict import torch import torch.nn as nn import torch.nn.functional as F class DoubleConv(nn.Sequential):def __init__(self, in_cha…