Go语言内存管理

本章节,就来学习一下go语言的内存模型,看一下内存的分配,存储都是如何实现的,与此同时,在正式开始今天的主题之前,首先先来学习操作系统基于这一方面的内容,来看看是如何管理内存的吧

本章及节内容参考小徐先生和刘丹冰老师的内容,加上一些个人注解,go语言版本是1.24.1,由于文章的内容是在语雀,这里就附上我的语雀链接,方便大家更好的查看https://www.yuque.com/chenxiangyang-n12yg/pli7v5/eadkhxswioy3w746?singleDoc# 《内存管理》

一.内存管理机制

1.1 操作系统的存储模型

在学习go语言的内存模型之前,先来熟悉一下操作系统金典的多级存储模型,如上图所示,差不多大家都清楚这一些东西。

1.2 虚拟内存和物理内存

虚拟内存:是一种内存管理技术,它为每一个进程提供了一个非常大的,一致的和独立且连续的地址空间。虚拟内存通过地址翻译硬件和页表,提供了内存保护和简化内存管理的能力,将物理内存和磁盘空间结合管理

它的作用如下:

  • 在用户与硬件间添加中间代理层(没有什么是加一个中间层解决不了的)
  • 优化用户体验(进程感知到获得的内存空间是“连续”的)
  • “放大”可用内存(虚拟内存可以由物理内存+磁盘补足,并根据冷热动态置换,用户无感知)

物理内存:是指计算机硬件实际的内存,即RAM(随机存取存储器)直接和CPU交互,用于临时存储运行中的程序和数据。

特点:

  • 有限性:容量受硬件限制(如8GB、16GB等)
  • 高速访问:读写速度远超磁盘,但断电后数据丢失。
  • 直接寻址:CPU通过物理地址直接访问内存单元。

1.3 分页管理

操作系统中通常会将虚拟内存和物理内存切割成固定的尺寸,于虚拟内存而言叫作“”,于物理内存而言叫作“”,原因及要点如下:

• 提高内存空间利用(以页为粒度后,消灭了不稳定的外部碎片,取而代之的是相对可控的内部碎片)

• 提高内外存交换效率(更细的粒度带来了更高的灵活度)

• 与虚拟内存机制呼应,便于建立虚拟地址->物理地址的映射关系(聚合映射关系的数据结构,称为页表)

• linux 页/帧的大小固定,为 4KB(这实际是由实践推动的经验值,太粗会增加碎片率,太细会增加分配频率影响效率)

二.Golang的内存管理机制

前面的一小节对操作系统的内存模型,做了一个简单的介绍,如果想要了解更多这一方面的知识可以上网查询一下,下面将迈入正题,来看看golang世界中内存模型的设计

2.1 golang的内存管理的模型图

首先先来看一下go语言的内存模型长什么样,然后我们在进一步去了解

看完它的大致样子之后,我们来介绍介绍这些东西都是什么:

  • mheap:全局的内存起源,访问要加全局锁
  • mcentral:每种对象大小规格(全局共划分为 68 种)对应的缓存,锁的粒度也仅限于同一种规格以内
  • mcache:每个 P(正是 GMP 中的 P)持有一份的内存缓存,访问时无锁

这些内容,我们会在后续详细展开说明

Golang内存模型设计的几个核心要点

  1. 以时间换空间,一次缓存,多次使用

首先我们要做到,每次向操作系统申请空间的操作都是很重的,那不妨一次性咱多要一点,以备后续使用。

Golang中的mheap正是基于这种思想,产生的数据结构。接下来我们从两个不同的角度去看这个堆:

  • 对于操作系统而言:这个堆就是用户进程中缓存的内存。
  • 对于Go进程内部:堆则是所有对象的起源。

  1. 多级缓存,以实现无/细锁化

为什么要设置多级缓存?

实际上,堆是Go运行时中最大的临界内存资源,这意味着每次存取都要加锁,在性能层面上看是一件非常可怕的事情.

