一文解析数据结构是如何装入 CPU 寄存器的?

2023-12-15 18:52

本文主要是介绍一文解析数据结构是如何装入 CPU 寄存器的?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我们在之前很多文章的讲解中涉及了CPU与寄存器,然后有同学问了这样一个问题:既然CPU内部的寄存器数量有限,容量有限,那么我们使用的庞大的数据结构是怎样装入寄存器供CPU计算的呢?这篇文章就为你讲解一下这个问题。

内存与数据

真正有用的程序是离不开数据的,比如一个int、一个float等,这些都是非常简单的数据。当然也有非常复杂的数据,这样的数据通常在内存中以数据结构的形式组织起来,比如你创建了一个数组、一个链表、创建了一棵树、一张图,就像这样:

那么很显然这些数据存放在内存中,而且这些数据在不同的场景下有不同的大小,从数B、数KB到数百GB都有可能,与此同时,CPU内部的寄存器数量是固定的,容量也是极其有限的,那么CPU是如何利用有限的资源操作庞大的数据结构呢?

要回答这一问题,我们需要要认识一位农夫,因为他不生产数据,他只是数据的搬运工,这位农夫就是。。

搬运数据的机器指令

你没有看错,这位农夫就是我们之前多次提到的机器指令。机器指令中除了负责逻辑运算、执行流控制、函数调用等指令外,还有一类指令,这类执行只负责和内存打交道,典型的就是精简指令集架构中的Load/Store机器指令,即内存读写指令(复杂指令集没有单独的内存读写指令)。原来,从宏观上看的话,存放在内存中的数据,比如一个数组,可能会非常庞大,但是具体到代码,每一个步骤操作的数据又会非常简单,就像这样:

int* huge_arr = new int[1 * 1024* 1024 *1024];

我们创建了一个长度为1G的数组,每个int 4字节,则这个数组的大小就是4GB,这显然是一个很庞大的数组。对于这样的数据,我们通常都会怎么使用呢?最常见的情况可能是遍历一边,然后对每个字符进行一个简单操作,这里以计算数组之和为例:

long int sum = 0;
for (int i = 0; i < 1 * 1024* 1024 *1024; i++) {sum += huge_arr[i];
}

虽然整个数组多达4GB,但具体到每一步我们一次只能操作一个元素,就像这里的:

sum += huge_arr[i];

这行代码翻译成机器指令可能是这样的,我们假设此时i为100:

load $r0 100($r2)
add $r1 $r1 $r0

(注意,实际当中编译器不会傻傻的生成100这样的常数,这里代码仅用来方便讲解问题)。

第一行指令中数组首地址存放在寄存器r2中,100($r2)表示数组首地址+100,这样我们就能得到huge_arr[100]的地址了,然后将该地址中的值利用load指令加载到寄存器r0中。第二行就简单多了,r1寄存器中保存的是sum的值,该行指令执行过后r1中的值就已经加上了huge_arr[100]。现在你应该能看出来了吧,虽然我们不能把整个数组加载到寄存器供CPU计算,但这其实是没有必要的,因为我们一次只能操作数组中的一个元素,我们只需要把这一个元素加载到寄存器就足矣了

对于其它复杂的数据结构也是同样的道理,无论多么复杂的数据,代码对其一次的操作都是很简单很微小的,这一微小的操作使用的基本元素都可以通过内存读写指令加载到寄存器,修改完后再写回内存。

编译器

现在你应该知道了为什么CPU内部那么少的寄存器能操作内存中庞大的数据结构,实际上由于内存中的数据要远大于CPU寄存器的容量,因此编译器必须精心挑选,好让那些经常使用的数据放到寄存器中的时间更长一点,这样可以减少内存读写次数。在上面的示例中,r2寄存器保存的是huge_arr这个数组在内存中的起始地址,那么这个数据应该放到寄存器中,因为后续遍历到的每一个元素都要用到该地址,这项工作就是编译器来完成的。编译器把那些经常使用的数据放到寄存器,剩下的放到内存中,然后利用内存读写指令在寄存器和内存之间来回搬运数据。

总结

通过本文不难发现,实际上我们没有必要一次性把整个数据全部装到CPU寄存器中,而是用到哪些才装载哪些。在最细粒度的操作中,依赖的操作数都可以直接加载到内存,这通常是由内存读写机器指令来完成的。

原文作者: 码农的荒岛求生

这篇关于一文解析数据结构是如何装入 CPU 寄存器的?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/497487

相关文章

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3

Java MCP 的鉴权深度解析

《JavaMCP的鉴权深度解析》文章介绍JavaMCP鉴权的实现方式,指出客户端可通过queryString、header或env传递鉴权信息,服务器端支持工具单独鉴权、过滤器集中鉴权及启动时鉴权... 目录一、MCP Client 侧(负责传递,比较简单)(1)常见的 mcpServers json 配置

从原理到实战解析Java Stream 的并行流性能优化

《从原理到实战解析JavaStream的并行流性能优化》本文给大家介绍JavaStream的并行流性能优化:从原理到实战的全攻略,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的... 目录一、并行流的核心原理与适用场景二、性能优化的核心策略1. 合理设置并行度:打破默认阈值2. 避免装箱

Maven中生命周期深度解析与实战指南

《Maven中生命周期深度解析与实战指南》这篇文章主要为大家详细介绍了Maven生命周期实战指南,包含核心概念、阶段详解、SpringBoot特化场景及企业级实践建议,希望对大家有一定的帮助... 目录一、Maven 生命周期哲学二、default生命周期核心阶段详解(高频使用)三、clean生命周期核心阶

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Java Scanner类解析与实战教程

《JavaScanner类解析与实战教程》JavaScanner类(java.util包)是文本输入解析工具,支持基本类型和字符串读取,基于Readable接口与正则分隔符实现,适用于控制台、文件输... 目录一、核心设计与工作原理1.底层依赖2.解析机制A.核心逻辑基于分隔符(delimiter)和模式匹

Java+AI驱动实现PDF文件数据提取与解析

《Java+AI驱动实现PDF文件数据提取与解析》本文将和大家分享一套基于AI的体检报告智能评估方案,详细介绍从PDF上传、内容提取到AI分析、数据存储的全流程自动化实现方法,感兴趣的可以了解下... 目录一、核心流程:从上传到评估的完整链路二、第一步:解析 PDF,提取体检报告内容1. 引入依赖2. 封装

SysMain服务可以关吗? 解决SysMain服务导致的高CPU使用率问题

《SysMain服务可以关吗?解决SysMain服务导致的高CPU使用率问题》SysMain服务是超级预读取,该服务会记录您打开应用程序的模式,并预先将它们加载到内存中以节省时间,但它可能占用大量... 在使用电脑的过程中,CPU使用率居高不下是许多用户都遇到过的问题,其中名为SysMain的服务往往是罪魁