冒险和预测

前言

大家好我是jiantaoyab,这是我所总结作为学习的笔记第十篇,在这里分享给大家,还有一些书籍《深入理解计算机系统》,《计算机体系结构:量化研究方法》,这篇文章讲冒险和预测

流水线设计需要解决的三大冒险,分别是结构冒险(Structural Hazard)、数据冒险(Data Hazard)以及控制冒险(Control Hazard)。

结构冒险

本质上是一个硬件层面的资源竞争问题,也就是一个硬件电路层面的问题

CPU 在同一个时钟周期,同时在运行两条计算机指令的不同阶段。但是这两个不同的阶段,可能会用到同样的硬件电路

image-20240310152714269

可以看到,在第 1 条指令执行到访存(MEM)阶段的时候,流水线里的第 4 条指令,在执行取指令(Fetch)的操作。访存和取指令,都要进行内存数据的读取。我们的内存,只有一个地址译码器的作为地址输入,那就只能在一个时钟周期里面读取一条数据,没办法同时执行第 1 条指令的读取内存数据和第 4 条指令的读取指令代码

怎么解决呢?

增加资源,把我们的内存分成两部分,让它们各有各的地址译码器。这两部分分别是存放指令的程序内存存放数据的数据内存

这种结构又叫哈佛结构,和我们熟知的冯·诺依曼体系结构不太一样

image-20240310152912954

这种方案的不足之处是对程序指令和数据需要的内存空间,我们就没有办法根据实际的应用去动态分配了。虽然解决了资源冲突的问题,但是也失去了灵活性

image-20240310153023164

所以,现代的 CPU 虽然没有在内存层面进行对应的拆分,而是在 CPU 内部的高速缓存部分进行了区分,把高速缓存分成了指令缓存(Instruction Cache)和数据缓存(Data Cache)两部分。

内存的访问速度远比 CPU 的速度要慢,所以现代的 CPU 并不会直接读取主内存。它会从主内存把指令和数据加载到高速缓存中,这样后续的访问都是访问高速缓存。而指令缓存和数据缓存的拆分,使得我们的 CPU 在进行数据访问和取指令的时候,不会再发生资源冲突的问题了。

数据冒险

是程序逻辑里面的问题,其实就是同时在执行的多个指令之间,有数据依赖的情况。这些数据依赖,我们可以分成三大类,分别是先写后读(Read After Write,RAW)、先读后写(Write After Read,WAR)和写后再写(Write After Write,WAW)。

先写后读

image-20240310155513907

image-20240310155236323

可以看到,在内存地址为 12 的机器码,0x2添加到0x4对应的内存地址里面。然后,在紧接着的内存地址为 16 的机器码,我们又要从 0x4内存地址里面,把数据写入到 eax 这个寄存器里。

所以,我们需要保证,在内存地址为 16 的指令读取 0x4 里面的值之前,内存地址 12 的指令写入到 0x4 的操作必须完成。这就是先写后读所面临的数据依赖。如果这个顺序保证不了,我们的程序就会出错。

这个先写后读的依赖关系,我们一般被称之为数据依赖,也就是 Data Dependency。

先读后写

image-20240310155623386

image-20240310155611797

在内存地址为 15 的汇编指令里,我们要把 eax 寄存器里面的值读出来,再加到 0x4 的内存地址里。接着在内存地址为 18 的汇编指令里,我们要再写入更新 eax 寄存器里面。

如果我们在内存地址 18 的 eax 的写入先完成了,在内存地址为 15 的代码里面取出 eax 才发生,我们的程序计算就会出错。这里,我们同样要保障对于 eax 的先读后写的操作顺序。

先读后写的依赖,一般被叫作反依赖,也就是 Anti-Dependency。

写后再写

image-20240310160002607

image-20240310155939340

内存地址 4 所在的指令和内存地址 b 所在的指令,都是将对应的数据写入到 0x4 的内存地址里面。如果内存地址 b 的指令在内存地址 4 的指令之后写入。那么这些指令完成之后,0x4 里的数据就是错误的。这就会导致后续需要使用这个内存地址里的数据指令,没有办法拿到正确的值。所以,我们也需要保障内存地址 4 的指令的写入,在内存地址 b 的指令的写入之前完成。

这个写后再写的依赖,一般被叫作输出依赖,也就是 Output Dependency。