正是为了解决这个问题,Golang在堆mheap上,做了更加细微的处理,建立了 mcentral、mcache。

通过设置不同规格的mcentral,从而实现对不同大小的对象进行管理,分开加锁,避免了直接对mheap直接加锁导致的性能问题。

  1. 多级规格,提高利用率

首先先来了解一下page和mspan这两个概念:

  • page:最小的存储单元

Golang 借鉴操作系统分页管理的思想,每个最小的存储单元也称之为页 page,但大小为 8 KB

  • mspan:最小的管理单元

mspan 大小为 page 的整数倍,且从 8B 到 80 KB 被划分为 67 种不同的规格,分配对象时,会根据大小映射到不同规格的 mspan,从中获取空间,实际上有一个更大的规格,所以说是68种,后续会在涉及

为什么要将mspan划分为多个规格呢?

  1. 有了规格化,便产生了等级制度,有了等级,才支持mcentral实现细琐化
  2. 消除了外部碎片,但是不能避免内部碎片,宏观上提高了整体空间的利用率

可以看一下整体的架构图,我们后续会不断对细节进行一个补充的。

2.2 内存单元 mspan

mspan,其实就是类似双向链表中的一个结点,我们来看下mspan有那些特点:

  • mspan 是 Golang 内存管理的最小单元
  • mspan 大小是 page 的整数倍(Go 中的 page 大小为 8KB),且内部的页是连续的(至少在虚拟内存的视角中是这样)
  • 每个 mspan 根据空间大小以及面向分配对象的大小,会被划分为不同的等级(2.2小节展开)
  • 同等级的 mspan 会从属同一个 mcentral,最终会被组织成链表,因此带有前后指针(prev、next)
  • 由于同等级的 mspan 内聚于同一个 mcentral,所以会基于同一把互斥锁管理
  • mspan 会基于 bitMap 辅助快速找到空闲内存块(块大小为对应等级下的 object 大小),此时需要使用到 Ctz64 算法.

type mspan struct {// 标识前后节点的指针 next *mspan     prev *mspan    // ...// 起始地址startAddr uintptr // 包含几页,页是连续的npages    uintptr // 标识此前的位置都已被占用 freeindex uintptr// 最多可以存放多少个 objectnelems uintptr // number of object in the span.// bitmap 每个 bit 对应一个 object 块,标识该块是否已被占用allocCache uint64// ...// 标识 mspan 等级,包含 class 和 noscan 两部分信息spanclass             spanClass    // ...
}

2.3 内存单元等级 spanClass

mspan根据空间大小和面向分配对象的大小,被划分为67种不同的等级(1-67,实际上还有一种隐藏的0级,用于处理更大的对象,上不封顶)

实际上对于不同规格的等级,都会产生对应的浪费,简单说一下这个浪费是怎么造成的,比如:

此时我们创建了一个对象,它的大小是1b,但是最小的mspan是8b,他不会分配1b大小的mspan,所以就会导致7b被浪费掉,比如这样:

除了上面谈及的根据大小确定的 mspan 等级外,每个 object 还有一个重要的属性叫做 noscan,标识了 object 是否包含指针,在 gc 时是否需要展开标记.

(这里做一个简单的拓展:Go的垃圾回收器GC需要找到存活的数据,为此GC会从根对象比如栈,全局变量,出发,遍历所有可以到达的对象,过程是标记所有存活对象,检查每个对象的内部字段,递归标记其引用的对象,如果对象不包含指针,扫描它就是多余,反而浪费CPU时间,所有noscan作用就是告诉GC,这家伙没有指针,跳过扫描。)

在 Golang 中,会将 span class + noscan 两部分信息组装成一个 uint8,形成完整的 spanClass 标识. 8 个 bit 中,高 7 位表示了上表的 span 等级(总共 67 + 1 个等级,8 个 bit 足够用了),最低位表示 nocan 信息.

2.4 线程缓存 mcache

要点:

(1)mcache 是每个 P 独有的缓存,因此交互无锁

