啊?排序字段的大小也会影响排序性能???面试官都惊了!

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

所以,我们可以采用下面两个手段避免回表查询:

  1. SQL中的SELECT部分中的字段尽量不要用*,而是指定字段,确保SELECT中的字段 + 排序字段的值大小小于等于参数max_length_for_sort_data,这样MySQL就会采用上面的(1)方案排序

  2. 如果一定要使用*,那么,务必保证表中字段的总长度不超过max_length_for_sort_data,这样MySQL也会采用上面的(1)方案排序

那么,是不是只要保证SELECT中的字段 + 排序字段的值大小小于等于参数max_length_for_sort_data,排序性能一定是最好的呢?

假如本章《覆盖索引》中的排序字段username,我们设计它的长度为32字节和200字节,同时保证了SELECT中的字段 + 排序字段的值大小小于等于参数max_length_for_sort_data,那么,两种字段长度的设计对排序性能有什么不同的影响呢?

这时,我就不得不提一下这个排序比较的过程,通过这个过程的讲解,我来揭开上面这个问题的答案!

排序比较

MySQL对排序字段进行排序对比时使用了C函数库的memcmp,因为memcmap充分利用了64位CPU的特性,所以,性能非常高!为什么呢?

这里我就以64位CPU为例,看一下memcmp的比较过程,memcmp函数转化为汇编指令后,主要包含了下面这些过程:

  1. 通过MOV指令,从内存中读取用于比较的两个入参地址,并将地址分别写入两个rax寄存器

  2. 通过NOP指令,对上面的rax寄存器中的地址做内存对齐

  3. 通过CMP指令,对没有对齐的部分,单字节(byte)比较

  4. 通过CMP指令,针对对齐的部分,以32个字节为单位,通过冗余指令,做4次8字节(qword)比较,为什么以32字节单位,拆分4次比较两个入参地址?下面我会讲到。

  5. 通过CMP指令,针对步骤4中不足32个字节的部分,以8个字节为单位,做8字节(qword)比较

  6. 通过CMP指令,针对步骤5中最后剩余不足8个字节的部分,做单字节(byte)比较

我先以上面的第一步的MOV指令为例,看下指令在CPU中的处理过程。

下图是Intel的Nehalem处理器的内核架构,我们从上到下看一下MOV指令的处理过程:

1.指令提取单元(IFU)从ITLB读取指令MOV在CPU L1 Cache中的位置

2.指令提取单元(IFU)从分支预测单元(BPU)读取if…then中分支then中的起始指令,由于MOV指令操作不涉及if条件,所以,该读取结果为空

3.指令提取单元(IFU)根据步骤1得到的位置到CPU L1 Cache中的Instruction Cache中找到指令MOV,并提取MOV

4.指令提取单元(IFU)将MOV指令传递给解码器(ILD)

5.解码器(ILD)对指令MOV进行预解码,校验指令长度等,如果超过指定长度,会做其他处理,ps:这个将来我会在其他文章中详细讲解。

6.解码器(ILD)将MOV指令传递给指令队列(Instruction Queue)进行缓冲,ps:当指令非常多时,指令队列可以收集一批指令后,在一个时钟周期批量将这批指令下传,一批4条指令

7.指令队列将MOV指令传递给指令解码器(Instruction Decoders)进行解码,指令解码器包含4个解码器:1个复杂解码器(Complex Deocder)和3个简单解码器(Simple Decoder),前者用于解析复杂的一条指令,将其解析为1 ~ 4条微指令(不包含1),后者将一条简单指令解析为一条微指令,微指令一般缩写为uop。上面memcmp函数中的MOV指令含义是从内存中读取用于比较的两个入参地址,并将地址分别写入两个rax寄存器,属于复杂指令,所以,这条MOV指令被解析为两条微指令uops:从内存中读取入参地址uop1和将地址写入rax寄存器uop2

8.指令解析器将分解的两个uops传递给指令解码队列(IDQ),进行指令去重

  • 指令解码队列(IDQ)依次将两个uops传递给循环检测器(LSD),循环检测器检查uop是否存在类似while这样的循环语句,如果存在,对循环中重复的uop去重。由于uop1uop2均不存在循环,所以,循环检测器直接返回uop1uop2给指令解码队列(IDQ)

  • 微指令序列号生成器给uop1uop2生成两个序列号,将序列号传递给指令解码队列,分配给uop1uop2

9.指令解码队列将uop1uop2传递给MicroOps Fusion,做微指令聚合。对于有些指令相同的,在这里就聚合为一条微指令 + 指令个数。uop1uop2不相同,所以,不做聚合。

10.至此,CPU完成了指令MOV的执行前工作,这个过程一般称作Front End。接着,Fusion将uop1uop2传递给分配器Allocator,进行微指令执行单元分配

(1)Dispatcher分别将微指令uop1uop2写入重排序缓冲区(ROB),等待指令执行结果。

