为了追求更快,CPU、内存、I/O都做了哪些努力?
背景
曾经,我面试的时候有两个最怕的。一怕问算法,二怕问高并发。
算法这个,刷了不少LeetCode,发现还是有套路可循的,虽不敢说算法能力有多强,至少没有以前那么怕了(才怪)。
而第二个,高性能高并发技术,感觉有好多技术要学,东学一点,西学一点,不成体系。直到有一次面试,遇到了一个大牛,问到了这方面,结果被虐的体无完肤。幸运的是,这位大牛不但技术一流,还认真跟我交流了学习心得,怎么样去有体系的系统去梳理这方面的技术知识,而不是瞎学。
CPU
不管什么样的编程语言,什么样的代码框架,最终都是由CPU去执行完成的(当然这么说不太准确,也有GPU、TPU、协处理器等其他情况,当然这不是本文探讨的重点)。
所以要想提高性能,提高并发量,首要问题就是如何让CPU跑的更快?
这个问题,也是一直以来CPU厂商一直在努力追求的方向。
如何让CPU更快?CPU厂商做了两个方面的努力:
加快指令执行的速度
加快CPU读取数据的速度
对于第一个方向,CPU执行指令的快慢,是跟CPU的主频紧密相关的,如何更快的取指令、指令译码、执行,缩短CPU的指令周期,提升主频在相当长一段时间里都是非常有效的办法。
从几百MHz,到如今到几GHz,CPU主频有了长足的进步,相同时间里能够执行的指令数变的更多了。
对于第二个方向,如何提升CPU读取数据的速度,答案就是加缓存,利用局部性原理将内存中经常会访问的数据搬运到CPU中,这样大大提升了存取速度。
从一级缓存,到二级缓存,乃至三级缓存,CPU缓存的层级和容量也在不断提升,读写数据的时间省了不少。
但随着时间到推移,尤其进入21世纪之后,处理器厂商发现,进一步提升主频变得越来越困难了,CPU的缓存也很难进一步扩容。
怎么办呢?既然一个人干活的速度已经很难再提升,那何不多找几个人一起干?于是,多核技术来了,一个CPU里面有多个核心,众人划桨开大船,CPU的速度再一次腾飞~
甚至,让一个核在“闲暇时间”,利用“闲置资源”去执行另外的线程,诞生了让一个核“同时”执行两个线程的超线程技术。
上面简单交代了为了提升性能,CPU所做的努力。但是光是CPU快是没用的,还需要我们更好的去利用开发,否则就是对CPU算力的浪费。
上面提到了线程,是的,如何提高性能,提高并发量?使用多线程技术当然是一个非常好的思路。
但多线程的引入,就不得不提到两个跟线程有关的话题:
线程同步
线程阻塞
多个线程协同工作,必然会引入同步的问题,常规解决方案是加锁,加锁的线程一般会进入阻塞。
线程遇到阻塞了,就需要切换,而切换是有一定的成本开销的,不仅是系统调度的时间开销,还可能有CPU缓存失效的损失。
如果线程频频加锁,频频阻塞,那这个损失就相当可观了。为了提升性能,无锁编程技术就出现了,利用CPU提供的机制,提供更轻量的加锁方案。
同时,为了让切换后的线程仍然能够在之前的CPU核心上运行,降低缓存损失,线程的CPU亲和性绑定技术也出现了。
现代操作系统都是以时间片的形式来调度分配给多个线程使用。如果时间片还没用完就因为这样或那样的原因将执行机会拱手相让,那线程也太亏了。
于是,有人提出要充分利用CPU,别让线程阻塞,交出执行权,自己在应用层实现多个执行流的调度,这里阻塞了,就去执行那里,总之要把时间片充分用完,这就诞生了协程技术,阻塞了不要紧,我还能干别的,不要轻易发生线程切换。
内存
与CPU工作相关的第一亲密伙伴就是内存了,二者协作才能唱好一出戏。
提升内存访问的速度,同样是高性能开发话题重要组成部分!
那如何提升呢?硬件层面程序员是很难改变的,咱们只好从软件层面下功夫。
内存的管理经历了从实地址模式到分页式内存管理,如今的计算机中,CPU拿的的地址都是虚拟地址,这中间就会涉及到地址的转换,在这里就有文章可做,有两个方向可以努力:
减少缺页异常
使用大页技术
现代操作系统,基本上都会使用一个叫换页/交换文件的技术:内存空间有限,但进程越来越多,对内存空间的需求越来越大,用完了怎么办?于是在硬盘上划分一块区域出来,把内存中很久不用的数据转移到这块区域上来,等程序用到的时候,触发访问异常,再在异常处理函数中将其从硬盘读取进来。
可以想象,如果程序访问的内存老是不在内存中,而是被交换到了硬盘上,就会频繁触发缺页异常,那程序的性能肯定大打折扣,所以减少缺页异常也是提升性能的好办法。
从虚拟地址寻址真实的物理内存,这个过程是CPU完成的,具体来说,就是通过查表,从页表->一级页目录->二级页目录->物理内存。
页目录和页表是存在内存中的,毫无疑问,内存寻址是一个非常非常高频的事情,时时刻刻都在发生,而多次查表势必是很慢的,有鉴于此,CPU引入了一个叫TLB(Translation Look- aside buffer)的东西,使用缓存页表项的方式来减少内存查表的操作,加快寻址速度。
默认情况下,操作系统是以4KB为单位管理内存页的,对于一些需要大量内存的服务器程序(Redis、JVM、ElascticSearch等等),动辄就是几十个G,按照4KB的单位划分,那得产生多少的页表项啊!
而CPU中的TLB的大小是有限的,内存越多,页表项也就越多,TLB缓存失效的概率也就越大。所以,大页内存技术就出现了,4KB太小,就弄大点。大页内存技术的出现,减少了缺页异常的出现次数,也提高了TLB命中的概率,对于提升性能有很大的帮助。
在一些高配置的服务器上,内存数量庞大,而CPU多个核都要通过内存总线访问内存,可想而知,CPU核数上去以后,内存总线的竞争势必也会加剧。于是NUMA架构出现了,把CPU核心划分不同的分组,各自使用自己的内存访问总线,提高内存的访问速度。
I/O
CPU和内存都够快了,但这还是不够。我们的程序日常工作中,除了一些CPU密集型的程序(执行数学运算,加密解密,机器学习等等)以外,相当一部分时间都是在执行I/O,如读写硬盘文件、收发网络数据包等等。
所以,如何提升I/O的速度,是高性能开发技术领域一个重要的话题。
因为I/O会涉及到与外设(硬盘、网卡等)的交互,而这些外设又通常是非常慢(相对CPU执行速度)的,所以正常情况下,线程执行到I/O操作时难免会阻塞,这也是前面在CPU部分提到过的。
阻塞以后那就没办法干活了,为了能干活,那就开多个线程。但线程资源是很昂贵的,没办法大量使用,况且线程多了,多个线程切换调度同样是很花时间的。
那可不可以让线程执行I/O时不阻塞呢?于是,新的技术又出现了:
非阻塞I/O
I/O多路复用
异步I/O
原来的阻塞I/O是一直等,等到I/O的完成,非阻塞I/O一般是轮询,可以去干别的事,过一会儿就来问一下:好了没有?
但每个线程都去轮询也不是个事儿啊,干脆交给一个线程去专门负责吧,这就是I/O多路复用,通过select/poll的方式只用一个线程就可以处理多个I/O目标。再然后,再改进一下,用epoll,连轮询也不用了,改用内核唤醒通知的机制,同时处理的I/O目标还更多了。
异步I/O就更爽了,设置一个回调函数,自己干别的事去了,回头操作系统叫你来收数据就好了。
再说回到I/O本身,会将数据在内存和外设之间传输,如果数据量很大,让CPU去搬运数据的话,既耗时又没有技术含量,这是对CPU算力的很大浪费。
所以,为了将CPU从中解放出来,又诞生了一门技术:直接内存访问DMA,把数据的传输工作外包出去,交由DMA控制器来完成,CPU只在背后发号施令即可。
有了DMA,再也不用麻烦CPU去执行数据的搬运工作。但对于应用程序而言,想要把文件通过网络发送出去,还是要把数据在内核态空间和用户态空间来回折腾两次,这两步还得CPU出马去复制拷贝,属于一种浪费,为了解决这个问题,提升性能,又进一步产生了零拷贝技术,彻底为CPU减负。
算法架构
CPU、内存、I/O都够快了,单台计算机的性能已经很难提升了。不过,现在的服务器很少是单打独斗了,接下来就要把目光转移到算法、架构上来了。
一台服务器搞不定,那就用硬件堆出性能来,分布式集群技术和负载均衡技术就派上用场了。
这年头,哪个后端服务没有数据库?如何让数据库更快?该轮到索引技术上了,通过给数据库建立索引,提升检索速度。
但数据库这家伙的数据毕竟是存在硬盘上的,读取的时候势必会慢,要是大量的数据请求都怼上来,这谁顶得住?于是基于内存的数据库缓存Redis、Memcached应运而生,毕竟,访问内存比从数据库查询快得多。
算法架构这一块的技术实在太多了,也是从一个普通码农通往架构师的必经之路,咱们下回再聊。
总结
高性能、高并发是后端开发永恒追求的话题。
每一项技术都不是凭空出现的,一定是为了解决某个问题而提出。我们在学这些技术的时候,掌握它出现的原因,和其他技术之间的关联,在自己的大脑中建立一座技术知识层级图,一定能事半功倍。
特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:
长按订阅更多精彩▼如有收获,点个在看,诚挚感谢
为了追求更快,CPU、内存、I/O都做了哪些努力?相关推荐
- 为了让机器能和人更好的聊天, Google 都做了什么?
来源:极客公园 如果未来机器人注定要崛起,那自然语言处理能力的突破无疑至关重要. 今天在上海的 Google 开发者大会上,Google 终于官宣了一个大新闻:谷歌 AI 中国中心在北京成立. 这个早 ...
- cpu内存占用率都不高,电脑卡的问题
从某一天起,电脑就出现了运行卡的情况,开始没有觉察到异常,后来连续几天都这样,开个浏览器都卡死,不得不用结束程序. 是垃圾太多了?--用ccleaner/360都清理了彻底一遍,无果: 是中毒了?-- ...
- Java VS Go,微服务究竟谁更快?
作者 | 程序猿DD 责编 | 张文 头图 | CSDN 下载自视觉中国 Java 微服务能像 Go 微服务一样快吗?这是我最近一直在思索的一个问题. 去年 8 月份的 the Oracle Gr ...
- POLARDB:向着更快、更高、更强不断前行!
如果各位读者朋友记忆力还不错的话,没准会记得老孙在去年9月份的时候曾经写过一篇文章<"宝拉"快跑!阿里云发布超高性能云数据库POLARDB>,彼时,阿里云数据库&quo ...
- 串行通信速度一定比并行通信速度“更快”吗?
为什么USB要用串行通信而不是用并行呢? 来自:http://zhidao.baidu.com/question/293842108.html?fr=qrl&cid=93&index= ...
- 如何设置计划任务程序 每6小时运行一次_如何使win10系统运行的更快?
微软推出的新操作系统Windows 10越来越受到欢迎,用户越来越多,大家是不是会有这样的感觉:同一款电脑,同样的配置,为什么有的人的电脑用起来非常的流畅,而有的人就连打开个网页都卡的要命:有的人的电 ...
- 怎么使计算机软件运行快w10,如何使win10系统运行的更快?
微软推出的新操作系统Windows 10越来越受到欢迎,用户越来越多,大家是不是会有这样的感觉:同一款电脑,同样的配置,为什么有的人的电脑用起来非常的流畅,而有的人就连打开个网页都卡的要命:有的人的电 ...
- 原来 CPU 为程序性能优化做了这么多
来自:武培轩 本文主要来学习内存屏障和 CPU 缓存知识,以便于我们去了解 CPU 对程序性能优化做了哪些努力. 首先来看下 CPU 缓存: CPU 缓存 CPU 缓存是为了提高程序运行的性能,CPU ...
- 如何写出让 CPU 跑得更快的代码?
作者 | 小林coding 来源 | 小林coding(ID:CodingLin) 前言 代码都是由 CPU 跑起来的,我们代码写的好与坏就决定了 CPU 的执行效率,特别是在编写计算密集型的程序,更 ...
最新文章
- Linux服务器上zsh和bash的对比
- 成功解决NameError: name 'file' is not defined
- 统计学习方法例2.1实现(转)
- Linux 字符设备驱动开发基础(二)—— 编写简单 PWM 设备驱动
- 前端学习(2004)vue之电商管理系统电商系统之阻止页签切换
- SQL Server如何链接到 Oracle并查询其中的数据?并实现做接口
- npi阶段是什么意思_《如何阅读一本书》:4个问题、4个阶段,助你实现高效、快速阅读...
- JavaScript之实例练习(模态对话框详解)
- 集群资源分配_推荐一款MySQL日常运维和集群管理的自动化平台--Arkcontrol
- urllib携带登录信息
- biee mysql,Linux环境中使用BIEE 连接SQLServer业务数据源的简单示例
- C#控件储备——信息提示控件toolTip
- linux下安装yum步骤
- 工信部发布2015年中国软件业务收入百强发展报告(zz)
- 关于Oracle官网需要登录Oracle账户问题(亲测有效)
- 微信小程序 如何实现列表
- java ssm人体健康体检信息管理系统-
- Better to follow, follow to be better(2019 ICCV)
- opencv光线补偿_光线补偿算法的实现
- 活动目录备份|活动目录教程
热门文章
- 介绍下JSP如何进行自动刷新
- 关于学习Python的一点学习总结(20->assert判断->while和for使用)
- Codeforce DIV2 614 SZU的cf集训round1 C ~ D
- c语言 dll注入,教大家写一个远程线程的DLL注入,其实还是蛮简单的……………………...
- 深入jvm虚拟机第4版_深入JVM虚拟机,阿里架构师直言,这份文档真的是JVM最深解读...
- @RequestBody映射
- 人工智能的影响调查_调查报告|文科大学生群体对于人工智能影响 就业的认知程度:基于访谈的质性研究...
- 2016 linux发行版排行_灵越7590 安装 linux (manjaro-gnome)
- mysql5.6允许远程连接_mysql允许远程连接的方法
- log函数 oracle power_数学函数