(2)mcache 将每种 spanClass 等级的 mspan 各缓存了一个,总数为 2(nocan 维度) * 68(大小维度)= 136

(3)mcache 中还有一个为对象分配器 tiny allocator,用于处理小于 16B 对象的内存分配

const numSpanClasses = 136
type mcache struct {// 微对象分配器相关tiny       uintptrtinyoffset uintptrtinyAllocs uintptr// mcache 中缓存的 mspan,每种 spanClass 各一个alloc [numSpanClasses]*mspan // ...
}

这里可能大家大家可能会疑惑为什么是136个大小,而不是68个,从图中不难看出它分为了scan和noscan两种,他表示是否含有指针,在spanclass里说过,这个指针就是告诉GC要不要扫描,如果我直接在他的上层直接将其分开,也就是有scan在一块,没有的在另外一块,这样就可以提高GC的效率,不用每次都去扫描mspan,判断有无指针了。

mcache具体的申请形式

通过alloc区分出了136个指针,指向对应的mspan,用完时,会重新向mcentral申请,并更新alloc中的指针哦。

2.5 中心缓存 mcentral

要点:

(1)每个 mcentral 对应一种 spanClass

(2)每个 mcentral 下聚合了该 spanClass 下的 mspan

(3)mcentral 下的 mspan 分为两个链表,分别为有空间 mspan 链表 partial 和满空间 mspan 链表 full

(4)每个 mcentral 都有一把属于自己的锁

type mcentral struct {_         sys.NotInHeap// 对应的 spanClassspanclass spanClass// 有空位的 mspan 集合,数组长度为 2 是用于抗一轮 GCpartial [2]spanSet // 无空位的 mspan 集合full    [2]spanSet 
}

简单的说一下这两个链表的作用:

内存分配的时候,mcache从partical链表获取mspan,当其mcache需要新的mspan时(例如mspan已满),会向mcentral请求。mcentral会优先从partial链表中取出一个mspan,返回给mcache

对于这个已满的mspan会归还到mcache的full链表。

当 GC 扫描并释放某些对象时,原本在 full 链表中的 mspan 可能重新获得空闲对象。此时,这些 mspan 会从 full 链表移回 partial 链表

2.6 全局堆缓存 mheap

  • 对于 Golang 上层应用而言,堆是操作系统虚拟内存的抽象
  • 以页(8KB)为单位,作为最小内存存储单元
  • 负责将连续页组装成 mspan
  • 全局内存基于 bitMap 标识页使用情况,每个 bit 对应一页,为 0 则自由,为 1 则已被 mspan 组装(也就是说全部的页是否使用,都会在一个巨大的bitmap里面标记)
  • 通过 heapArena 聚合页,记录了页到 mspan 的映射信息(后续2.8小节展开)
  • 建立空闲页基数树索引 radix tree index,辅助快速寻找空闲页(后续2.7小节展开)
  • 是 mcentral 的持有者,持有所有 spanClass 下的 mcentral,作为自身的缓存
  • 内存不够时,向操作系统申请,申请单位为 heapArena(64M)

内存的包含形式

这里做一个简简单单的说明哈,可能之前的图会误导大家,就是mcache和mcentral都是mheap的一部分哦,可不是分开的。

type mheap struct {// 堆的全局锁lock mutex// 空闲页分配器,底层是多棵基数树组成的索引,每棵树对应 16 GB 内存空间pages pageAlloc // 记录了所有的 mspan. 需要知道,所有 mspan 都是经由 mheap,使用连续空闲页组装生成的allspans []*mspan// heapAreana 数组,64 位系统下,二维数组容量为 [1][2^22]// 每个 heapArena 大小 64M,因此理论上,Golang 堆上限为 2^22*64M = 256T// 通过这个来间接管理bitmaparenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena// ...// 多个 mcentral,总个数为 spanClass 的个数central [numSpanClasses]struct {mcentral mcentral// 用于内存地址对齐pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte}// ...
}

2.7 空闲页索引 pageAlloc