(2)Dispatcher同时将微指令写入中继器(RS),对于uop1uop2,这个写入是有区别的:

  • uop1是一个内存读取操作,所以,写入重排序缓冲区的uop1是完整微指令:uop1 ADDR1ADDR1为读取的内存地址。又因为uop1无依赖前置数据,所以,Dispatcher将uop1完整指令同时写入中继器(RS),待执行。

  • uop2是一个写寄存器操作,写入源数据当前不存在,所以,Dispatcher不会将uop2的完整指令写入中继器(RS)

11.中继器分析哪些微指令有依赖关系,哪些没有依赖关系,有依赖关系的串行执行,无依赖的并行执行。由于,当前中继器中只包含uop1,所以,只给uop1分配执行单元,即通过port2端口,将uop1完整指令传递给AGU Load执行单元,执行uop1,即该执行单元从内存排序缓冲区(MOB)中读取地址为ADDR1,如果不存在,再从CPU L1 Data Cache中读取,L1 Data Cache中没有,继续从L2 Cache读取,L2 Cache没有,就从L3 Data Cache中读取,CPU三级缓存中都没有,那只能通过地址总线从内存中读取ADDR1

12.当AGU Load执行单元成功读取到ADDR1后,发送完成状态COMPLETEADDR1到重排序缓冲区(ROB)

13.重排序缓冲区将uop1执行结果(状态)和ADDR1写入回退寄存器文件(RRF)

14.重排序缓冲区移除微指令uop1

15.此时,重排序缓冲区中有了ADDR1和步骤10写入的uop2,于是,重排序缓冲区将uop2ADDR1传递给Dispatcher,Dispatcher从寄存器别名表中命名一个rax寄存器,构造完整的uop2指令,即uop2 rax, ADDR1,表示将ADDR1写入rax寄存器,然后,将完整指令写入中继器(RS),准备执行uop2

16.中继器通过port 3端口分配AGU Store Address执行单元执行uop2,将ADDR1写入rax寄存器

17.执行单元执行成功后,将结果状态COMPLETE写入重排序缓冲区

18.重排序缓冲区将uop2执行结果(uop2,rax)写入回退寄存器文件(RRF),记录下rax寄存器中的值ADDR1

19.重排序缓冲区移除微指令uop2

通过上面的步骤,你应该了解一条指令在CPU中的处理过程,那么,回到上面的一个问题:为什么memcmp函数,要以32字节单位,拆分4次比较两个入参地址呢?

  1. 从上图可以发现,右侧L2 Data Cache和底部L1 Data Cache连接在一起,用来传输数据,而这个传输的带宽为256bit,即一次最多可以传输32个字节的数据,所以,将入参地址以32字节为单位执行uop1微指令,可以充分利用L2 Data Cache和L1 Data Cache之间的带宽。

  2. 结合上面的步骤11,由于CPU每个时钟周期可以同时从中继器(RS)中挑选可并发执行的4条微指令并发执行,所以,将相同的比较指令CMP拆分4次,同时对比较的入参地址的4个部分并发比较,最后将比较结果汇总。这样,原来串行执行4次的比较任务变成的并行的一次执行,性能将大大提升。

结合上面memcmp函数中MOV指令在CPU中的处理过程,我们知道如果用于比较的排序字段长度超过32字节,而此时该字段值不在CPU L1 Cache中,那么,CPU不得不分多次将字段值写入L1 Cache,影响了性能,所以,建议排序字段的大小不要超过32字节

小结

通过本章内容的讲解,我们知道了一些排序优化的方法:

  1. 将排序字段加入索引,实现覆盖索引,避免排序

  2. SQL中SELECT字段 + 排序字段的值大小小于等于参数max_length_for_sort_data,可以避免回表查询,提升性能

  3. 排序字段大小尽量不要超过32字节,充分利用64位CPU的特性,提升排序性能

思考题

关于《覆盖索引》中的那条SQL:

SELECT * FROM user WHERE user_name LIKE “%am%” AND age >= 18 AND age <= 24 AND sex = 0 ORDER BY age, user_name LIMIT 0, 50

本章中,使用了快速排序对age,username排序,有没有更好的办法,提升排序的性能?

推荐好文
强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!
分享一套基于SpringBoot和Vue的企业级中后台开源项目,代码很规范!
能挣钱的,开源 SpringBoot 商城系统,功能超全,超漂亮

总结

虽然我个人也经常自嘲,十年之后要去成为外卖专员,但实际上依靠自身的努力,是能够减少三十五岁之后的焦虑的,毕竟好的架构师并不多。

架构师,是我们大部分技术人的职业目标,一名好的架构师来源于机遇(公司)、个人努力(吃得苦、肯钻研)、天分(真的热爱)的三者协作的结果,实践+机遇+努力才能助你成为优秀的架构师。

如果你也想成为一名好的架构师,那或许这份Java成长笔记你需要阅读阅读,希望能够对你的职业发展有所帮助。

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
nvw7oG-1713109898896)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-hNgDhUoy-1713109898897)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值