为什么网上牌九的页面老提示电脑出现错误页面

&p&五倍吞吐量的提升,跨可用区的六副本,低于一分钟的宕机恢复,兼容 MySQL 协议,这是 AWS 推出 Aurora 数据库时给出的数据。这种量级的提升不可能是小修小补,大都是在架构上有了变革性的突破才能达到,坊间流传了很多 Aurora 性能提升的秘密,在 Sigmod'17 上 Amazon 终于自己发了一篇论文介绍了在云环境下如何重新打造数据库这种传统软件。很多的传统软件都可以看一下上云是不是只是装一个软件那么简单。&/p&&h2&数据持久性&/h2&&p&尽管 Aurora 的性能数据很亮眼,但对于数据库来说最基本的要求还是一旦数据写入成功就不能丢,数据的持久性才是第一位的。多副本是解决数据持久性的常用办法,AWS 采用了基于 quorum 投票的协议来管理副本。简单说就是如果有 N 个副本,则一次写数据要求至少写入(N/2)+1 个节点才算写入成功,剩下的节点通过相互之间的一致性协议可以达到共同的状态,而读数据则要求至少从 N/2 个节点中读出相同的数据才能决定哪个数据是最新的。&/p&&p&对于一个 3 副本的 quorum 系统,读写的 quorum 数都是 2 ,这也是一个比较常见的高可用系统选择的 quorum 数。但是这个副本数放在大规模多组户的系统里是不能保证数据的持久性的。因为在大规模系统中,节点出问题是每时每刻都会发生的,如果只有三副本的话,基本上每时每刻都会有一个用户的系统处在 2/3 的健康状态。AWS 的 3 副本是分布在 3 个不同的可用区的,如果一个可用区出了故障,那么所有的数据库副本就会变成 2/3 的健康状态,这种情况下任意一个机器的故障都会导致一个用户的 quorum 健康数变为 1/3,这时候就没有办法判断数据是否一致了。&/p&&p&所以 Aurora 采用的是六副本,每个可用区两副本。这种架构可以保证写入操作可以容忍一个可用区不可用的情况 4/6,而读数据可以容忍一个可用区外加一台机器不可用的情况 3/6。一旦出现了一个可用区加一台机器不可用的 3/6 情况,写数据会被禁止,但是由于读数据可以进行可以很快的根据读出的数据再恢复出一个副本达到 4/6 状态恢复写入。&/p&&h2&数据分片&/h2&&p&现在来想一下出现了一个 AZ 加一台机器挂掉的情况,这时我们需要重建一个副本,那么重建的时间就取决于数据库副本文件有多大,随着数据库增长,恢复时间也会线性增加。这个对一个发展越来越好的业务是不能接受的,所以需要尽可能降低数据恢复时间。恢复时间过长还会带来另一个隐患就是在恢复的过程中如果又有一个机器故障,那么数据就没办法直接通过其他副本来恢复了,而恢复时间越长这个风险越大。从 CAP 的角度来看
Aurora 作为一个利用分布式存储的数据库是选择了 CP,但是如果可用性出了问题能在极短的时间恢复,那么从实际使用角度也就和 CAP 系统差不多了。 &/p&&p&一个直接的做法就是把副本分片,这样一个副本就可以散落在多台机器,这样一台机器挂的情况下我们就不需要恢复完整的副本,只需要恢复机器上的分片就可以。另一方面由于数据进行了分片,读数据的时候也可以利用多台主机的 IO 带宽,对性能也有提升。AWS 采用了 10G 的分片大小,这样在万兆网络的内网环境下,恢复一个分片可以在 10s 内完成,这样在比较极端的 1AZ+1 出故障的情况下,可以保证 10s 内恢复数据库读写。&/p&&p&这种数据分片的方式保证了数据库有很高的可用性,从运维角度来说就可以对这个系统进行 rolling update 了。比如想要升级底层的操作系统和软件,可以直接把机器下线进行升级,上面的分片都会很快在别的机器上进行重建,然后再把机器加回集群升级下一个。发现某台机器硬件有异常也不需要做手工的数据迁移,直接把机器下线送修。另外当出现数据热点的时候也可以直接将这个热点机器的其他分片标记为不可用把分片迁移走,来避免某一个用户的行为造成其他用户的性能下降。这些都是分片带来的好处。&/p&&h2&副本的副作用&/h2&&p&前面说了多副本带来的数据持久和高可用,但是多副本并不是没有代价的。本来磁盘 IO 的速度就慢,多个副本之间即使是并行的写入操作,latency 也会变成最慢的节点的 latency,随着副本数的增多,抖动会变得更大。另外在云环境下的数据库和传统环境下数据库很重要的一点不同就是计算和存储是分离的,这种情况下读数据可以从本地缓存中拿,而写数据就需要网络 IO,由于存储分散在了多台机器上可以并行利用磁盘带宽,瓶颈就来到了网络。&/p&&p&而 MySQL 自己的一些 IO 机制主要是针对本地磁盘设计的,很多优化方法并不适用于网络存储,此外 MySQL 自己的一些事务和故障恢复的机制,会造成写放大的问题。来看一下 MySQL mirror 机制下的 IO 流程:&/p&&p&&br&&/p&&img src=&/v2-cbebd892954abf_b.png& data-rawwidth=&1096& data-rawheight=&1058& class=&origin_image zh-lightbox-thumb& width=&1096& data-original=&/v2-cbebd892954abf_r.png&&&p&&br&&/p&&p&可以看到数据库一次写入除了要把数据写入还有 log, binlog,double-write,frm-file 这些东西要写入,而且需要在 master 上完成后才能再去 mirror 上执行同样的过程,等到 mirror 完成后一次请求才能结束。如果直接用 MySQL 的 mirror 机制那么上面提到的 6 副本系统实际上写入需要满足 6/6 才能成功,延迟会极大的加大。如果 AWS 采用这种机制,吞吐量不降到六分之一就算万幸了,更不要提五倍的提升了。这大概也是 AWS 想要自己搞数据库的原因,那么该怎么搞呢?&/p&&h2&Log 即数据&/h2&&p&要说 AWS 比起 Oracle(MySQL 现在的爹)做 MySQL 有什么优势的话那就是虽然数据库我没你们熟,但是底层的系统硬件我们都可以自己控制呀。看一下上面那张 IO 流程图,其实很多 log 都是怕底层存储出问题,机器突然断电造成数据不一致和考虑如何进行恢复才生成的。此外这里的一些数据是有冗余的,比如数据库在写入数据前都要先写入 WAL 再进行数据页的写入,至于为什么要这么做可以参考 《WAL 是如何保证数据库事务一致性的》这里只要知道通过 WAL 我们是可以重建数据的,只是这样做读数据效率会比较低所以数据库都会再写一次完整数据到硬盘,保证读的效率。&/p&&p&另外由于 MySQL 自己本身不能更改存储系统的功能,所以镜像功能需要自己控制实现,所有的数据同步都要过 MySQL 这个用户层程序去控制,而这些同步的功能又会占用大量的资源开销影响正常的读写操作,相当于每写一次数据,资源开销是原来的多倍。此外在故障恢复的时候 MySQL 在启动时需要花大量的时间对比日志和实际数据状态的差异,就行数据修复,这个过程也会导致故障恢复时间的延长。&/p&&p&AWS 的解决方式就是既然 WAL 里已经包含了所有的数据,那么我就不同步其他的东西了,只同步 log 就可以了。MySQL 去控制同步过程效率低,我底层的 EBS 自己就带同步功能啊,根本不需要 MySQL 再去控制,MySQL 节点只需要老老实实的做 SQL 相关的工作就可以了。数据库的故障恢复,存储系统可以自己进行数据恢复,MySQL 只需要把引擎启动起来就可以了。总结来说就是 MySQL 之前之所以慢是因为文件系统有很多缺陷需要很多额外的功夫来保证数据写入能成功,现在 AWS 给了一个及其完善的文件系统,数据写入了就能成功,断电了也能自己恢复,还能自己做多副本,那么 MySQL 自己要做的事情就少了,性能自然也就上去了。现在的流程就简化成了:&/p&&p&&br&&/p&&img src=&/v2-fdced67a791ff291da289d_b.png& data-rawwidth=&1054& data-rawheight=&780& class=&origin_image zh-lightbox-thumb& width=&1054& data-original=&/v2-fdced67a791ff291da289d_r.png&&&p&&br&&/p&&p&&br&&/p&&p&所有的 Replica 节点不需要将数据写入磁盘了,数据同步由底层的 EBS 系统来做。Replica 节点只需要接收 Primay 的 Log 信息来更新本地的缓存和一些全局的配置。Primary 的写入操作也不再需要各种各样的 log 写入,只需要把 WAL 给底层存储系统,存储系统会自动的写入到一定的 quorum 节点上。而有了这些 log,存储系统会自动的去做 checkpoint, 备份和故障恢复,而且这些耗时的操作都是在后台默默的异步执行的,不需要前台的 MySQL 引擎。这样写入操作的 IO 量和操作路径都显著变少了,AWS 才能把 MySQL 的性能在云环境里提升那么多。&/p&&p&再加上分片的存储将磁盘 IO 的压力分散,读写性能都得到了显著提升,放两张图来感受一下:&/p&&p&&br&&/p&&img src=&/v2-c7d2f47e07109cac4c8e52_b.png& data-rawwidth=&1082& data-rawheight=&1688& class=&origin_image zh-lightbox-thumb& width=&1082& data-original=&/v2-c7d2f47e07109cac4c8e52_r.png&&&p&&br&&/p&&p&&br&&/p&&p&MySQL 这样的应用在云环境下都有这种玩法,其他的传统软件,在云环境里又该怎么办呢?&/p&&p&&br&&/p&&p&论文下载链接:&a href=&/?target=http%3A///files/p1041-verbitski.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&&/span&&span class=&invisible&&/files/p1041-verbitski.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&
五倍吞吐量的提升,跨可用区的六副本,低于一分钟的宕机恢复,兼容 MySQL 协议,这是 AWS 推出 Aurora 数据库时给出的数据。这种量级的提升不可能是小修小补,大都是在架构上有了变革性的突破才能达到,坊间流传了很多 Aurora 性能提升的秘密,在 Sigmod'17…
&p&个人推荐看 Kenneth Reitz 大神的成名之作 Requests,感受一下什么是真正的Pythonic代码,什么是 Keep It Simple and Stupid&/p&&p&有网友已经整理了一份Requests源码阅读清单,内容幽默诙谐有趣,推荐看一看&/p&&ul&&li&&a href=&///?target=https%3A///wangshunping/read_requests/blob/master/doc/Requests_v0.2.0.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Requests v0.2.0 Birth!&i class=&icon-external&&&/i&&/a& &/li&&li&&a href=&///?target=https%3A///wangshunping/read_requests/blob/master/doc/Requests_v0.3.0.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Requests v0.3.0 Be frinendly&i class=&icon-external&&&/i&&/a& &/li&&li&&a href=&///?target=https%3A///wangshunping/read_requests/blob/master/doc/Requests_v0.4.0.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Requests v0.4.0 Amazing tour &i class=&icon-external&&&/i&&/a&&/li&&li&&a href=&///?target=https%3A///wangshunping/read_requests/blob/master/doc/Requests_v0.5.0.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Requests v0.5.0 Context Manager &i class=&icon-external&&&/i&&/a&&/li&&li&&a href=&///?target=https%3A///wangshunping/read_requests/blob/master/doc/Requests_v0.6.0.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Reuqests v0.6.0 Captain Hook! &i class=&icon-external&&&/i&&/a&&/li&&li&&a href=&///?target=https%3A///wangshunping/read_requests/blob/master/doc/Requests_v0.7.0.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Reuqests v0.7.0 awesome gevent&i class=&icon-external&&&/i&&/a& &/li&&li&&a href=&///?target=https%3A///wangshunping/read_requests/blob/master/doc/Requests_v0.8.0.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Reuqests v0.8.0 &i class=&icon-external&&&/i&&/a&2016-03-??&/li&&/ul&&p&還有老外分享的一個PPT,手把手教你如何阅读源码,也是拿Requests作为参考例子 &a href=&///?target=https%3A//www.slideshare.net/onceuponatimeforever/lets-read-code& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&slideshare.net/onceupon&/span&&span class=&invisible&&atimeforever/lets-read-code&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a& ,没有梯子的在这里下载 &a href=&///?target=https%3A///s/1i5ggKjr& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/s/1i5ggKj&/span&&span class=&invisible&&r&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&p&下面是Kenneth Reitz大神自己推薦的源碼閱讀清單,來源:&a href=&///?target=http%3A//python-guide-pt-br.readthedocs.io/en/latest/writing/reading/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Reading Great Code&i class=&icon-external&&&/i&&/a&&/p&&ul&&li&&a href=&///?target=https%3A///gleitz/howdoi& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Howdoi&i class=&icon-external&&&/i&&/a& Howdoi is a code search tool, written in Python&/li&&li&&a href=&///?target=https%3A///mitsuhiko/flask& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Flask&i class=&icon-external&&&/i&&/a& Flask is a microframework for Python based on Werkzeug and Jinja2. It’s intended for getting started very quickly and was developed with best intentions in mind.&/li&&li&&a href=&///?target=https%3A///python-diamond/Diamond& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Diamond&i class=&icon-external&&&/i&&/a& Diamond is a python daemon that collects metrics and publishes them to Graphite or other backends. It is capable of collecting cpu, memory, network, i/o, load and disk metrics. Additionally, it features an API for implementing custom collectors for gathering metrics from almost any source.&/li&&li&&a href=&///?target=https%3A///mitsuhiko/werkzeug& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Werkzeug&i class=&icon-external&&&/i&&/a& Werkzeug started as simple collection of various utilities for WSGI applications and has become one of the most advanced WSGI utility modules. It includes a powerful debugger, full-featured request and response objects, HTTP utilities to handle entity tags, cache control headers, HTTP dates, cookie handling, file uploads, a powerful URL routing system and a bunch of community-contributed addon modules.&/li&&li&&a href=&///?target=https%3A///kennethreitz/requests& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Requests&i class=&icon-external&&&/i&&/a& Requests is an Apache2 Licensed HTTP library, written in Python, for human beings.&/li&&li&&a href=&///?target=https%3A///kennethreitz/tablib& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Tablib&i class=&icon-external&&&/i&&/a& Tablib is a format-agnostic tabular dataset library, written in Python.&/li&&/ul&
个人推荐看 Kenneth Reitz 大神的成名之作 Requests,感受一下什么是真正的Pythonic代码,什么是 Keep It Simple and Stupid有网友已经整理了一份Requests源码阅读清单,内容幽默诙谐有趣,推荐看一看
&p&我们的程序的响应时间是10us(从收到行情到发出报单的响应时间),但是ping期货公司的交易前置机需要大约30us【这个数值会变化,见注释4】,所以网络延时占据了大量时间。&/p&&br&&p&我所有的性能测试都是在一台DELL r630机器上运行的,这台机器有2个NUMA结点,CPU型号是E5 .4GHz 6核)。所有的测试都是用rdtsc指令来测量时间,Intel官网上有一篇pdf文档[Gabriele Paoloni, 2010],讲述了如何精准地测量时间(要用cpuid来同步)。我自己做的性能测试的结果会写成“100(sd20)ns”的形式,代表平均值是100ns,标准差是20ns。我在算均值和标准差的时候会去掉最大的0.1%的数据再算,因为那些数据似乎并不是程序延时,而是cpu被调度执行别的任务了【原因见注释3】。有些性能测试在网上有现成的测试结果,我就没自己测,直接拿来用了,但是以后我会重新在我的机器上测一遍。&/p&&br&&p&一些我们比较注意的点:&/p&&p&&b&1.限制动态分配内存&/b&&/p&&p&&b&相关的知识背景:&/b&glibc默认的malloc背后有复杂的算法,当堆空间不足时会调用sbrk(),当分配内存很大时会调用mmap(),这些都是系统调用,似乎会比较慢,而且新分配的内存被first touch时也要过很久才能准备好。&/p&&p&&b&可取的做法:&/b&尽量使用vector或者array(初始化时分配足够的空间,之后每次使用都从里面取出来用)。尽量使用内存池。如果需要二叉树或者哈希表,尽量使用侵入式容器(boost::intrusive)。&/p&&p&&b&性能测试:&/b&我测试的分配尺寸有64和8128两种。首先,我测试了glibc malloc的性能,分配64字节耗时98(sd247)ns,分配8128字节需要耗时1485(sd471)ns。其次,我写了一个多进程安全的内存池,分配64字节需要29(sd15)ns,分配8128字节需要22(sd12)ns。【内存池的细节见注释6】。最后,我单独测试了sbrk()和first touch的性能,但是数据不记得了。&/p&&br&&p&&b&2.使用轮询,尽量避免阻塞&/b&&/p&&p&&b&相关的知识背景:&/b&上下文切换是非常耗时的,其中固定的消耗包括(cpu流水线被冲掉、各种寄存器需要被保存和恢复、内核中的调度算法要被执行),此外,缓存很有可能出现大量miss,这属于不固定的时间消耗。&/p&&p&&b&可取的做法:&/b&使用带有内核bypass功能的网卡。每个进程或者线程都独占一个cpu核【isolcpus和irqbalance的细节见注释3】,并且不停地轮询,用以保证快速响应。尽量避免任何可能导致阻塞的事件(如mutex),某些注定很慢的活动(比如把log写到磁盘上)应该被独立出来放到别的cpu上,不能影响主线程。&/p&&p&&b&性能测试:&/b&网上有一篇博客[tsunanet, 2010]测试了mode switch、thread switch、process switch的耗时,但是这篇文章太早了,以后我要用我的新cpu重新测一下。这篇博客里面,系统调用只需要&100ns,线程/进程切换需要&1us(不包括缓存miss的时间)。&/p&&br&&p&&b&3.使用共享内存作为唯一的IPC机制&/b&&/p&&p&&b&相关的知识背景:&/b&共享内存只有在初始化的时候有一些系统调用,之后就可以像访问正常内存一样使用了。其他IPC机制(管道、消息队列、套接字)则是每次传输数据时都有系统调用,并且每次传输的数据都经历多次拷贝。因此共享内存是最快的IPC机制。&/p&&p&&b&可取的做法:&/b&使用共享内存作为唯一的IPC机制。当然,可能需要手动实现一些东西来保证共享的数据在多进程下是安全,我们是自己实现了无锁内存池、无锁队列和顺序锁【关于seqlock的疑点见注释1】。&/p&&p&&b&性能测试:&/b&我使用了boost中的Interprocess库和Lockfree库,在共享内存上建立了一个spsc队列,然后用这个队列来传送数据,代码参考了stackoverflow上的一个答案[sehe, 2014]。我传送的数据是一个8字节整数,延时是153(sd61)ns。至于其他IPC机制,我在[cambridge, 2016]看到了一些性能测试结果,通常是要几微秒到几十微秒不等。&/p&&br&&p&&b&4.传递消息时使用无锁队列&/b&&/p&&p&&b&相关的知识背景:&/b&我只关注基于数组的无锁队列,其中:spsc队列是wait-free的,不论是入队出队都可以在确定的步数之内完成,而且实现时只需要基本的原子操作【为什么这很重要见注释7】;mpmc队列的实现方式则多种多样,但都会稍微慢一点,因为它们需要用一些比较重的原子操作(CAS或者FAA),而且有时它们需要等待一段不确定的时间直到另一个线程完成相应操作;另外,还有一种multi-observer的『广播队列』,多个读者可以收到同一条消息广播,这种队列也有sp和mp类型的,可以检查或者不检查overwrite;最后,还有一种队列允许存储不定长的消息。&/p&&p&&b&可取的做法:&/b&总的来说,应该避免使用mp类型的队列,举例:如果要用mpsc队列,可以使用多个spsc来达成目的,并不需要mp队列;同理,如果是消息广播,也可以使用多个sp队列来取代一个mp队列;如果广播时observer只想订阅一部分消息,那么可以用多个spsc+有计数功能的内存池【具体做法见注释2】;如果要求多个观察者看到多个生产者的消息,并且顺序一致,那只能用mp队列了。总结一下,mp类型的队列应该尽量避免,因为当多个生产者同时抢占队列的时候,延时会线性增长。&/p&&p&&b&性能测试:&/b&我写了一个mp类型的广播队列,传输的数据是8字节int,当只有一个生产者时,传输的延时是105(sd26)ns。增加观察者会使延时略微变大,增加生产者会使延时急剧变大(我用rdtsc指令控制不同生产者同时发送消息)。对于这个队列来说,它的延时只略高于跨核可视延时【测试结果见注释8】,所以应该算是不错了。&/p&&br&&p&&b&5.考虑缓存对速度的影响&/b&&/p&&p&&b&相关的背景知识:&/b&现在的机器内存是十分充足的,但是缓存还是很小,因此所有节省内存的技巧都还有用武之地。&/p&&p&&b&可取的做法:&/b&尽量让可能被同时使用的数据挨在一起;减少指针链接(比如用array取代vector,因为链接指向的地方可能不在缓存里);尽量节省内存(比如用unique_ptr&Data[]&取代vector&Data&,比如成员变量按照从大到小排序,比如能用int8的地方就不用int16);指定cpu affinity时考虑LLC缓存(同核的两个超线程是共享L1,同cpu的两个核是共享L3,不同NUMA核是通过QPI总线);会被多个核同时读写的数据按照缓存行对齐(避免false sharing)。&/p&&br&&p&【注释1】:有一篇惠普的论文[Hans-J.Boehm, 2012]大致叙述了顺序锁的实现方法,但是那里面有两点让我感到困惑。一是需要用到thread_fence,这在某些cpu上可能会影响性能(x86似乎没影响);二是被保护的内容也必须是原子变量(可以是多个原子变量,所以被保护的内容可以很长)。但这是我见过的唯一一个符合C++标准的SeqLock的实现。&/p&&p&【注释2】:如果有M个生产者要发消息给N个观察者,可以建M*N个spsc队列和M个内存池,观察者只能读内存池里的数据,只有对应的那一个生产者可以修改内存池。我感觉这样应该会更快,但我没测过。&/p&&p&【注释3】:isolcpus可以隔离出一些cpu,避免其他线程被调度到这些cpu上执行。此外,设置irq affinity可以让一些cpu尽量避免响应中断,但在/proc/interrupts里面仍然有一些项目是避免不了的,而cpu处理中断时,用户程序会有一段时间(有时高达几十微秒)无法响应,我们没法解决这个问题。&/p&&p&【注释4】:在不同的时间点,ping的结果会有很大差异。交易时间段内ping出来的结果是30us,其它时间段ping出来的结果可能是几百微秒。我不知道这是什么原因,可能是期货公司为了省电关掉了某些东西?&/p&&p&【注释6】:我们要在共享内存上使用内存池,所以不得不自己写一个。我写的内存池只能分配固定尺寸的内存块,但是用户可以建立好几个内存池,用来分配不同的尺寸。实现的过程中有两个要点。一是用无锁链表来保存空闲的内存块;二是每个线程内部有一个缓冲区,所以真正取内存块的时候是没有CAS操作的。&/p&&p&【注释7】:在Intel x86的cpu上,如果C++中的内存顺序只用了acquire和release,那么编译出来的汇编代码里面不会有任何内存栅栏指令;如果同时也没有RMW(读-改-写)指令的话,无锁的代码编译出来就会像是普通的代码一样了。事实上,spsc队列的延时几乎等于跨核可视延时。&/p&&p&【注释8】:跨核可视延时:对于一个共享变量来说,如果有一个核上面的进程或者线程修改了这个变量,另一个核需要过一段时间才能看到这个修改,这段时间被称作跨核可视延时。我不确定在这段时间内,第二个核是会看到旧的数据还是这条指令会执行很久。在我的机器上,对于同一个cpu上的不同核心,这个值是96(sd14)ns。另外,对于同一个核心上的不同超线程,这个值应该会更小;对于同一台机器上的不同cpu,这个值应该会更大。&/p&&br&&p&[cambridge, 2016]:&a href=&///?target=http%3A//www.cl.cam.ac.uk/research/srg/netos/projects/ipc-bench/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Computer Laboratory&i class=&icon-external&&&/i&&/a&&/p&&p&[Gabriele Paoloni, 2010]:&a href=&///?target=http%3A///content/www/us/en/embedded/training/ia-32-ia-64-benchmark-code-execution-paper.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Code Execution Times: IA-32/IA-64 Instruction Set Architecture&i class=&icon-external&&&/i&&/a&&/p&&p&[Hans-J.Boehm, 2012]:&a href=&///?target=http%3A//www./techreports/2012/HPL-2012-68.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&/techreports/&/span&&span class=&invisible&&2012/HPL-2012-68.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&p&[sehe, 2014]:&a href=&///?target=http%3A///a/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Shared-memory IPC synchronization (lock-free)&i class=&icon-external&&&/i&&/a&&/p&&p&[tsunanet, 2010]:&a href=&///?target=http%3A//blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&blog.tsunanet.net/2010/&/span&&span class=&invisible&&11/how-long-does-it-take-to-make-context.html&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&
我们的程序的响应时间是10us(从收到行情到发出报单的响应时间),但是ping期货公司的交易前置机需要大约30us【这个数值会变化,见注释4】,所以网络延时占据了大量时间。 我所有的性能测试都是在一台DELL r630机器上运行的,这台机器有2个NUMA结点,CPU型…
了解编程的历史,当然是有用的,而且很有用。 我先对编程的历史做个定义:计算机硬件发展的历史、程序语言发展的历史,计算机科学理论的发展历史、软件工程的发展历史等。&br&&br&我要拿出哲学三大问题镇楼了:从哪里来,到哪里去,是什么?&br&&br&万事万物,都不是凭空产生的,尤其是计算机这门人造的科学。了解历史,可以帮你构建编程领域的世界观,可以让你明白当下所用技术的起源,以及它的出现是为了解决什么问题,从宏观上把握这门技术,可以让自己了解该技术在计算机领域知识体系中的位置,明确这门技术的边界在哪。明白这些问题,可以让你更好的掌握和应用这门技术。了解从哪里来,才能对“是什么”,“到哪里去”有自己的判断,才不至于迷失在技术的细节中。当你做技术选型的时候,你才可以更好的选择适合自己当前场景的那门技术,因为你知道该技术的存在是解决了什么样的问题,适合什么样的场景。&br&&br&看题目的标签,有javascript,就拿javascript举个例子。假如你不了解编程历史,你上来直接学习什么现在最流行的概念,async/await,你确实可以通过示例很快上手,甚至可以用的很溜,但是假如你了解浏览器的演变,事件编程模型的各种进化,你会对这些概念有触及到本质的深刻理解,而不至于迷失于现在前端界风云变幻的各种名词概念中。&br&&br&再比如,现在很多人在吐槽动态语言的性能,虽然这是事实,但是你了解点历史,你完全可以理解那些动态语言为什么那么慢了,省下吐槽的时间来多学点东西。比如Ruby语言,当初Matz在设计他的时候,正是考虑到摩尔定律,认为CPU、内存等硬件这些在未来都不是个事,所以他以牺牲性能来换取程序员可以快乐的编程。而有的语言,从设计之初就是为了内存安全,比如Rust,有的语言天生就是为了高并发,比如erlang和golang,了解这些语言发展史,可以让我们正面的去面对各种语言的缺陷,拥抱它们的优势。&br&&br&针对题主说,“虽然知道,但是感觉无用”,在学习相关历史的时候,你得思考呀,知识需要内化,需要和你现在学习到的内在知识体系挂钩呀。&br&&br&抛砖引玉。谢邀。
了解编程的历史,当然是有用的,而且很有用。 我先对编程的历史做个定义:计算机硬件发展的历史、程序语言发展的历史,计算机科学理论的发展历史、软件工程的发展历史等。 我要拿出哲学三大问题镇楼了:从哪里来,到哪里去,是什么? 万事万物,都不是凭空…
&img src=&/ac61cbf2933a_b.jpg& data-rawwidth=&660& data-rawheight=&250& class=&origin_image zh-lightbox-thumb& width=&660& data-original=&/ac61cbf2933a_r.jpg&&&p&&b&作者简介&/b&&br&&/p&&p&Netfairy,本科在读学生,白帽子,曾在看雪论坛发布过十余篇经典漏洞分析文章。&/p&&p&&b&正文&/b&&/p&&p&
1988年,世界上第一个缓冲区溢出攻击--Morris蠕虫在互联网上泛滥,短短一夜的时间全世界6000多台网络服务器瘫痪或半瘫痪,不计其数的数据和资料被毁。造成一场损失近亿美元的空前大劫难!&/p&&p&
那么,缓冲区溢出到底是怎么一回事呢?&/p&&p&
缓冲区溢出就好比一个杯子倒太多的水,洒出来,这叫溢出。它是一种非常普遍、非常危险的漏洞,最常出现在C/C++编写的程序中。&/p&&p&
早期,人们还不那么重视安全的时候,往往会&strong&使用像strcpy这类不安全函数&/strong&,这种函数&strong&对用户的输入不作任何检查&/strong&,总之,来自不拒,那么,分配的内存空间是有限的,如果输入超长的字符串,必然会导致溢出。&/p&&img src=&/8c615db5fe436d193d5e_b.png& data-rawwidth=&605& data-rawheight=&513& class=&origin_image zh-lightbox-thumb& width=&605& data-original=&/8c615db5fe436d193d5e_r.png&&&p&讲了这么多,小伙伴会不会有点晕吗?其实,缓冲区溢出很容易理解的。下面我们看一个具体的例子:&/p&&blockquote&&p&#include&stdio.h&&/p&&p&#include&string.h&&/p&&p&int main()&/p&&p&{&/p&&p& char str[8];&/p&&p& char input[256];&/p&&p& gets(input);&/p&&p& strcpy(str,input);&/p&&p& return 0;&/p&&p&}&/p&&/blockquote&&p&这段代码存在一个经典的&strong&缓冲区溢出漏洞strcpy&/strong&,为str变量分配了8个字节的空间,输入小于等于8个字符,那没问题:&/p&&img src=&/3fc944fd3d3a790fd3d839_b.png& data-rawwidth=&611& data-rawheight=&295& class=&origin_image zh-lightbox-thumb& width=&611& data-original=&/3fc944fd3d3a790fd3d839_r.png&&&p&但是当我们输入超长的数据时,问题出现了&/p&&img src=&/ee099b95e5_b.png& data-rawwidth=&662& data-rawheight=&478& class=&origin_image zh-lightbox-thumb& width=&662& data-original=&/ee099b95e5_r.png&&&p&&b&缓冲区溢出带来的危害:&br&&/b&&/p&&p&上面是我们自己写的程序,存在漏洞这没什么大惊小怪并不会造成什么恶劣的影响。但是,如果问题出现在我们常用的一些软件,比如操作系统,浏览器,QQ,那后果不堪想象。&/p&&p&缓冲区溢出中,最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时&strong&改变返回程序的地址&/strong&,让其&strong&跳转到任意地址&/strong&,带来的危害一种是&strong&程序崩溃&/strong&导致拒绝服务,另外一种就是跳转并且&strong&执行一段恶意代码&/strong&,比如得到系统权限,然后为所欲为。&/p&&p&利用缓冲区溢出攻击,可以&strong&导致程序运行失败、系统宕机、重新启动&/strong&等后果。更为严重的是,它可被利用来&strong&执行非授权指令&/strong&,甚至可以取得系统特权,进而进行各种非法操作。&/p&&p&有可能小伙伴你会对病毒/蠕虫没有概念,那我们来举一个简单的例子吧。平时玩电脑的时候,你正在听歌,突然间收到未知的“朋友”发给你的一个m3u音频文件,并引导你进行电击——“这首歌很好听呢,你也听听”。&/p&&p&&strong&凑巧的是,你电脑刚好了就装了VUPlayer.exe 这个播放器&/strong&,不多想,就打开这个音乐想听听看,奇怪的是,软件闪了一下就退出了,你以为是文件损坏了了吧。其实,这个时候,你已经被黑了,没错!这就是那个播放器。&/p&&img src=&/296c7cf2bf6db49d8aa71d_b.png& data-rawwidth=&590& data-rawheight=&401& class=&origin_image zh-lightbox-thumb& width=&590& data-original=&/296c7cf2bf6db49d8aa71d_r.png&&&p&很普通的播放器,但是它&strong&存在致命缓冲区溢出漏洞&/strong&。我们构造10000个A的m3u文件,然后用调试器附加VUPlayer.exe,打开文件:&/p&&img src=&/d4d31a57bd055febbd40d8e_b.png& data-rawwidth=&774& data-rawheight=&475& class=&origin_image zh-lightbox-thumb& width=&774& data-original=&/d4d31a57bd055febbd40d8e_r.png&&&p&我们看到下面的内存全部被字符A覆盖了,其实就是使&strong&用了不安全的lstrcpyA函数导致溢出&/strong&,这个是栈溢出,还有一种是堆溢出,但本质是一样的。&/p&&p&证明有溢出漏洞之后,往往需要进一步分析写利用代码,不同的人目的不竟相同,类似我的很多安全爱好者之时弹弹计算器(在桌面上不停地弹出计算器工具),可是黑市上的人就会攻击你的系统,盗取机密文件等等。当然,这只是一个简单的实验。&/p&&p&目前常见的保护措施:&/p&&p& 缓冲区攻击的日渐泛滥,微软并未任其张扬,陆陆续续推出了各种保存措施。其中重要的有GS,SafeSeh,ASLR,DEP等,接下来我们针对这些措施进行原理分析:&/p&&p& ①.GS保护原理:&/p&&p& 通过VC++编译器在函数前后&strong&添加额外的处理代码&/strong&,前部分用于&strong&由伪随机数生成的cookie并放入.data节段&/strong&,当本地变量初始化,就会向栈中插入cookie,它位于局部变量和返回地址之间在缓冲区溢出利用时,如果将恶意代码从局部变量覆盖到返回地址,那么自然就会覆写cookie,当检测到与原始cookie不同时,就会触发异常,最后终止进程。&/p&&p& ②.SafeSeh保护&/p&&p& 为了防止SEH节点被攻击者恶意利用,微软在.net编译器中&strong&加入/sdeseh编译选项引入SafeSEH技术&/strong&。编译器在编译时将PE文件所有合法的异常处理例程的地址解析出来制成一张表,放在PE文件的数据块(LQAJ)一C0N—FIG)中,并使用shareuser内存中的一个随机数加密,用于匹配检查。&/p&&p& 如果该PE文件不支持safesEH,则表的地址为0。当PE文件被系统加载后,表中的内容被加密保存到ntdl1.dll模块的某个数据区。在PE文件运行期间,如果发生异常需要调用异常处理例程,系统会逐个检查该例程在表中是否有记录:如果没有则说明该例程非法,进而不执行该异常例程。&/p&&p&③.ASLR保护&/p&&p& ASLR(地址空间布局随机化)技术的主要功能是通过&strong&对系统关键地址的随机化&/strong&,防止攻击者在堆栈溢出后利用固定的地址定位到恶意代码并加以运行。&/p&&p&④.DEP保护&/p&&p& 数据执行保护 (DEP) 是一套软硬件技术,能够&strong&在内存上执行额外检查&/strong&以防止在不可运行的内存区域上执行代码&/p&&p&这些保护机制的出现,一度使得攻击难度大大增加,但攻击者们也不是吃素的,他们也在研究绕过这些保护的办法。所谓千里之堤溃于蚁穴,对于上面的保护,攻击者只要找到了它们的一个弱点,便可以突破所有防线,实现攻击。&strong&攻击与防护,不断在对抗&/strong&,可以预见,在未来一段时间,这种游戏还将持续上演。&/p&&p&但是,现在的漏洞门槛比十年前高了N倍,笔者在实际中也常常遇到有漏洞却不能利用的情况。但是也不要灰心,老的技术被淘汰,新的技术又会出来,本属正常,但像缓冲区溢出这种经典的东西,无论什么时候,都值得我们学习。&/p&&p&总结:&/p&&p&目前来说,&strong&底层的漏洞缓冲区溢出还是占据较大比例&/strong&,研究的人员相对于web来说少的可怜,主要是门槛比较高,但是学好了,回报也很高,所以骚年,不要犹豫,拿起你的电脑,跟我一起改变世界。&/p&&p&读完了本文,小伙伴针对缓冲区溢出攻击的防御,你有什么别的思路呢?评论区留言,对该思路讨论点赞数最高者,有可爱的超人兔公仔抱回家哦。(正好明天七夕,送给女朋友还是留着自己玩都是不错的哦)&/p&&p&&strong&其他推荐:&/strong&&br&&/p&&p&&strong&1、&/strong&&a href=&/?target=http%3A//mp./s%3F__biz%3DMzIwMTQ2NzY4NA%3D%3D%26mid%3D%26idx%3D1%26sn%3D842ece66f0f%26scene%3D21%23wechat_redirect& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&重磅分享 | 白帽子黑客浅谈顾问式销售与服务&i class=&icon-external&&&/i&&/a&&/p&&p&&strong&2、&/strong&&a href=&/?target=http%3A//mp./s%3F__biz%3DMzIwMTQ2NzY4NA%3D%3D%26mid%3Didx%3D1%26sn%3Db2778cafdee9b1a7f63eb1fc75025fdc%26scene%3D21%23wechat_redirect& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&安全观点:企业信息安全十大痛点,你中招了?&i class=&icon-external&&&/i&&/a&&/p&&p&&strong&3、&/strong&&a href=&/?target=http%3A//mp./s%3F__biz%3DMzIwMTQ2NzY4NA%3D%3D%26mid%3Didx%3D1%26sn%3Dcf6cb8a5573a11afa0a315ad4bebba13%26scene%3D21%23wechat_redirect& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&招人必看!301浅谈国内安全人才薪酬现状&i class=&icon-external&&&/i&&/a&&/p&&p&&strong&4、&/strong&&a href=&/?target=http%3A//mp./s%3F__biz%3DMzIwMTQ2NzY4NA%3D%3D%26mid%3D%26idx%3D1%26sn%3Df29be84d7fe449escene%3D21%23wechat_redirect& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&白帽黑客成长独白:301消失的那几年&i class=&icon-external&&&/i&&/a&&/p&&p&&strong&5、&/strong&&a href=&/?target=http%3A//mp./s%3F__biz%3DMzIwMTQ2NzY4NA%3D%3D%26mid%3D%26idx%3D1%26sn%3D39ff41af1dc1e761f593bec52e51c870%26scene%3D21%23wechat_redirect& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&邻居说WiFi安全不重要,结果被“黑”傻了&i class=&icon-external&&&/i&&/a&&/p&&p&&strong&6、&/strong&&a href=&/?target=http%3A//mp./s%3F__biz%3DMzIwMTQ2NzY4NA%3D%3D%26mid%3D%26idx%3D1%26sn%3D3943ebdb52ef287eb3bde%26scene%3D21%23wechat_redirect& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&美女iPhone丢了后,黑客跟妹子干了这样一件事&i class=&icon-external&&&/i&&/a&&/p&&p&&strong&7、&/strong&&a href=&/?target=http%3A//mp./s%3F__biz%3DMzIwMTQ2NzY4NA%3D%3D%26mid%3D%26idx%3D1%26sn%3D48dc75d93bbee235b6e234%26scene%3D21%23wechat_redirect& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&不看后悔:为什么企业招不到安全人才?你知道吗?&i class=&icon-external&&&/i&&/a&&/p&&p&&strong&8、&/strong&&a href=&/?target=http%3A//mp./s%3F__biz%3DMzIwMTQ2NzY4NA%3D%3D%26mid%3D%26idx%3D1%26sn%3D6fbb1ac6ccf9e096c3da329%26scene%3D21%23wechat_redirect& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&我有笔钱,想给你。&i class=&icon-external&&&/i&&/a&&/p&&p&&b&更多精彩内容请关注微信公众号:301在路上&/b&&/p&
作者简介 Netfairy,本科在读学生,白帽子,曾在看雪论坛发布过十余篇经典漏洞分析文章。正文 1988年,世界上第一个缓冲区溢出攻击--Morris蠕虫在互联网上泛滥,短短一夜的时间全世界6000多台网络服务器瘫痪或半瘫痪,不计其数的数据和资料被毁。造成一场损…
&img src=&/v2-dbaac341fb_b.png& data-rawwidth=&1920& data-rawheight=&640& class=&origin_image zh-lightbox-thumb& width=&1920& data-original=&/v2-dbaac341fb_r.png&&&p&昨天花了一天的时间试着写了一下JPEG解码器,虽然遇到了一些麻烦,不过最后还是成功地解出了图像的数据。总共400行代码左右。&/p&&img src=&/v2-d5476157abd906c5e2c56caea50b7ac3_b.png& data-rawwidth=&623& data-rawheight=&554& class=&origin_image zh-lightbox-thumb& width=&623& data-original=&/v2-d5476157abd906c5e2c56caea50b7ac3_r.png&&&p&&i&(解码的核心也就这50行左右,因为只是验证一下以前了解的信息,所以看起来还是有点乱,而且没有做各种错误处理,所以不要太在意啦┑( ̄Д  ̄)┍)&/i&&/p&&p&下面是JPEG的编码过程的整理(因为编码和解码是完全对称的所以就只说编码,解码可以自行脑补)(仔细看的话都能在上面那段代码中找到对应哦):&/p&&img src=&/v2-5763bdafe3b99a11890cde_b.png& data-rawwidth=&373& data-rawheight=&920& class=&content_image& width=&373&&&br&&p&&i&(图片自制)&/i&&br&&b&RGB转YCbCr&/b&:因为人眼对亮度比较敏感,而对于色度不那么敏感,所以,我们就先将RGB的数据转换到YCbCr色彩空间,便于下面的处理。&/p&&br&&p&&b&降采样&/b&:转到YCbCr色彩空间后,就可以将 Cb 和 Cr 这两个通道进行降采样,这里一般是将 2*2 个像素变为 1*1 个像素,虽然分辨率下降到了四分之一,但对于人眼来说差别是不大的。(这一步是有损的)&/p&&p&&b&分块&/b&:顾名思义,将图像分为若干个 8*8 的小块,方便下面的处理。&/p&&p&&b&DCT&/b&:这一步的目的和RGB转YCbCr有一些相似之处,都是将人眼较为敏感和不敏感的部分进行分离,然后就可以对减少人眼不敏感的部分的信息量。在这一步中,DCT可以将图像的低频(人眼敏感)和高频(人眼不敏感)部分进行分离。这样得到的结果是每个 8*8 小块得到 8*8 的系数矩阵。&/p&&br&&p&&b&量化&/b&:将DCT后得到的每个系数都除以量化矩阵中对应的值,然后进行取整。通常来说频率较高的部分对应的量化参数比较大,这样一来就能够在较好地保留图像的低频部分并去除一些高频部分。这一步下来得到的矩阵中高频部分几乎全部变为0,这也为进一步的操作提供了便利。值得注意的是,JPEG中压缩率的调整是在这一步中,量化参数越大,压缩后的大小就会越小,但信息的损失也就越多,图片的失真也会更严重。(这一步是有损的)&/p&&p&&b&Huffman编码&/b&:准确地说是Huffman编码和RLE,将上一步得到的矩阵进行进一步地压缩(这一步是无损的)。量化后得到的矩阵左上角的那个数比其他数来得大得多,所以我们将它单独拿出来进行编码,称之为直流分量(DC),将剩下的称之为交流系数(AC)。这一步会将矩阵按照 zigzag 的顺序摊成一维,如下图所示:&/p&&img src=&/equation?tex=%5Cleft%5B%0A%5Cbegin%7Bmatrix%7D%0AAC+%26+DC_%7B1%7D+%26+DC_%7B5%7D+%26+DC_%7B6%7D+%26+DC_%7B14%7D+%26+DC_%7B15%7D+%26+DC_%7B27%7D+%26+DC_%7B28%7D+%26+%5C%5C%0ADC_%7B2%7D+%26+DC_%7B4%7D+%26+DC_%7B7%7D+%26+DC_%7B13%7D+%26+DC_%7B16%7D+%26+DC_%7B26%7D+%26+DC_%7B29%7D+%26+DC_%7B42%7D+%26+%5C%5C%0ADC_%7B3%7D+%26+DC_%7B8%7D+%26+DC_%7B12%7D+%26+DC_%7B17%7D+%26+DC_%7B25%7D+%26+DC_%7B30%7D+%26+DC_%7B41%7D+%26+DC_%7B43%7D+%26+%5C%5C%0ADC_%7B9%7D+%26+DC_%7B11%7D+%26+DC_%7B18%7D+%26+DC_%7B24%7D+%26+DC_%7B31%7D+%26+DC_%7B40%7D+%26+DC_%7B44%7D+%26+DC_%7B53%7D+%26+%5C%5C%0ADC_%7B10%7D+%26+DC_%7B19%7D+%26+DC_%7B23%7D+%26+DC_%7B32%7D+%26+DC_%7B39%7D+%26+DC_%7B45%7D+%26+DC_%7B52%7D+%26+DC_%7B54%7D+%26+%5C%5C%0ADC_%7B20%7D+%26+DC_%7B22%7D+%26+DC_%7B33%7D+%26+DC_%7B38%7D+%26+DC_%7B46%7D+%26+DC_%7B51%7D+%26+DC_%7B55%7D+%26+DC_%7B60%7D+%26+%5C%5C%0ADC_%7B21%7D+%26+DC_%7B34%7D+%26+DC_%7B37%7D+%26+DC_%7B47%7D+%26+DC_%7B50%7D+%26+DC_%7B56%7D+%26+DC_%7B59%7D+%26+DC_%7B61%7D+%26+%5C%5C%0ADC_%7B35%7D+%26+DC_%7B36%7D+%26+DC_%7B48%7D+%26+DC_%7B49%7D+%26+DC_%7B57%7D+%26+DC_%7B58%7D+%26+DC_%7B62%7D+%26+DC_%7B63%7D+%26+%5C%5C%0A%5Cend%7Bmatrix%7D%0A%5Cright%5D+%0A%5Crightarrow+%0A%5Cleft%5B+AC+%5Cright%5D%0A%5Cleft%5B+DC_%7B1%7D%2C+DC2%2C+DC3%2C+...+%5Cright%5D+& alt=&\left[
\begin{matrix}
AC & DC_{1} & DC_{5} & DC_{6} & DC_{14} & DC_{15} & DC_{27} & DC_{28} & \\
DC_{2} & DC_{4} & DC_{7} & DC_{13} & DC_{16} & DC_{26} & DC_{29} & DC_{42} & \\
DC_{3} & DC_{8} & DC_{12} & DC_{17} & DC_{25} & DC_{30} & DC_{41} & DC_{43} & \\
DC_{9} & DC_{11} & DC_{18} & DC_{24} & DC_{31} & DC_{40} & DC_{44} & DC_{53} & \\
DC_{10} & DC_{19} & DC_{23} & DC_{32} & DC_{39} & DC_{45} & DC_{52} & DC_{54} & \\
DC_{20} & DC_{22} & DC_{33} & DC_{38} & DC_{46} & DC_{51} & DC_{55} & DC_{60} & \\
DC_{21} & DC_{34} & DC_{37} & DC_{47} & DC_{50} & DC_{56} & DC_{59} & DC_{61} & \\
DC_{35} & DC_{36} & DC_{48} & DC_{49} & DC_{57} & DC_{58} & DC_{62} & DC_{63} & \\
\end{matrix}
\rightarrow
\left[ AC \right]
\left[ DC_{1}, DC2, DC3, ... \right] & eeimg=&1&&&br&&p&这样做的好处是矩阵的高频部分(右下角)会被安排在后面,因为它们基本上全是0,所以在编码中有一个特殊的标记表示这之后的系数全是0,从而减少压缩后的大小。&/p&&p&(话说回来,Huffman解码这一步应该是解码中最麻烦的一部分……位的顺序搞了好久)&br&&/p&&p&上面这些步骤完成之后,再加上一些必要的其他信息(量化矩阵和Huffman的信息,这两个是直接没有压缩就存储在文件中的),就得到了最终的JPEG数据。&/p&&p&暂时先整理这么多,以后有时间再认真写一下解码器,争取某一天能够达到 libjpeg 的水平吧 :) (以后应该会包含在 Medicat 中←某个妄图把各种图像/音频格式都自己实现一遍的轮子)&/p&&br&&br&&p&参考:&/p&&a href=&/?target=https%3A//www.w3.org/Graphics/JPEG/itu-t81.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&w3.org/Graphics/JPEG/it&/span&&span class=&invisible&&u-t81.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&&a href=&/?target=https%3A//en.wikipedia.org/wiki/JPEG& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&en.wikipedia.org/wiki/J&/span&&span class=&invisible&&PEG&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&
昨天花了一天的时间试着写了一下JPEG解码器,虽然遇到了一些麻烦,不过最后还是成功地解出了图像的数据。总共400行代码左右。(解码的核心也就这50行左右,因为只是验证一下以前了解的信息,所以看起来还是有点乱,而且没有做各种错误处理,所以不要太在意…
&img src=&/v2-bb59ebbc16cf1ae15d4a7f4ff54dbba2_b.jpg& data-rawwidth=&500& data-rawheight=&306& class=&origin_image zh-lightbox-thumb& width=&500& data-original=&/v2-bb59ebbc16cf1ae15d4a7f4ff54dbba2_r.jpg&&&p&上文(&a href=&/p/& class=&internal&&杂谈闪存二:NOR和NAND Flash - 知乎专栏&/a&)提到NAND flash管理的核心FTL(Flash Translation Layer)。事实上几乎所有的应用NAND Flash的设备都必须配备FTL,包括我们经常碰到的SD, eMMC, UFS, SSD等等。通常FTL由这些设备的固件提供实现。我们来深入了解下什么是FTL吧。&/p&&h2&&strong&NAND flash的组成&/strong&&/h2&&p&一个典型的Flash芯片由Package, die, plane, block和page组成,如下图:&img src=&/v2-ab8173eacba_b.png& data-rawwidth=&494& data-rawheight=&363& class=&origin_image zh-lightbox-thumb& width=&494& data-original=&/v2-ab8173eacba_r.png&&&/p&&p&&strong&Package&/strong&: 也就是chip即Flash芯片,就是我们经常在M.2的SSD上看到的NAND flash颗粒:&img src=&/v2-9b9ce865a33434bea03245_b.jpg& data-rawwidth=&453& data-rawheight=&300& class=&origin_image zh-lightbox-thumb& width=&453& data-original=&/v2-9b9ce865a33434bea03245_r.jpg&&&/p&&p&&strong&Die&/strong&: 一个NAND颗粒是由一颗或者多颗Die封装在一起而成,这种封装可是平排的,也可以是层叠的。die内部可以通过3D 堆叠技术扩展容量,譬如三星的V-NAND每层容量都有128Gb(16GB),通过3D堆叠技术可以实现最多24层堆叠,这意味着24层堆叠的总容量将达到384GB!就像盖楼房一样:&img src=&/v2-fd08cc8d75a7e1e2f9be6e2d55579ba5_b.jpg& data-rawwidth=&600& data-rawheight=&293& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-fd08cc8d75a7e1e2f9be6e2d55579ba5_r.jpg&&&/p&&p&Die也是可以单独执行命令和返回状态的最小单位。&/p&&p&&strong&Plane&/strong&: 一个die可以包含几个Plane.&/p&&p&&strong&Block&/strong&: 重要的概念,它是擦除操作的最小单位。&/p&&p&&strong&Page&/strong&:也很重要,它是写入动作的最小单位。&/p&&h2&&strong&FTL&/strong&&/h2&&p&当我们有了块“干净”的Flash,我们第一个想干的事就是写些东西上去,无论我们是写一个byte还是很多东西,必须以page为单位,即写一个byte上去也要写一个page。当我们满意的写了东西上去后,如何修改呢?是不是可以再写一次呢?可惜的是,我们的program操作只能把bit 1改成0,而不能从0改成1. 即我们可以把改成,但不能改成。即有点像逻辑and操作(我们可以通过NAND这个名字联想记忆一下)。那么如何把0改回1呢?这就要靠擦除操作了,但是擦除操作只能在block的上面操作,如果我们把整块block上的所有page都读回RAM,改动后再擦除再写回去(也叫read-modify-write),似乎问题就完美解决了。但是别忘了,我们前文中提到,NAND flash的寿命是由其擦写次数决定的(P/E数 (Program/Erase Count)来衡量的),频繁的擦除慢慢的会产生坏块。那么我们如何才能平衡整块flash的整体擦写次数呢?这就要我们的FTL登场了。&/p&&b&1。FTL原理&/b&&p&FTL简单来说就是系统维护了一个逻辑Block地址(LBA,logical block addresses )和物理Block地址(PBA, physical block addresses)的对应关系, 如图:&img src=&/v2-a1724e7bc5_b.jpg& data-rawwidth=&300& data-rawheight=&178& class=&content_image& width=&300&&&/p&&p&有了这层映射关系,我们需要修改时就不需要改动原来的物理块,只需要标记原块为废块,同时找一个没用的新物理块对应到原来的逻辑块上就好了。问题解决了!实际情况比这个稍微复杂些,我们需要解决以下问题:&/p&&p&&strong&A.&/strong& LBA和PBA究竟是对应page还是block?&/p&&p&&strong&B.&/strong& 如何做到寿命均衡?&/p&&p&&strong&C.&/strong& 废块何时回收?&/p&&p&&strong&D.&/strong& 这个逻辑对应表存储到哪里?&/p&&p&&b&2。LBA/PBA的颗粒度问题&/b&&/p&&p&如果我们把LBA和PBA都设置成page大小,这也叫做page-level mapping,这和我们写入颗粒一致,很灵活。但这么小的粒度会带来一个问题:逻辑对应表太大!想象一下,我们有个64GB的Die,每个page只有4KB,我们的对应表需要64* * 4 /4 = 64MB!这个绝对不能接受。那么按照block,也就是block-level mapping呢? 会不会好些呢?至少占用空间好了很多,但是因为写入时不清楚page情况,往往要整块擦除,效能会大大下降。有没有更好的办法呢?实际使用中常用的是log-block mapping,它从我们现代的log文件系统中学到经验,也充分利用了page和block的不同特性。它比较复杂,我们这里按下不表。&/p&&p&&b&3。如何做到寿命均衡(Wear Levelling)&/b&&/p&&p&LBA/PBA的映射本身会对寿命均衡产生正面影响。就如我们SD卡上的FAT文件系统,文件分配表会被经常修改,但由于修改的是逻辑块,我们可以让每次物理块不同而避免经常擦写相同的物理块,这本身就保证不会有物理块被经常擦写。但是有一种情况它没有办法处理,即冷的数据块(cold block),它们被写入后没有更改,就一直占据某些物理块,而这些物理块寿命还很长,而别的热的块却在飞速损耗中。这种情况怎么办呢?我们只有在合适的时机帮它们换个位置了,如何选择这个时机很重要,而且这个搬家动作本身也会损耗寿命本身。这些策略也是各个FTL算法的精华了。&/p&&p&&b&4。垃圾回收&/b&&/p&&p&废块需要找时机回收,这就需要垃圾回收(GC,Garbage Collection)机制。&img src=&/v2-3f5f5ed652f55_b.jpg& data-rawwidth=&300& data-rawheight=&220& class=&content_image& width=&300&&&/p&&p&现在到处都会碰到GC,各种高级语言都在炫耀自己有GC,不像C++/C那样要手动回收内存。这里的GC是指回收废块,但是时机非常重要,谁都不希望看到一个存盘后,SSD硬盘固件发现没有干净的块可用,开始整盘GC,就像开始了一个没有进度条的磁盘整理,系统被完全卡死了。聪明的固件往往未雨绸缪,在我们不知不觉下在后台悄悄做垃圾回收,不少固件还同时进行Wear Levelling。&/p&&p&&b&5。LBA/PBA表存储在哪里&/b&&/p&&p&这个表随时可以变化,我们把它存储在哪里呢?是不是也找个page存起来呢?事实上,在大部分的NAND Flash里,还有些空闲块,我们叫它OP(Over Provisioning)。如下图:&img src=&/v2-d9edd616036eaa8970f1dfa_b.jpg& data-rawwidth=&457& data-rawheight=&362& class=&origin_image zh-lightbox-thumb& width=&457& data-original=&/v2-d9edd616036eaa8970f1dfa_r.jpg&&&/p&&p&这些空闲的块可以极大的帮助我们改善NAND flash的性能,它可以:&/p&&p&&strong&A.&/strong& 坏块处理。发现坏块,这些后备的可以立刻顶上,因为有映射机制,上层软件完全感受不到。&/p&&p&&strong&B.&/strong& 存储LBA/PBA表&/p&&p&&strong&C.&/strong& 给GC和Wear Levelling留下极大的腾挪空间。&/p&&p&&b&D&/b& . 减少写入放大(Write Amplification)&/p&&p&事实上,现在几乎所有主流SSD等NAND die上都有OP。譬如我们拿到标称容量240GB的SSD,实际空间可能有256GB甚至更高(一般&7.37%),只不过这些多余的空间我们用不到,感受不到,它完全被SSD固件藏做私用而已。&/p&&h2&&strong&尾声&/strong&&/h2&&p&现在能生产NAND芯片的厂家在Wikipedia上能查出11家,而能生产主控芯片和固件的就四家。他们各自的绝活都在FTL中的各种调优上,再加上Trim等等特性,这也就决定了SSD硬盘性能的好坏。&/p&&p&下一篇我们会介绍现有各种NAND Flash存储媒体的历史和区别,以及接口的不同,如SATA,M.2,U.2,NVMe等等。敬请关注。&/p&&p&闪存系列其他文章:&/p&&a href=&/p/& class=&internal&&杂谈闪存二:NOR和NAND Flash - 知乎专栏&/a&&br&&a href=&/p/& class=&internal&&杂谈闪存三:FTL - 知乎专栏&/a&&br&&a href=&/p/& class=&internal&&杂说闪存四:闪存硬盘接口大比拼 - 知乎专栏&/a&&p&欢迎大家关注微信公众号&UEFIBlog&加入讨论,同时欢迎大家给本专栏和公众号投稿!&/p&
上文()提到NAND flash管理的核心FTL(Flash Translation Layer)。事实上几乎所有的应用NAND Flash的设备都必须配备FTL,包括我们经常碰到的SD, eMMC, UFS, SSD等等。通常FTL由这些设备的固件提供实现。我们来深入…
&img src=&/v2-6a90b2cbb96a80ea2a8d12_b.png& data-rawwidth=&550& data-rawheight=&758& class=&origin_image zh-lightbox-thumb& width=&550& data-original=&/v2-6a90b2cbb96a80ea2a8d12_r.png&&&p&
如果我们找一个程序员问他“什么是内存”,我想不太容易得到满意的答案,这是一个太大的话题,并且对不同领域的编程人员而言,理解千差万别。比如Java程序员可能会想起来讨厌的GC参数,C++程序员可能想起来线上OOM、crash的经历,内核程序员可能想起来怎么绞尽脑汁增加cache命中率、减少不同core之间的交互,内存控制器的设计者可能会想到增加频率、提高带宽、减少纠错代价,而内存颗粒的制造者可能在想怎么才能增加密度,降低功耗。最普通的就是最难定义的。&br&&/p&&p&
作为一个使用C++开发分布式NoSQL数据库的程序员,我对内存的理解是偏软件的角度,也就是操作系统包装之后的内存,比如虚拟内存,物理内存,地址翻译缓存,swap影响性能,OOM会挂,无效访问会crash,各种内存分配器,page cache等,这些当然是有用的,但是当我想进一步了解每次内存访问发生了什么,仍然是一头雾水。后来一个朋友提到了《What Every Programmer Should Know About Memory》,作者是Redhat公司的Ulrich Drepper,我读后觉得写的非常棒,做一些笔记。这篇文章写的有点早,所以有些技术不是最新的了,但是基础框架是类似的,并不影响理解。&br&&/p&&p&&strong&Page-0 内存长什么样子&/strong&&br&&/p&&p&
内存条具体的样子见图1,有过攒机经验的肯定都摆弄过内存条,甚至出问题时候还自己动手拔出来用橡皮清理一下,当然,一般情况下都是解决不了问题的。图1只展示了内存条的一面,另外一面是一样的,当然也有只有一面的的内存条,不过我没见过。&/p&&img src=&/v2-373aeb8bba544d5f3a33331_b.jpg& data-rawwidth=&893& data-rawheight=&529& class=&origin_image zh-lightbox-thumb& width=&893& data-original=&/v2-373aeb8bba544d5f3a33331_r.jpg&&&br&&p&图1 内存条实物[2]&/p&&p&
接下来要肢解内存,并学习几个名词。图2中展示了内存条的学名,Dual Inline Memory Module,简称DIMM。每一面称为一个rank,所以两面就有两个rank。每个rank里面包含若干个bank组(内存颗粒,图中标128MB的黑方块),每个bank组包含多个bank(有时候也叫逻辑Bank,Logical Bank,L-Bank)。每个bank可以被认为是一个表格,表格里面的cell存储bit,软件对数据的读写就是操作这样的一个个cell。具体来说,图2中展示每个bank组包含8个逻辑bank,而每个逻辑bank的行是16384,列是1024 x 8(其实就是8196列,x 8的意思是bank位宽是8,每次访问返回8bit数据,这样列地址线也省了3根),则容量是16384 x 1024 x 8 = 128Mb = 16MB,再乘以每个bank组包含8个bank,所以每个bank组容量是128MB,内存条两面共16个逻bank组,容量为128MB * 8 * 2 = 2GB。&/p&&p&
关于rank:这里的叫法不一,也有的文章称为Physical Bank(P-Bank),如果这么称呼,一般上面说到的bank就被称为Logical Bank。本质是CPU要求内存位宽和自己对齐,比如CPU位宽一般是64bit,但是典型的内存位宽是8bit,所以就像图2中展示的一样,CPU一次访问需要同时访问8个逻辑bank组(一面正好8个),每个逻辑bank组中按需挑选一个提供读写服务(一次只能访问一个bank组中的一个bank,因为一个bank组共享地址线),这些都是内存控制器做的。&/p&&p&
关于内存位宽:首先CPU已经限制死出口总位宽,比如是64bit,此时内存位宽跟容量就是一个可以trace off的地方,比如选择位宽为8bit的内存bank,则可以放8个bank组;而如果选择位宽为16bit的内存bank,只能放4个bank组;一个bank的容量是受控的,这就意味着如果选择高位宽的bank,则总容量会下降,但是会换来性能上的提升。如果内存自带Error Correction Code(ECC)功能,则每个内存位宽会大于8bit,比如9bit,多余的bit用来做校验。&/p&&p&&img src=&/v2-6a90b2cbb96a80ea2a8d12_b.png& data-rawwidth=&550& data-rawheight=&758& class=&origin_image zh-lightbox-thumb& width=&550& data-original=&/v2-6a90b2cbb96a80ea2a8d12_r.png&&图2 内存条粗略肢解[3]&br&&/p&&p&&strong&Page-1 内存的分类&/strong&&/p&&p&
准确的说应该是内存颗粒的分类,内存条只是内存颗粒的集合并提供一些共享组件。内存颗粒可以等效于课本上说的RAM(Random Access Memory),从软件的角度,我们一般情况下认为可以按照字节访问就叫做随机了,其实内存里面每个bit都可以独立操作,不过这样做需要的地址空间会很大,带来的好处不明显,所以没听说过谁这么干。内存可以按照多种方式分类,技术上看比较有意义的分类是SRAM(Static RAM)和DRAM(Dynamic RAM)。这里Static和Dynamic我认为本质上是说其cell里面晶体管状态的稳定性,相关的cell的逻辑见图3和图4。&/p&&p&
SRAM和DRAM对比如下:&/p&&ol&&li&&p&SRAM使用多个晶体管(transistor)表示一个bit,一般是6;DRAM使用一个晶体管和一个电容(capacitor)表示一个bit。显然SRAM的密度是低于DRAM的,成本也会更高&/p&&/li&&li&&p&SRAM只要持续、稳定的电压维持cell的状态,不需要周期性充电;DRAM电容的电量会泄露,漏电到一定程度数据就丢失了,需要定期充电,比如一般内存条上写的4096/64ms意思就是在64millon-seconds内会刷新每个bank里面的4096行cell(多个bank并行)。SRAM耗电远低于DRAM,而且因为SRAM比较贵,一般容量也不大,耗电问题不明显;而DRAM则越来越大,这样定期刷新充电所消耗的能量也将越来越大&/p&&/li&&li&&p&SRAM读写流程简单,开关打开立刻就可以读写;而DRAM读写流程极其复杂(下面会有流程描述),也就导致了性能差;一般来说CPU cache、交换机内存使用SRAM,而我们常说的台式机、服务器内存使用DRAM,主要是成本和容量的考虑&/p&&/li&&/ol&&br&&p&&img src=&/v2-ec07586dc9eae8c98f552c1_b.png& data-rawwidth=&466& data-rawheight=&388& class=&origin_image zh-lightbox-thumb& width=&466& data-original=&/v2-ec07586dc9eae8c98f552c1_r.png&&图3 DRAM cell [1]&br&&/p&&img src=&/v2-e_b.png& data-rawwidth=&1074& data-rawheight=&768& class=&origin_image zh-lightbox-thumb& width=&1074& data-original=&/v2-e_r.png&&&p&图4 SRAM cell [1]&br&&/p&&br&&p&&strong&Page-2 DRAM读写流程&/strong&&/p&&p&
当我们写一段代码,读写一个变量的时候,我们可能不太有多大的感觉,认为世界就是这样的,但是站在内存控制器的角度,读写一个变量是一段长长的旅行。弄明白这个流程有助于我们设计性能更好的软件,也许有些时候对debug也有帮助。&/p&&img src=&/v2-3a309b810a5b932100ae_b.png& data-rawwidth=&1644& data-rawheight=&1178& class=&origin_image zh-lightbox-thumb& width=&1644& data-original=&/v2-3a309b810a5b932100ae_r.png&&&p&图5 CPU读写内存示意[1]&/p&&p&
图5展示了CPU从内存读数据的流程,我们可以看到CPU将向L1/L2/L3逐步查找该地址,如果全部没有找到则内存控制器会读写主存,内存控制器按照既定的策略做好地址转换,然后开始读写SDRAM。到这里已经是一个很复杂的流程,然而下面列举的是SDRAM内部的细节,也就是图5最上面的主存(Main Memory)访问的一个细节部分。注意图5没有描绘多CPU情况下的一致性检查流程。&/p&&img src=&/v2-287d05f5819e89ece1fc434e1de042c1_b.png& data-rawwidth=&1722& data-rawheight=&1084& class=&origin_image zh-lightbox-thumb& width=&1722& data-original=&/v2-287d05f5819e89ece1fc434e1de042c1_r.png&&&p&图6 SDRAM访问的时序[1]&/p&&p&
SDRAM访问时序如下,见图6:&/p&&ol&&li&&p&内存控制器将CPU传过来的地址翻译成行地址和列地址(就是上面提到的bank的行、列索引)&/p&&/li&&li&&p&行地址先传送到地址总线上,发信号说行地址准备好了&/p&&/li&&li&&p&等待T(RCD)个时钟周期,T(RCD)等待周期是电气特性决定的,无法跳过,一般是3,是不是开始对物理有感觉了?&/p&&/li&&li&&p&将列地址传送到地址总线,发信号说列地址准备好了&/p&&/li&&li&&p&等待T(CL)个时钟周期,T(CL)等待周期是电气特性决定的,无法跳过,一般是2&/p&&/li&&li&&p&数据开始出现在数据总线上,可以读取&/p&&/li&&li&&p&上面说的是正常情况,如果需要换行读取,即行地址改变,那么就要close当前行,open另外一行,会有一个precharge的过程,一般是2个时钟周期;另外,上面说的每隔64ms刷新一次所有行,如果读取时候恰好碰到在刷新,也要等待。&/p&&/li&&/ol&&p&
可以看到,每次启动内存访问需要数个时钟周期,而且这只是一个SDRAM内部启动数据读取的时间,不包括数据传输时间,内存控制器、FSB总线(内存控制器集成到CPU后就没了,访问非直连内存通过QPI进行[7])上的任何竞争。因此为了提升访问内存的效率,一般每次访问内存会将连续的数据都取出来,比如每次取一个cache line(比如64B)上来,还记得那个矩阵计算可能导致巨大性能差异的面试题吗?向深了问就可以给面试官这个了。还有一个需要注意的问题是,这里的时钟周期计算要使用SDRAM的时钟频率来计算,而不是使用CPU或者内存控制器的时钟频率,SDRAM的时钟频率一般不高,比如200M、400M,那么数十个时钟周期还是比较可观的。&/p&&br&&p&&strong&Page-4 DRAM的演进&/strong&&/p&&p&
从上面的时序可以看到,SDRAM每个周期只能传输1个字长(典型8bit),DDR(Double Data Rate) SDRAM做了改进,这样在时钟的上升沿和下降沿均可以传输数据,每个时钟周期传输2个字,将带宽提高了一倍,不过延时没有变化。DDR2对时序进行了精简,并延长了读写的周期,这样每个周期可以传输4个字,再次提高了一倍带宽,显然,DDR2提高了延时。DDR3沿着老路继续,读写周期再次被延长,带宽再次翻倍,延时再次增加。为什么DDRx延时一直增加我们没有感知?大部分时候是因为内存容量也上去了,对整体系统来说,性能仍然提升了。DDR4也出来了,其没有继续走老路,而是靠提升频率和并行度来继续增加带宽,同时保持延时类似。同时随着DDR的演进,其需要的电压也逐渐降低,这可以节约部分能源消耗。&/p&&p&
从上面的演进,其实我们更能认识到延时和带宽的不同,对于软件系统来说就是Response Time和Throughput的不同,虽然某种程度上他们是相关的,但是我们还是要清晰的认识到这两者的差异,并在做系统优化的时候弄清楚核心目标是优化延时还是优化带宽,甚至两个都不要,只要把优先级做好就行了。&/p&&br&&p&[1]. What Every Programmer Should Know About Memory &/p&&p&[2]. 图片1:&a href=&/?target=http%3A///jp/wp-content/uploads/dram-memory-modules-7.jpg& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/jp/wp-conten&/span&&span class=&invisible&&t/uploads/dram-memory-modules-7.jpg&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&p&[3]. 图片2:&a href=&/?target=http%3A///doci/3851/DRAM%2520Memory%2520Topology.png& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/do&/span&&span class=&invisible&&ci/3851/DRAM%20Memory%20Topology.png&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&p&[4]. rank/bank概念:&a href=&/?target=http%3A///icview-.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&请问,在DRAM 中 BANK 和 RANK 的区别~~~~~~&i class=&icon-external&&&/i&&/a&&/p&&p&[5]. rank/bank概念:&a href=&/?target=http%3A//lichunlong2008./blog/static//& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&内存上面的标识解读(Memory Rank Single Rankx4)&i class=&icon-external&&&/i&&/a&&/p&&p&[6]. CPU集成内存控制器: &a href=&/?target=http%3A///UploadFile//3494110.jpg& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&/UploadFile/20&/span&&span class=&invisible&&10/8/20/3494110.jpg&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&br&&p&[7]. &a href=&/?target=https%3A//en.wikipedia.org/wiki/Intel_QuickPath_Interconnect& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Intel QuickPath Interconnect&i class=&icon-external&&&/i&&/a&&/p&
如果我们找一个程序员问他“什么是内存”,我想不太容易得到满意的答案,这是一个太大的话题,并且对不同领域的编程人员而言,理解千差万别。比如Java程序员可能会想起来讨厌的GC参数,C++程序员可能想起来线上OOM、crash的经历,内核程序员可能想起来怎么…
&p&芯片厂商向客户介绍产品,从硬件角度说的最多的就是功能,性能,功耗和价格。功能这个主要就是看芯片提供了什么接口,比如闪存,内存,PCIe,USB,SATA,以太网等,还看内部有什么运算模块,比如浮点器件,解码器,加解密,图形加速器,网络加速器等等。性能,对CPU来说就是的测试程序能跑多少分,比如Dhrystone,Coremark,SPEC等等。针对不同的应用,比如手机,还会看图形处理器的跑分;又比如网络,还会看包转发率。当然,客户还会跑自己的一些典型应用程序,来得到更准确的性能评估。功耗就是在跑某个程序的时候,整个芯片的功率是多少瓦。通常,这时候处理器会跑在最高频率,但这并不意味着所有的晶体管都在工作,由于power
gating和clock gating的存在,那些没有被用到的逻辑和片上内存块并没在完全耗电。我看到的芯片公司给出的处理器最大功耗,通常都是在跑Dhrystone。这个程序有个特点,它只在一级缓存之上运行,不会访问二级缓存,不会访问内存。这样得出的其实并不是真正的最大功耗。但是从实际经验看,没有应用程序能让CPU消耗更高的能量,所以这么测量最大功耗也没什么错。当然,作为整体的芯片功耗,还得包括各种加速器和接口,尤其是会被用到的模块。&/p&&p&在设计SoC的时候,性能,功耗和价格就转换成了PPA。啥是PPA?其实就是性能,功耗和面积。其中,性能有两层含义。在前端设计上,它指的是每赫兹能够跑多少标准测试程序分。设计处理器的时候,会有个多少级流水线的说法。通常来说,流水线级数越多,芯片能跑到的最高频率越高,这个大家应该都知道。可是并不是频率越高,性能就越高。这和处理器构架有很大关系。典型的反例就是Intel的奔腾4,30多级流水,最高频率高达3G赫兹,可是由于流水线太长,一旦指令预测错误,重新抓取的指令要重走这几十级流水线,代价是很大的。而它的指令预测又极大依赖于编译器来优化,结果当时编译器又没跟上,导致它总体性能低下。你看MIPS或者PowerPC的处理器频率都不高,但是每赫兹性能相对来说还不错,总体性能就会提高一些。所以看性能要看总体跑分,而不是每赫兹跑分。前一段时间龙芯在宣传的时候就钻了这个空子,号称每赫兹都赶上至强了,但是也就能跑个1Ghz多,而16核至强可以到将近3Ghz。&/p&&p&性能的另外一个含义就是指频率,这是从后端设计角度来说的。通常后端的人并不关心每赫兹能达到多少跑分,他们只看芯片能跑到多少频率。频率越高,在每赫兹跑分一定的情况下,总体性能就越高。请注意对于那些跑在一级缓存的程序,处理器每赫兹跑分不会随着频率的变化而变化。当然如果考虑到多级缓存,总线和外围接口,那肯定就不是随频率线性增加了。系统级的性能问题,我会在以后慢慢展开。&/p&&p&那哪些因素会影响频率?就算只从后端角度考虑,答案也很多。我并不是做后端和制程的,只能把道听途说的写下来,仅供参考。&/p&&p&首先,受工艺的影响。现在先进的半导体工厂就那么几家,Intel,台积电,三星,联电,格罗方德。拿台积电来说,它目前提供16纳米的工艺,其中还分了很多小结点,比如FFLL++和FFC。每个小节点各有特点,有些能跑到更高频率,有些功耗低,有些成本低。在不同的工艺上,芯片能跑的最高频率不同,功耗和面积也不同。&/p&&p&其次,受后端库的影响。台积电会把工艺中晶体管的参数抽象出来,做成一个物理层开发包PDK,提供给eda工具厂商,IP厂商和芯片厂商。而这些厂商的后端工程师,就会拿着这个物理层开发包,做自己的物理库。物理库一般包含逻辑和memory两大块。根据晶体管的channel length,会有不同特性,适合于不同的用途的单元cell。而怎么把这些不同特性的库里的cell,合理的用到不同的前端设计模块,就是一门大学问。一般来说,channel
length越短,电子漂移距离越短,能跑的频率就越高。可是,频率越高,功耗就越大,并且是指数上升。除了cell之外,还会有9T/12T这种说法,这里的T是Track,就是cell的高度。T越大,电流越大,越容易做到高频,相应的面积也越大。还有一个可调的参数就是Voltage Threshold,决定了栅极的电压门限,门限越低,漏电越大,频率能冲的越高。&/p&&p&接下来,受布局和布线的影响。芯片里面和主板一样,也是需要布线的。每一层都有个利用率的说法,总体面积越小,利用率越高,可是布线就越困难。在给出一些初始和限制条件后,EDA软件会自己去不停的计算,最后给出一个可行的频率和面积。&/p&&p&再次,受前后端协同设计的影响。比如,某个访问memory的操作,如果知道处理器会花多少时间,用哪些资源,就可以让memory的空闲块关闭,从而达到省电的目的。这种技巧可能有上千处,不自己设计处理器是没法知道的,哪怕你有RTL代码。&/p&&p&再往上,就是动态电压频率缩放DVFS。这里需要引入功耗的组成概念。芯片功耗分成动态和静态两部分,静态就是晶体管漏电造成的,大小和芯片工艺,晶体管数,电压相关,而动态是开关切换造成的,所以和晶体管数,频率,电压相关。具体公式我就不列出了,网上有。控制动态功耗的方法是clock
gating,频率变小,自然动态功耗就小。 控制静态功耗的方法是power gating,关掉电源,那么静态和动态功耗都没了。还可以降低电压,那么动态功耗和静态功耗自然都小。可是电压不能无限降低,否则电子没法漂移,晶体管就不工作了。并且,晶体管跑在不同的频率,所需要的电压是不一样的,拿16nm来说,往下可以从0.9V变成0.72V,往上可以变成1V或者更高。别小看了这一点点的电压变化,要知道,动态功耗的变化,是和电压成2-3次方关系的。1V和0.7V,电压差了50%,动态功耗可以差3.4倍。我看到过的数据,在500Mhz以下,处理器的动态功耗是小于静态功耗的,变成3GHz的时候,远高于静态功耗。&/p&&p&再往上,就是软件电源管理,控制功耗了。芯片设计者把每个大模块的clock gating和power gating进行组合,形成不同的休眠状态,软件可以根据温度和运行的任务,动态的告诉处理器每个模块进入不同的休眠状态,从而在任务不忙的时候降低功耗。这又是一个很大的话题,以后再展开。&/p&&p&从上面我们可以看到,功耗和性能其实是和在一起的。而芯片设计者可以用不同的工艺和物理库,设计出最高可运行频率,然后软件控制芯片动态运行频率和功耗。&/p&&p&那面积呢?其实也是相辅相成的。由于针对不同的逻辑,memory和布线,选用了不同的物理库cell,不同的track,形成的芯片面积也会不一样。通常来说,越是需要跑高频的芯片,所需的面积越大。频率差一倍,面积可能有百分之几十的差别。可别小看这百分之几十,对晶体管来说,面积就是成本,晶圆的总面积一定,价钱一定,那单颗芯片的面积越小,成本越低,并且此时良率也越高。&/p&&p&芯片成本除了制造费,还来自于授权费,工具费,流片费,运营开销等,通常手机处理器这样复杂的芯片,没有上千万美元是不可能做出来的。就算做出来,没有卖掉几百万片,那是肯定亏的。&/p&&p&最后还想提下ARM的大小核设计。其最初的目的是想设计两组核,小核每赫兹性能低,面积小,跑在低频;大核每赫兹性能高,面积大,跑在高频。运行简单任务,大核关闭,小核在低频,动态功耗低,静态功耗占上风,并且由于面积小,总体功耗更低。而大核用高频运行复杂任务。和x86的单纯调节电压频率比,增加了一点低频小核面积,和整个芯片的面积比,其实没多多少。&br&&br&那为什么不让小核跑在高频运行复杂任务呢?理论上,由于每赫兹性能低,对于相同的任务,小核必须跑在比大核更高的频率才能完成,这就意味着更高的电压。此时,动态功耗占上风,并且和电压成三次方关系。最终的功耗会高出大核不少。此外,我们前面已经解释过,小核要跑在高频,面积会增大不少,可能比大核还要大。我们从里面可以看到存在一个平衡点。这个平衡点并不好找。拿A53/A57在28nm上举例,当它们跑在1.2Ghz的时候,功耗可能差两倍,性能却只差50%。而平衡点可能要达到2Ghz。事实上,很多手机芯片的大小核都是使用同样的处理器,跑在不同高低频率。&/p&&p&所以,设计芯片很大程度上就是在平衡。影响因素,或者说坑,来自于方方面面,IP提供商,工厂,市场定义,工程团队。水很深,坑很大,没有完美的芯片,只有完美的平衡。在这点上,苹果是一个很典型的例子。它的CPU频率不很高,但是Geekbench单核跑分却比海思的 A73高了整整75%,接近Intel桌面处理器的性能。为什么?一个原因是它使用了六发射,而A73只有双发射,流水线宽了整整三倍。这里,苹果用了大量的面积换取性能。当然,三倍的发射宽度并不表示性能就是三倍,由于数据相关性的存在,发射宽度的效益是递减的。再一点,苹果使用了整整6MB的缓存,而这个数字在别的手机芯片上通常是1MB。我做过测试,对一些标准跑分,比如SpecInt,128KB到256KB二级缓存带来的性能提升仅仅是7%左右,而256KB到1MB带来的提升更小,缓存面积却是4倍。面积的提升同样带来了静态功耗的增加。不过由于苹果的生态都是他自己的,它引入的复杂的电源,电压和时钟控制,并从软件层面就开始优化,将整体功耗控制的非常好。但是也只有苹果能这么做,一般公司绝对不会走苹果这样用2-3倍面积换性能和功耗的路线,那样的话毛利就太低了。而没有手机整体高利润的保障,也没有统一的软件系统控制功耗,其结果就是一个死。&/p&&br&&p&下面,让我们从访存这个简单的问题开始讨论SoC。CPU是怎样访问内存的?简单的答案是,CPU执行一条访存指令,把读写请求发往内存管理单元。内存管理单元进行虚实转换,把命令发往总线。总线把命令传递给内存控制器,内存控制器再次翻译地址,对相应内存颗粒进行存取。之后,读取的数据或者写入确认按照原路返回。再复杂些,当中插入多级缓存,在每一层缓存都未命中的情况下,访问才会最终达到内存颗粒。&/p&&p&知道了完整的路径,那我们开始研究每一步中的硬件到底是怎么样的,读写指令到底是怎样在其中传输的。要了解硬件,首先要说下处理器。处理器的基本结构并不复杂,一般分为取指令,译码,发射,执行,写回五个步骤。而我们说的访存,指的是访问数据,不是指令抓取。访问数据的指令在前三步没有什么特殊,在第四步,它会被发送到存取单元,等待完成。当指令在存取单元里的时候,产生了一些有趣的问题。&/p&&p&第一个问题,对于读指令,当处理器在等待数据从缓存或者内存返回的时候,它到底是什么状态?是等在那不动呢,还是继续执行别的指令?一般来说,如果是乱序执行的处理器,那么可以执行后面的指令,如果是顺序执行,那么会进入停顿状态,直到读取的数据返回。当然,这也不是绝对的。在举反例之前,我们先要弄清什么是乱序执行。乱序执行是说,对于一串给定的指令,为了提高效率,处理器会找出非真正数据依赖的指令,让他们并行执行。但是,指令执行结果在写回到寄存器的时候,必须是顺序的。也就是说,哪怕是先被执行的指令,它的运算结果也是按照指令次序写回到最终的寄存器的。这个和很多程序员理解的乱序执行是有区别的。我发现有些人在调试软件问题的时候,会觉得使用了一个乱序的处理器,那么可能会使得后面的代码先被执行,从而让调试无法进行。他们搞混了两个个概念,就是访存次序和指令完成次序。对于普通的运算指令,他们仅仅在处理器内部执行,所以你看到的是写回次序。而对于访存指令,指令会产生读请求,并发送到处理器外部,你看到的次序是访存次序。对于乱序处理器,可能同时存在多个请求,而其次序,是打乱的,不按原指令顺序的。但是此时,这些被发送到外部的读请求,并没有拿到返回结果,指令也没有完成。所以,这并不违反乱序执行顺序完成的原则。如果有前后两条读指令,没有数据相关性,哪怕是后面那条读的数据先被返回,它的结果也不能先写回到最终的寄存器,而是必须等到前一条完成后才可以。&/p&&p&对于顺序执行的处理器,同样是两条读指令,一般必须等到前一条指令完成,才能执行第二条,所以在处理器外部看到的是按次序的访问。不过也有例外,比如读写同时存在的时候,由于读和写指令实际上走的是两条路径,所以可能会看到同时存在。这个问题在引入更详细的硬件结构之后再展开。&/p&&p&还有,顺序处理器上,哪怕是两条读指令,也有可能同时存在两个外部请求。比如Cortex-A7,对于连续的读指令,在前一条读未命中一级缓存,到下一级缓存或者内存抓取数据的时候,第二条读指令可以被执行。所以说,乱序和顺序并不直接影响指令执行次序。他们的区别在于,乱序需要额外的缓冲和逻辑块(称为重排序缓冲,
re-order buffer)来计算和存储指令间的相关性以及执行状态。而顺序处理器没有重排序缓冲,或者非常简单。这些额外的面积可不小,据我所看到的,可以占到处理器核心的40%。它们所带来的更高的并行度,性能提升却未必有40%。因为我们写的单线程程序,由于存在很多数据相关,造成指令的并行是有限的,再大的重排序缓冲也解决不了真正的数据相关。所以对于功耗敏感的处理器还是使用顺序执行。&/p&&p&还有一点需要注意,顺序执行的处理器,在指令抓取,解码和发射阶段,两条或者

我要回帖

更多关于 电脑出现错误页面 的文章

 

随机推荐