这一小节的内容主要讲解空闲页寻址分配的计数树索引有关的内容,可能比较苦涩难懂,就做一个简单的介绍吧,gogogo出发咯。

pageAlloc的底层是基数树组成的一个索引,它是Go语言高效管理大内存空间的核心机制。尤其是在空闲页的快速寻址和分配中。这一设计在 Google 的提案文档(Scaling the Page Allocator)中有详细描述。

接下来我会做一个简单的介绍

为什么要使用基数树(radix tree),而不采用传统的bitmap呢?

  • 主要就是现代程序的内存都是TB级别,传统的bitmap扫描算法时间复杂度为O(N),无法满足需求
  • 通过基数树可以降低时间复杂度为O(1)或O(logN)

该数据结构的一些必备知识点

  • 一颗基数树的大小为16GB,聚合了内存中各页的使用情况,帮助mheap快速找到指定长度的连续的空闲页所在的位置
  • mheap内存的上限为256TB,所以它不是单单由一颗基数树组成,而是森林,一共有2的14次方棵。
  • mheap会对被使用过的页进行标记,1表示已使用,0表示空闲

基数树的节点设置

基数树中,每个节点称之为 PallocSum,是一个 uint64 类型,体现了索引的聚合信息,包含以下四部分:

  • start:最右侧 21 个 bit,标识了当前节点映射的 bitMap 范围中首端有多少个连续的 0 bit(空闲页),称之为 start;
  • max:中间 21 个 bit,标识了当前节点映射的 bitMap 范围中最多有多少个连续的 0 bit(空闲页),称之为 max;
  • end:左侧 21 个 bit,标识了当前节点映射的 bitMap 范围中最末端有多少个连续的 0 bit(空闲页),称之为 end.
  • 最左侧一个 bit,弃置不用

(这样做的目的就是为了避免内存碎片化,这里的start和end只关心首端和尾端是不是0,如果不是0,那么他们的结果就是0,只有当首端和尾端是0的时候才会开始计算)

start和end主要用于为父节点提供合并之后的max是否发生改变,每个PallocSum下都有8个,相邻的两个节点的strat和end可能会合成比原本更大的一个max,所以需要记录,后续会讲到。

父子节点的关系

  • 每个父 pallocSum 有 8 个子 pallocSum
  • 根 pallocSum 总览全局,映射的 bitMap 范围为全局的 16 GB 空间(其 max 最大值为 2^21,因此总空间大小为 2^21*8KB=16GB);
  • 从首层向下是一个依次八等分的过程,每一个 pallocSum 映射其父节点 bitMap 范围的八分之一,因此第二层 pallocSum 的 bitMap 范围为 16GB/8 = 2GB,以此类推,第五层节点的范围为 16GB / (8^4) = 4 MB,已经很小
  • 聚合信息时,自底向上. 每个父 pallocSum 聚合 8 个子 pallocSum 的 start、max、end 信息,形成自己的信息,直到根 pallocSum,坐拥全局 16 GB 的 start、max、end 信息
  • mheap 寻页时,自顶向下. 对于遍历到的每个 pallocSum,先看起 start 是否符合,是则寻页成功;再看 max 是否符合,是则进入其下层孩子 pallocSum 中进一步寻访;最后看 end 和下一个同辈 pallocSum 的 start 聚合后是否满足,是则寻页成功.

2.8 heapArena

  • 每个 heapArena 包含 8192 个页,大小为 8192 * 8KB = 64 MB
  • heapArena 记录了页到 mspan 的映射. 因为 GC 时,通过地址偏移找到页很方便,但找到其所属的 mspan 不容易. 因此需要通过这个映射信息进行辅助.
  • heapArena 是 mheap 向操作系统申请内存的单位(64MB)

三.分配流程流程

下面来串联 Golang 中分配对象的流程,不论是以下哪种方式,最终都会殊途同归步入 mallocgc 方法中,并且根据 3.1 小节中的策略执行分配流程:

  • new(T)
  • &T{}
  • make(xxxx)