流水线停顿

对于同一个寄存器或者内存地址的操作,都有明确强制的顺序要求。而这个顺序操作的要求,也为我们使用流水线带来了很大的挑战。因为流水线架构的核心,就是在前一个指令还没有结束的时候,后面的指令就要开始执行

可以用流水线停顿来解决,如果我们发现了后面执行的指令,会对前面执行的指令有数据层面的依赖关系,那最简单的办法就是“再等等”。

我们在进行指令译码的时候,会拿到对应指令所需要访问的寄存器和内存地址。所以,在这个时候,我们能够判断出来,这个指令是否会触发数据冒险。如果会触发数据冒险,我们就可以决定,让整个流水线停顿一个或者多个周期。

image-20240310160606077

其实,我们并没有办法真的停顿下来。流水线的每一个操作步骤必须要干点儿事情。所以,在实践过程中,我们并不是让流水线停下来,而是在执行后面的操作步骤前面,插入一个 NOP 操作,也就是执行一个其实什么都不干的操作

image-20240310160645242

小总结:解决结构冒险和数据冒险

  1. 增加资源,通过添加指令缓存和数据缓存,让我们对于指令和数据的访问可以同时进行。这个办法帮助 CPU 解决了取指令和访问数据之间的资源冲突
  2. 进行等待。通过插入 NOP 这样的无效指令,等待之前的指令完成。这样我们就能解决不同指令之间的数据依赖问题。

但是这2种解决方法并不是最优的

NOP 操作和指令对齐

在 MIPS 的体系结构下,不同类型的指令,会在流水线的不同阶段进行不同的操作

image-20240310161222355

image-20240310161229966

以 MIPS 的 LOAD,这样从内存里读取数据到寄存器的指令为例,来仔细看看,它需要经历的 5 个完整的流水线。STORE 这样从寄存器往内存里写数据的指令,不需要有写回寄存器的操作,也就是没有数据写回的流水线阶段。至于像 ADD 和 SUB 这样的加减法指令,所有操作都在寄存器完成,所以没有实际的内存访问(MEM)操作。

image-20240310161257134

有些指令没有对应的流水线阶段,但是我们并不能跳过对应的阶段直接执行下一阶段。不然,如果我们先后执行一条 LOAD 指令和一条 ADD 指令,就会发生 LOAD 指令的 WB 阶段和 ADD 指令的 WB 阶段,在同一个时钟周期发生。这样,相当于触发了一个结构冒险事件,产生了资源竞争

image-20240310161321449

所以,在实践当中,各个指令不需要的阶段,并不会直接跳过,而是会运行一次 NOP 操作。通过插入一个 NOP 操作,我们可以使后一条指令的每一个 Stage,一定不和前一条指令的同 Stage 在一个时钟周期执行。这样,就不会发生先后两个指令,在同一时钟周期竞争相同的资源,产生结构冒险了

image-20240310161353236

操作数前推

通过 NOP 操作进行对齐,我们在流水线里,就不会遇到资源竞争产生的结构冒险问题了。但是,插入过多的 NOP 操作,意味着我们的 CPU 总是在空转。

add $t0, $s2,$s1
add $s2, $s1,$t0

后一条的 add 指令,依赖寄存器 t0 里的值。而 t0 里面的值,又来自于前一条指令的计算结果。所以后一条指令,需要等待前一条指令的数据写回阶段完成之后,才能执行。我们遇到了一个数据依赖类型的冒险。所以要在第二条指令的译码阶段之后,插入对应的 NOP 指令,直到前一天指令的数据写回完成之后,才能继续执行。

这样的方案,虽然解决了数据冒险的问题,但是也浪费了两个时钟周期,运行了两次空转的 NOP 操作

image-20240310162503774

其实我们第二条指令的执行,未必要等待第一条指令写回完成,才能进行。如果我们第一条指令的执行结果,能够直接传输给第二条指令的执行阶段,作为输入,那我们的第二条指令,就不用再从寄存器里面,把数据再单独读出来一次,才来执行代码。

可以在第一条指令的执行阶段完成之后,直接将结果数据传输给到下一条指令的 ALU。然后,下一条指令不需要再插入两个 NOP 阶段,就可以继续正常走到执行阶段。

image-20240310162636214