3.1 分配对象

golang中,会依据object的大小,分为三类对象,从而对其进行内存的分配,接下来就来看看吧:

不同类型的对象,会有不同的分配策略,这些内容在mallocgc方法中都有体现:

核心流程类似于读多级缓存的过程,自上而下,每一步只要成功就立即返回,若失败,则由下层方法兜底。

对于微对象的分配流程:

(1)从 P 专属 mcache 的 tiny 分配器取内存(无锁)

(2)根据所属的 spanClass,从 P 专属 mcache 缓存的 mspan 中取内存(无锁)

(3)根据所属的 spanClass 从对应的 mcentral 中取 mspan 填充到 mcache,然后从 mspan 中取内存(spanClass 粒度锁)

(4)根据所属的 spanClass,从 mheap 的页分配器 pageAlloc 取得足够数量空闲页组装成 mspan 填充到 mcache,然后从 mspan 中取内存(全局锁)

(5)mheap 向操作系统申请内存,更新页分配器的索引信息,然后重复(4)

对于小对象的分配流程是跳过(1)步,执行上述流程的(2)-(5)步;

对于大对象的分配流程是跳过(1)-(3)步,执行上述流程的(4)-(5)步.

3.2 分配流程

(1)微小对象分配(Tiny Allocator)

  • 条件:对象大小 < 16 B 不包含指针(避免影响垃圾回收扫描)。
  • 流程
    1. 尝试合并:若当前 mcache.tiny 块剩余空间足够,直接在当前 tiny 块分配(无锁)。
    2. 申请新块:若空间不足,向 mheap 申请一个新的 16 B 内存块(可能触发垃圾回收)。
  • 优化:通过合并多个微小对象减少内存碎片。

(2)小对象分配(mcache -> mcentral)

  • 步骤
    1. 确定 Size Class:根据对象大小匹配预定义的 Size Class(Go 有 67 个预定义规格,如 8 B、16 B、32 B ... 32 KB)。
    2. 从 mcache 获取 Span
      • mcache 为每个 P(处理器)维护本地缓存 Span,包含对应 Size Class 的空闲对象链表。
      • 若当前 Span 有空闲对象,直接分配(无锁)。
    1. mcache 向 mcentral 申请新 Span
      • mcache 的 Span 耗尽,向对应的 mcentral 请求新的 Span。
      • mcentral 维护全局 Span 列表(nonemptyempty 链表),通过锁保证并发安全。
    1. mcentral 向 mheap 扩容
      • mcentral 无可用 Span,向 mheap 申请新的 Span(通常为 8 KB 的倍数)。
      • mheap 通过基数树查找连续空闲页,切割为所需大小的 Span 返回给 mcentral

(3)大对象分配(直接通过 mheap)

  • 触发条件:对象大小 > 32 KB。
  • 流程
    1. 计算所需页数:将对象大小转换为页数(1 页 = 8 KB)。
    2. 基数树查找空闲页
      • mheap 使用基数树快速定位满足需求的连续空闲页。
      • 基数树的 max 字段帮助跳过无法满足需求的子树,start/end 处理跨子树合并。
    1. 分配并更新元数据
      • 标记位图中对应页为已占用。
      • 更新基数树节点的 start/max/end 摘要信息。
    1. 直接返回内存地址:无需切割 Span,直接将连续页映射给大对象。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【docker】启动临时MongoDB容器、挂载数据卷运行数据库服务,并通过备份文件恢复MongoDB数据库备份数据

‌启动临时 MongoDB 容器、挂载数据卷运行数据库服务&#xff0c;并通过备份文件恢复数据 1.命令分解与功能说明1.1.启动一个临时 MongoDB 容器‌&#xff0c;并进入交互式终端&#xff08;1&#xff09;执行命令&#xff08;2&#xff09;实现功能‌&#xff08;3&#xff09;…

【最新 MCP 战神手册 08】工具使用详解:实现 AI 行动