这就是操作数前推,在硬件层面上,需要再单独拉一根信号传输的线路出来,使得 ALU 的计算结果,能够重新回到 ALU 的输入里来。这样的一条线路,就是我们的“旁路”。它越过(Bypass)了写入寄存器,再从寄存器读出的过程,也为我们节省了 2 个时钟周期。

操作数前推可以和流水线停顿一起使用,比如说,我们先去执行一条 LOAD 指令,再去执行 ADD 指令。LOAD 指令在访存阶段才能把数据读取出来,所以下一条指令的执行阶段,需要在访存阶段完成之后,才能进行。

image-20240310162836658

填上空闲的NOP

无论是流水线停顿,还是操作数前推,归根到底,只要前面指令的特定阶段还没有执行完成,后面的指令就会被“阻塞”住

但是这个“阻塞”很多时候是没有必要的。因为尽管代码生成的指令是顺序的,但是如果后面的指令不需要依赖前面指令的执行结果,完全可以不必等待前面的指令运算完成。

在流水线里,后面的指令不依赖前面的指令,那就不用等待前面的指令执行,它完全可以先执行。

假设

a = b + c
x = a * o
y = 2 * 4

image-20240310163121517

可以看到,因为第三条指令并不依赖于前两条指令的计算结果,所以在第二条指令等待第一条指令的访存和写回阶段的时候,第三条指令就已经执行完成,在计算机组成里面,这种叫乱序执行(Out-of-Order Execution,OoOE)

乱序执行

从软件开发的维度来思考,乱序执行好像是在指令的执行阶段,引入了一个“线程池”。

image-20240310163442304

  • 在取指令和指令译码的时候,乱序执行的 CPU 和其他使用流水线架构的 CPU 是一样的。它会一级一级顺序地进行取指令和指令译码的工作。
  • 在指令译码完成之后,就不一样了。CPU 不会直接进行指令执行,而是进行一次指令分发,把指令发到一个叫作保留站(Reservation Stations)的地方。,这个保留站,就像一个高铁站一样。发送到车站的指令,就像是一列列的高铁。
  • 这些指令不会立刻执行,而要等待它们所依赖的数据,传递给它们之后才会执行。这就好像一列列的高铁都要等到乘客来齐了才能出发。
  • 一旦指令依赖的数据来齐了,指令就可以交到后面的功能单元(Function Unit,FU),其实就是 ALU,去执行了。有很多功能单元可以并行运行,但是不同的功能单元能够支持执行的指令并不相同。就和铁轨一样,有些从广州去北方,有些是从北方到南方。
  • 指令执行的阶段完成之后,我们并不能立刻把结果写回到寄存器里面去,而是把结果再存放到一个叫作重排序缓冲区(Re-Order Buffer,ROB)的地方。
  • 在重排序缓冲区里,我们的 CPU 会按照取指令的顺序,对指令的计算结果重新排序。只有排在前面的指令都已经完成了,才会提交指令,完成整个指令的运算结果。
  • 实际的指令的计算结果数据,并不是直接写到内存或者高速缓存里,而是先写入存储缓冲区(Store Buffer 面,最终才会写入到高速缓存和内存里。

整个乱序执行技术,就好像在指令的执行阶段提供一个“线程池”。指令不再是顺序执行的,而是根据池里所拥有的资源,以及各个任务是否可以进行执行,进行动态调度。在执行完成之后,又重新把结果在一个队列里面,按照指令的分发顺序重新排序。即使内部是“乱序”的,但是在外部看起来,仍然是井井有条地顺序执行

控制冒险

在结构冒险和数据冒险中,所有的流水线停顿操作都要从指令执行阶段开始。流水线的前两个阶段,也就是取指令(IF)和指令译码(ID)的阶段,是不需要停顿的。CPU 会在流水线里面直接去取下一条指令,然后进行译码。

取指令和指令译码不会需要遇到任何停顿,这是基于一个假设。这个假设就是,所有的指令代码都是顺序加载执行的。不过这个假设,在执行的代码中,一旦遇到 if…else 这样的条件分支,或者 for/while 循环,就会不成立。

cmp 比较指令、jmp 和 jle 这样的条件跳转指令,指令发生的候,CPU 可能会跳转去执行其他指令。jmp 后的那一条指令是否应该顺序加载执行,在流水线里面进行取指令的时候,我们没法知道。要等 jmp 指令执行完成,去更新了 PC 寄存器之后,我们才能知道,是否执行下一条指令,还是跳转到另外一个内存地址,去取别的指令。

为了确保能取到正确的指令,而不得不进行等待延迟的情况,控制冒险(Control Harzard)

缩短分支延迟

条件跳转指令其实进行了两种电路操作

第一种,是进行条件比较。这个条件比较,需要的输入是,根据指令的 opcode,就能确认的条件码寄存器。

第二种,是进行实际的跳转,也就是把要跳转的地址信息写入到 PC 寄存器。无论是 opcode,还是对应的条件码寄存器,还是跳转的地址,都是在指令译码(ID)的阶段就能获得的。而对应的条件码比较的电路,只要是简单的逻辑门电路就可以了,并不需要一个完整而复杂的 ALU

所以,我们可以将条件判断、地址跳转,都提前到指令译码阶段进行,而不需要放在指令执行阶段。对应的,我们也要在 CPU 里面设计对应的旁路,在指令译码阶段,就提供对应的判断比较的电路。

这种方式,本质上和前面数据冒险的操作数前推的解决方案类似,就是在硬件电路层面,把一些计算结果更早地反馈到流水线中。这样反馈变得更快了,后面的指令需要等待的时间就变短了。

跳转指令的比较结果,仍然要在指令执行的时候才能知道。在流水线里,第一条指令进行指令译码的时钟周期里,我们其实就要去取下一条指令了。这个时候,我们其实还没有开始指令执行阶段,自然也就不知道比较的结果

分支预测

让 CPU 来猜一猜,条件跳转后执行的指令,应该是哪一条。

最简单的分支预测技术,叫作“假装分支不发生”。顾名思义,自然就是仍然按照顺序,把指令往下执行。其实就是 CPU 预测,条件跳转一定不发生。这样的预测方法,其实也是一种静态预测技术。就好像猜硬币的时候,你一直猜正面,会有 50% 的正确率

如果分支预测是正确的,这样我们就节省下来本来需要停顿下来等待的时间。

如果分支预测失败了呢?

那我们就把后面已经取出指令已经执行的部分,给丢弃掉。这个丢弃的操作,在流水线里面,叫作 Zap 或者 Flush。CPU 不仅要执行后面的指令,对于这些已经在流水线里面执行到一半的指令,我们还需要做对应的清除操作。比如,清空已经使用的寄存器里面的数据等等,这些清除操作,也有一定的开销

image-20240310164805400

所以,CPU 需要提供对应的丢弃指令的功能,通过控制信号清除掉已经在流水线中执行的指令。

动态分支预测

一级分支预测(One Level Branch Prediction),或者叫1 比特饱和计数(1-bit saturating counter)。这个方法,其实就是用一个比特,去记录当前分支的比较情况,直接用当前分支的比较情况,来预测下一次分支时候的比较情况

2 比特饱和计数,或者叫双模态预测器(Bimodal Predictor),采用一个状态机用2比特来记录对应的状态

image-20240310170352019

这里用了2个函数来测试不嵌套的影响,一开始时间是不同的,后面又测了几次都相同了。

image-20240310170455033

循环其实也是利用 cmp 和 jle 这样先比较后跳转的指令来实现的,这里的代码,每一次循环都有一个 cmp 和 jle 指令。每一个 jle 就意味着,要比较条件码寄存器的状态,决定是顺序执行代码,还是要跳转到另外一个地址。也就是说,在每一次循环发生的时候,都会有一次“分支”

image-20240310170630779

image-20240310170652203

分支预测策略最简单的一个方式,自然是“假定分支不发生”。对应到上面的循环代码,就是循环始终会进行下去。在这样的情况下,上面的第一段循环,也就是内层 k 循环 10000 次的代码。每隔 10000 次,才会发生一次预测上的错误。而这样的错误,在第二层 j 的循环发生的次数,是 1000 次。

最外层的 i 的循环是 100 次。每个外层循环一次里面,都会发生 1000 次最内层 k 的循环的预测错误,所以一共会发生 100 × 1000 = 10 万次预测错误。

上面的第二段循环,也就是内存 k 的循环 100 次的代码,则是每 100 次循环,就会发生一次预测错误。这样的错误,在第二层 j 的循环发生的次数,还是 1000 次。最外层 i 的循环是 10000 次,所以一共会发生 1000 × 10000 = 1000 万次预测错误。

第一段代码发生“分支预测”错误的情况比较少,更多的计算机指令,在流水线里顺序运行下去了,而不需要把运行到一半的指令丢弃掉,再去重新加载新的指令执行
的 i 的循环是 100 次。每个外层循环一次里面,都会发生 1000 次最内层 k 的循环的预测错误,所以一共会发生 100 × 1000 = 10 万次预测错误。

上面的第二段循环,也就是内存 k 的循环 100 次的代码,则是每 100 次循环,就会发生一次预测错误。这样的错误,在第二层 j 的循环发生的次数,还是 1000 次。最外层 i 的循环是 10000 次,所以一共会发生 1000 × 10000 = 1000 万次预测错误。

第一段代码发生“分支预测”错误的情况比较少,更多的计算机指令,在流水线里顺序运行下去了,而不需要把运行到一半的指令丢弃掉,再去重新加载新的指令执行

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

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

相关文章

CodeReview 规范及实施

优质博文:IT-BLOG-CN 一、为什么需要CodeReview 随着业务压力增大,引发代码质量下降,代码质量的下降导致了开发效率的降低,维护成功高等问题,开发效率下降后又加重了业务压力,最终陷入了死亡三角的内耗之…

VScode的列选

可以用来优化代码排布,让变量整齐成为一排 一、批量复制: 在1处左键单击,然后摁住SHIFTALT键的同时,左键单击2处,即可复制一整块的内容 如果所示 就可以复制了 二、批量输入 在1处左键单击,然后摁住SHI…

基于斑翠鸟优化算法(Pied Kingfisher Optimizer ,PKO)的无人机三维路径规划(MATLAB)

一、无人机路径规划模型介绍 二、算法介绍 斑翠鸟优化算法(Pied Kingfisher Optimizer ,PKO),是由Abdelazim Hussien于2024年提出的一种基于群体的新型元启发式算法,它从自然界中观察到的斑翠鸟独特的狩猎行为和共生关系中汲取灵…

Linux系统中的任务迁移技术

任务迁移技术是指将任务从一个处理器核心(CPU核心)移动到另一个核心的过程,以实现负载均衡、优化系统性能或者其他系统目标的技术。任务迁移技术在多核系统中具有重要的作用,可以通过动态调整任务位置来避免负载不均衡和性能瓶颈&…

【工具】Git的24种常用命令

相关链接 传送门&#xff1a;>>>【工具】Git的介绍与安装<< 1.Git配置邮箱和用户 第一次使用Git软件&#xff0c;需要告诉Git软件你的名称和邮箱&#xff0c;否则无法将文件纳入到版本库中进行版本管理。 原因&#xff1a;多人协作时&#xff0c;不同的用户可…

恒驰上云规划实施解决方案上线华为云官网

华为云与伙伴共同打造联合解决方案 已成为更多企业的数字化转型利器 1月恒驰上云规划实施解决方案 完成上市宣讲并正式上架华为云官网 恒驰上云规划实施解决方案能力全景图&#xff1a;融合厂商云服务能力&#xff0c;一站式高效云迁移 从深入了解企业的本地IT环境、业务特点…

分享一下 iOS 发布/测试证书 申请过程

1.使用 已开通iOS开发者 的账号登陆 Apple Developer Apple Developer 2.点击下图右上角的 Account&#xff08;账户&#xff09; 点击下图中的 certificates&#xff08;证书&#xff09; 然后会挑战至下图所示页面 3.然后先要注册一个 App id 点击 register 就完成了 4.…

【JAVA】CSS2:样式、选择器、伪类、颜色、字体、边框、列表、背景、盒子、布局、浮动

本文介绍了CSS样式、选择器、伪类、像素、颜色、字体、边框、列表、表格属性、背景、盒子、布局与浮动 1.样式 1.1 行内样式 <h1 style"color: aqua;font-size: large;">123</h1> 1.2 内部样式 <style>h1{color: red;font: 100;}</style>…

Oracle SQL优化(读懂执行计划 一)

目录 SQL执行计划的作用示例演示执行计划概念介绍执行计划实例DISPLAY_CURSOR 类型DISPLAY_AWR 类型 指标详解 SQL执行计划的作用 示例演示 执行计划概念介绍 执行计划实例 DISPLAY_CURSOR 类型 DISPLAY_AWR 类型 指标详解

vim基础命令

目录 前言 一.vim基础命令大全 二.vim熟练的好处 三.入门使用命令 四.使用案例 4.1 gg和G 4.2 i 和 u 和 ESC使用 4.3 y$ 和 p 和 u 使用 五.注意事项 前言 启动vim编辑器后自动进入编辑模式&#xff0c;在此模式中输入命令对应vim一个动作&#xff0c;比如&#xff1a;进入编辑…

linux 配置jdk环境变量

1.确保已上传jdk包到指定目录 2.打开终端&#xff0c;使用文本编辑器&#xff08;比如vi、nano等&#xff09;创建或修改~/.bashrc文件。命令为&#xff1a; sudo vi ~/.bashrc3.在.bashrc文件末添加以下内容&#xff1a; export JAVA_HOME/usr/local/jdk/jdk1.8.0_391 #将…

理解STM32的低功耗模式

低功耗模式简介 TM32的低功耗模式是特别设计来减少微控制器在不活跃状态下的能耗。这些模式允许STM32在保持核心功能的同时尽可能减少电力消耗&#xff0c;适合用在电池供电或需长期运行的场景。理解各种低功耗模式如何节能&#xff0c;主要包括以下几个方面&#xff1a; 关闭…

C++类和对象(下篇)

目录 一.再谈构造函数 二.static成员 三.友元 四.内部类 五. 再次理解类和对象 一.再谈构造函数 1.构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值。 class Date { public:Date(int year, int month…

MES系统是怎么进行数据采集的?

在MES管理系统中&#xff0c;数据采集作为最基础也最为关键的一环&#xff0c;对于实现生产过程的透明化、可控好以及优化生产流程具有重要意义。 mes系统是怎么采集数据的? 一、PLC类数据采集&#xff1a;使用C#或C直接编程访问PLC(不需要花钱买组态软件或第三方软件) 二、…

HTML 学习笔记(四)图片

<!--通过图片标签"<img src "图片路径">"来调用图片在网页中进行显示--> <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthd…

PostgreSQL索引篇 | GiST索引

GiST索引 PostgreSQL版本为8.4.1 &#xff08;本文为《PostgreSQL数据库内核分析》一书的总结笔记&#xff0c;需要电子版的可私信我&#xff09; GiST&#xff08;Generalized Search Tree&#xff0c;通用搜索树&#xff09;是一种平衡的、树状结构的访问方法。 它在系统中…

【数学建模】传染病模型笔记

传染病的基本数学模型&#xff0c;研究传染病的传播速度、空间范围、传播途径、动力学机理等问题&#xff0c;以指导对传染病的有效地预防和控制。常见的传染病模型按照传染病类型分为 SI、SIR、SIRS、SEIR 模型等&#xff0c;按照传播机理又分为基于常微分方程、偏微分方程、网…

Redis特性与应用场景

Redis是一个在内存中存储数据的中间件&#xff0c;用于作为数据库&#xff0c;用于作为数据缓存&#xff0c;在分布式系统中能够发挥重要作用。 Redis的特性 1.In-memory data structures: MySQL使用表的方式存储数据&#xff0c;这意味着数据通常存储在硬盘上&#xff0c;并且…

python 网络库集锦

目录 通用网络库 网络爬虫框架 1.功能齐全的爬虫 2.其他 HTML/XML解析器 1.通用 2.清理 文本处理 自然语言处理 浏览器自动化与仿真 多重处理 异步网络编程库 队列 云计算 网页内容提取 WebSocket DNS解析 计算机视觉 通用网络库 1.urllib -网络库(stdlib)。…

【MATLAB】语音信号识别与处理:一维信号NLM非局部均值滤波算法去噪及谱相减算法呈现频谱

1 基本定义 一维信号NLM非局部均值滤波算法是一种基于非局部均值思想的滤波方法&#xff0c;它通过对信号进行分块&#xff0c;计算每个块与其他块之间的相似度&#xff0c;以非局部均值的方式去除噪声。该算法的主要思想是在一定范围内寻找与当前块相似的块&#xff0c;以这些…