文章目录 1. 开始啦!2. 第一部分:设计高效且安全的工具3. 第二部分:定义工具蓝图——参数、输出与约束条件4. 第三部分:弥合差距:LLM 兼容性(函数调用)5. 第四部分:实施与测试的最佳实践1. 开始啦! 在前几章中,我们将工具介绍为 AI 模型在 MCP 客户端引导下向 MCP 服…

介绍 IntelliJ IDEA 快捷键操作

IntelliJ IDEA 快捷键操作 1. 编辑与导航2. 查找与替换3. 调试与运行4. 导航与视图5. 重构与生成6. 高级快捷键&#xff08;提高效率&#xff09;注意事项 IntelliJ IDEA 是一款功能强大的集成开发环境&#xff0c;掌握其常用快捷键可以显著提升开发效率。但是有些小伙伴并不清…

Javascript 中作用域的理解?

一、作用域的类型 1. 全局作用域&#xff08;公司大门外&#xff09; 范围&#xff1a;整个 JavaScript 文件变量&#xff1a;像贴在公告栏上的信息&#xff0c;所有人可见例子&#xff1a;const companyName "阿里"; // 全局变量&#xff0c;任何地方都能访问 fu…

Leetcode刷题记录22——滑动窗口最大值

题源&#xff1a;https://leetcode.cn/problems/sliding-window-maximum/description/?envTypestudy-plan-v2&envIdtop-100-liked 题目描述&#xff1a; 思路一&#xff1a; 暴力遍历法&#xff0c;通过一个长度为k的滑动窗口遍历nums&#xff0c;将其中最大的数依次记…

Apache Flink的架构设计与运行流程说明

在大数据领域&#xff0c;实时计算的重要性随着业务需求的爆发式增长愈发凸显。从电商的实时销量监控到金融的高频交易风控&#xff0c;从物联网设备的实时告警到社交平台的热点追踪&#xff0c;企业对“秒级甚至毫秒级”数据处理能力的需求已成为刚需。在众多实时计算框架中&a…

经典算法 最长单调递增子序列

最长单调递增子序列 问题描述 找出由n个数组成的序列的最长单调递增子序列。 示例输入 9 2 1 5 3 6 4 8 9 7示例输出 5示例输入 6 5 6 7 1 2 8示例输出 4c代码(动态规划 O(n^2)) #include<bits/stdc.h>using namespace std;int main() {int n, ans 0;cin >&g…

【语法】C++继承中遇到的问题及解决方法

目录 1.子类构造函数中初始化父类成员 2.子类显式调用父类的析构函数 第一种说法&#xff1a;重定义 反驳&#xff1a; 第二种说法&#xff1a;operator~ 3.因编译器版本过低而出现错误 贴主在学习C的继承时&#xff0c;遇到了很多问题&#xff0c;觉得很变态&#xff0c…

前缀和 后缀和 --- 寻找数组的中心下标

题目链接 寻找数组的中心下标 给你一个整数数组 nums &#xff0c;请计算数组的 中心下标 。 数组 中心下标 是数组的一个下标&#xff0c;其左侧所有元素相加的和等于右侧所有元素相加的和。 如果中心下标位于数组最左端&#xff0c;那么左侧数之和视为 0 &#xff0c;因为…

NVIDIA --- 端到端自动驾驶

前言 参加了NVIDIA 高级辅助驾驶开发者实验室的活动&#xff0c;本次活动基于 NVIDIA 汽车行业的端到端解决方案——DRIVE AGX™ 平台&#xff0c;实现高级别智能和安全性的软硬件开发工具和 AV 基础设施。并且NVIDIA自动驾驶实验室推出了一系列自动驾驶算法最新的前沿研究视频…

SQL实战:03之SQL中的递归查询

文章目录 概述SQL 中的递归实现题目一:分析组织层级题解题目二:树节点求解题解步骤一&#xff1a;通过递归查询出每个节点的上级节点和下级节点分布步骤二&#xff1a;分组统计 概述 最近刷题时遇到了一道需要根据组织层级来统计各个层级的一些数据&#xff0c;当时碰到时的第…

MySQL 语法与基础完全指南

MySQL 是最流行的开源关系型数据库管理系统之一&#xff0c;广泛应用于 Web 应用程序开发。本文将全面介绍 MySQL 的基础知识和完整语法结构。 一、MySQL 基础概念 1. 数据库基本术语 数据库(Database): 存储数据的集合 表(Table): 数据以表格形式组织 列(Column): 表中的一…

【Sqlalchemy Model转换成Pydantic Model示例】

【Sqlalchemy Model转换成Pydantic Model示例】 由于Sqlalchemy和Pydantic的模型字段类型可能有差异, 所以需要一个通用的装换类 def sqlalchemy_to_pydantic_v2(sqlalchemy_model, pydantic_model):"""通用函数&#xff0c;将 SQLAlchemy 模型实例转换为 Pyd…

2025年欧洲西南部大停电

2025年4月28日&#xff0c;欧洲西南部出现大规模停电&#xff0c;西班牙、葡萄牙和法国南部均受到影响。有报道指出停电可能与 欧洲电网出现问题有关&#xff0c;但最终原因尚未确定。由于停电&#xff0c;上述地区的交通和通信服务均受到严重影响&#xff0c;交通信号灯停止工…

Java EE初阶——计算机是如何工作的

1. cpu 冯诺依曼体系&#xff08;Von Neumann Architecture&#xff09; • CPU 中央处理器: 进⾏算术运算和逻辑判断. • 存储器: 分为外存和内存, ⽤于存储数据(使⽤⼆进制⽅式存储) • 输⼊设备: ⽤⼾给计算机发号施令的设备. • 输出设备: 计算机个⽤⼾汇报结果的设…

飞鸟游戏模拟器 1.0.3 | 完全免费无广告,内置大量经典童年游戏,重温美好回忆

飞鸟游戏模拟器是一款专为安卓用户设计的免费游戏模拟器&#xff0c;内置了大量经典的童年游戏。该模拟器拥有丰富的游戏资源&#xff0c;目前已有约20,000款游戏&#xff0c;包括多种类型如冒险、动作、角色扮演等。用户可以直接搜索查找想要玩的游戏进行下载并启动。游戏库中…

网络爬取需谨慎:警惕迷宫陷阱

一、技术背景:网络爬虫与数据保护的博弈升级 1. 问题根源:AI训练数据爬取的无序性 数据需求爆炸:GPT-4、Gemini等大模型依赖数万亿网页数据训练,但大量爬虫无视网站的robots.txt协议(非法律强制),未经许可抓取内容(如新闻、学术论文、代码),引发版权争议(如OpenAI被…

Qwen3简介:大型语言模型的革命

Qwen3简介&#xff1a;大型语言模型的革命 Qwen系列语言模型的最新发布——Qwen3&#xff0c;标志着人工智能&#xff08;AI&#xff09;技术的一次重大飞跃。基于前代版本的成功&#xff0c;Qwen3在架构、推理能力和多项先进功能上都取得了显著提升&#xff0c;正在重新定义大…

MODSIM选型指南:汽车与航空航天企业如何选择仿真平台

1. 引言 在竞争激烈的汽车与航空航天领域&#xff0c;仿真技术已成为产品研发不可或缺的环节。通过在设计阶段验证概念并优化性能&#xff0c;仿真平台能有效缩短开发周期并降低物理样机制作成本。 MODSIM&#xff08;建模与仿真&#xff09;作为达索系统3DEXPERIENCE平台的核…

linux 内核 debugfs 使用介绍

一&#xff1a;概述 debugfs 是 Linux 内核提供的一个特殊的虚拟文件系统&#xff0c;用于 暴露内核模块&#xff08;如驱动&#xff09;内部的调试信息或控制接口&#xff0c;供开发者、调试人员实时查看和排查问题。即 debugfs 就是一个“调试专用的 /proc 或 /sys”&#xf…