操作系统-芯片-3D目标检测分析
参考文献链接
https://mp.weixin.qq.com/s/iPJCdag-Q-MTqVKFx4O9RQ
https://mp.weixin.qq.com/s/9NH1UgkiyhHNJVpyXQns9Q
https://mp.weixin.qq.com/s/h-cjDJq5_4qFD3aS3mqw7g
https://mp.weixin.qq.com/s/IOywVJ5fhc2pJTnVL6USOQ
操作系统的x86处理器
当然,更不乏某些夜郎自大的高论,像「就算x86 指令集再复杂,控制单元也还是很好设计」、「微指令转译早就克服了所有x86 指令集的瓶颈」等。拜托,「做出勉强能动的东西」和「开发出有市场竞争力的产品」完全是天差地别的两件事,要不然你以为英特尔和AMD 天文数字般的产品开发经费,都拿去填海了吗?总之,高效能x86 处理器之所以难做,混杂了诸多技术和商业因素,暗藏在台面下的细节非外人足以道也。但我们可以回到x86 处理器最重要的历史转捩点:1993年Pentium 处理器,抽丝剥茧一般人难以察觉到的蛛丝马迹。以「唯偏执狂得以生存」(Only theParanoid Survive)留名于世的英特尔创办人之一安迪·葛洛夫(Andy Grove) 曾经说,2003 年的Centrino 是英特尔的「第二个儿子」与「十年来最重要的品牌」 ,那「第一个儿子」和「十年前最重要的品牌」,想当然尔就是1993 年的Pentium 了。Pentium 之名起源于希腊文的Penta(意思是「五」),再加上拉丁文的ium 结尾,意指「第五代x86 处理器」,英特尔自此也不再使用80×86 来定义处理器世代(要不然现在还真的讲不出来到底是「几86」)。而Pentium 也在不知不觉中,一步步转型为入门级低价处理器品牌,更不再受限于1993 年的P5,进而横跨历代英特尔的超标量(Superscalar)x86 微架构。原始Pentium 里的P5 处理器微架构,并没有像后继Pentium Pro 的P6 活得如此之长,影响又如此深远(到2011 年Sandy Bridge 才结束)。但Pentium 问世的时机,却是众多历史机运的集大成:
- 「x86 处理器走向高效能化」。
- 「x86 开始与RISC 正面竞争」。
- 「x86 指令集的缺陷让厂商感到棘手」。
- 「x86 处理器进军多处理器平台」。
- 「个人电脑市场因Windows 95 的问世而蓬勃成长」。
- 「笔记本电脑即将逐渐普及」。
- 「英特尔默默埋下让擅长改良的以色列海法研发团队,主导x86 处理器技术发展」。
- 「x86指令集的兼容性,成为其他竞争者的潜在障碍,也造成软件开发商的困扰」。
这些因素交错,奠定了之后25 年技术演进与市场发展的基础逻辑,连时下「AMD 处理器的核心太多,竟然造成Windows 操作系统的大麻烦」,也和Pentium 留下的遗产,多少有些千丝万缕的纠结。既然连AMD Zen成功的背后,都有这么多不为人知的故事,了解英特尔「Landmark」芯片Pentium的轨迹,将有助跳脱琳琅满目的技术营销名词,重新建立属于自己的「x86处理器世界观」,值得各位细细品味。x86 指令集先天不足后天失调的原罪催生「计算机结构」(ComputerArchitecture)一词的「指令集架构」(Instruction Set Architecture),为「电脑的基础语言」与「软硬件中间的接口」(Interface Between Hardware and Software),相同指令集的电脑理应可执行一样软件,具备彼此兼容性,对电脑与处理器微架构(Microarchitecture)的未来发展有举足轻重的影响力。不同的时空背景,也会产生相异的指令集设计理念,没有绝对的优劣对错──x86算是少见的例外,另一个则是DEC VAX,两者的共同点只有「着毋庸议」的糟糕,英特尔IA-64(Itanium)和DEC Alpha的激烈反动,足以证明连生父都如此厌恶小孩。
说到指令集架构如何影响处理器微架构的设计,这几年来最有名的实例,就是苹果利用充分掌握封闭平台的优势在「最短时间内驱逐32位元应用程序」,掌握ARM指令集迈向64位元的过程带来的改革与机会,让A7之后的自家ARM处理器,彻底为64位元的ARMv8-A量身订做,研发出一系列能同时有效处理更多指令的先进微架构。所谓的CISC(复杂指令集电脑)出自存储器容量稀少、缺乏成熟的高级语言编译器、大多数人使用语言撰写程序的时空背景,程序设计者寄望直接用透过微码(Microcode)组成功能强大的单一指令,像十进位数字和复杂字串处理等,撰写应用程序。相较之下,RISC(精简指令集电脑)则是在「万事皆备」的环境,追求更快更便宜的电脑,将晶体管预算砸在「最经常被使用的简单指令和运算元定址模式」的刀口上,尽量用硬件线路去取代微码,并藉由强制仅载入(Load)/储存(Store)指令可存取存储器,搭配大型化的数据寄存器与快取存储器,填补处理器和存储器之间越来越大的效率鸿沟。
时过境迁,在x86 主宰云端数据中心、中低端服务器、工作站、桌面机一路到笔电的今日,可能已经没有多少人能回想起「RISC 与CISC 之争」曾发生在1990 年代初期的史迹,但毕竟x86 指令集公认是充满缺陷的产物,连当代最伟大的计算机结构教科书都「白纸黑字」并「烧录」于无数莘莘学子的脑中,而AMD K5 的创造者更是用一句「毫无道理可循」(it just doesn'tmake a lot of sense)一锤定音,几无争议空间。让人满脸黑线的,只有动辄挥出「逆向思考全垒打」的大恩大德,将x86 的市场胜利,视为「x86 指令集架构优良」佐证的天真论点,并时有所闻,连ARM 都比x86 更「存储器存取密集」(Memory-Intensive)这种话都讲得出口。我们先来瞧瞧当年AMD K5 的总工程师Mike Johnson 怎么评论x86 指令集,由设计x86 处理器的大师级人物(他本人的确是超标量流水线技术的先驱者,那份变成首本超标量技术专书的史丹佛大学博士论文非常有名,附录更深度分析设计超标量x86 处理器的困难点)写出来的批评,特别有说服力,也一再被后人引述:
从这短短一段话,可看到几个重点:
「毫无道理可循」(doesn't make a lotof sense):相较于指令编码程度统──4 Bytes(32 bits)的多数RISC体系,x86指令集的格式和编码极度混乱,长短粗细肥瘦不一,从1~17 Bytes都有可能。影响所及,遍及所有的环节,从快取存储器撷取指令、实作指令流水线化,到处理器发生中断例外时要迅速储存执行状态并尽快回复等,都造成非常严重的后遗症,激增研制高效能x86处理器的门槛,也增加验证产品的时间与成本。「缺乏足够的寄存器」(the lack ofregisters):一般RISC指令集都会定义32个通用数据寄存器(讲的精确一点是31个)或浮点运算寄存器,但x86在64位元和AVX之前,怎么样都只有少少的8个(因为要扣掉ESP和EBP,说6个或许比较贴切)。当引进指令流水线化和更先进的超标量流水线后,自然就激增了寄存器冲突的机率,这也是激发x86处理器拥有强大非循序指令执行(寄存器重新更名)与高效率存储器子系统的主因。「让人感到极度痛苦的定址模式」(extremelypainful addressing scheme):定址模式讲得白话一点是「存取所需运算元(运算的目标,例如某个寄存器或某个存储器位址)的方式」,历经多年叠床架屋的x86定址模式,尤其是恶名昭彰的节区存储器(Segmentation),不只加重产生有效位址的工作负荷,也激增整数逻辑运算(ALU)和控制单元的复杂度,「副作用」跟第一项「毫无道理可寻」可谓不相上下。这些烫手山竽,就是1980 年代末期和1990 年代初期,众多企图研制高效能x86 处理器的有志之士,包含不小心制造出这些「炸弹」的英特尔,不得不面对并设法克服的挑战,身为x86 世界首颗「超标量(Superscalar)流水线」处理器的Pentium,则是第一个提出的「有效解决方案」,英特尔为此付出了不小代价。一个时钟周期执行一个以上指令的「超标量流水线」近似近代工业生产线的概念,让所有人都「闲闲有事做」的流水线化(Pipeline)一直是提高效率的最基本手段,80386 的预先指令撷取(Prefetch)已有雏型,而80486 是x86世界首款真正达成指令流水线化的处理器,为了确保存储器跟得上运算需求,也配置指令/数据共用的第一阶快取存储器,虽然真正「每个时钟周期都可稳定输出」者,也只限于简单的整数逻辑运算指令。「一个时钟周期内执行一个以上指令」的超标量流水线(Superscalar),早在1965 年发迹于RISC 始祖的CDC6600,1980年代陆续出现在RISC 处理器,如1985 年Power 前身的IBM「America」计划(也是「超标量」一词由来)、1988 年Motorola MC88100、
1989 年英特尔i960CA、1990年AMD 29050 等。各位绝对没看错,英特尔、AMD 也做过RISC 处理器。
指令流水线化、超标量流水线、非循序指令执行、大型化快取存储器等常见的效能加速手段,清一色优先降临RISC 处理器的原因,也很简单:RISC 指令集的单纯性与简洁性,让设计者更轻易导入这些技术,并用更短的时间完成产品开发与验证。1990 年代上半期,一提到高效能处理器,几乎都由RISC 独领风骚,像至今硕果仅存的IBM Power、HP 的PA-RISC、SGI 的MIPS、Sun 的UltraSPARC、Fujitsu 的SPARC64及充满传奇色彩的DEC Alpha 等,这也是「RISC 优于CISC」的理论基础。但「理论」是一回事,不代表CISC 的x86「实务上」做不到,更何况又是拥有一整支庞大「研发军队」的英特尔,革命性的第五世代x86处理器Pentium 在1993年3 月22 日登上历史舞台,不只要迎击来自AMD、Cyrix、NexGen(被AMD 购并,Nx686 变成K6)的竞争产品(之后还多出Centaur 和Transmeta),更要面对AIM 联盟(Apple、 IBM、Mororola,不是美国的空对空飞弹)PowerPC 的挑战,后者享有威镇四方的「RISC 王者」IBM Power 当强大后盾,市面更不乏「专书」鼓吹PowerPC 相对x86 的优越性,把Pentium批评得一文不值。更扯的是,意图抢夺英特尔势力范围的IBM 还开发了「脚位与Pentium 兼容,可以同等效率硬件执行x86 程序码,并兼具32 / 64 位元PowerPC 指令集兼容性」的PowerPC 615,要不是微软可能觉得难搞,不符成本效益,拒绝支持这颗处理器,导致永远无法量产,英特尔的处境会更危险。至于PowerPC 615 取消后,研发团队就投靠Transmeta,接着就无疾而终了。也许各位难以想像「为什么x86 会受PowerPC 威胁,不是属于不同市场吗」,但此时此刻,没半个正常人会将连服务器市场边都沾不上的x86 和「高效能」三个字联想在一起,甚至连那时候英特尔内部,都很少人愿意相信x86 还有未来性,否则也不会出现IA-64 指令集和Itanium 处理器了。况且微软1993 年7 月发表未来操作系统基础的WindowsNT,打从一开始就同时支持x86、MIPS 和Alpha 三版本,之后还加码PowerPC 和IA-64,「不将鸡蛋放在同一个篮子里」意图太明显了。换句话说,英特尔当时的战略地位,并不像今天如此牢不可破,更早在1990 年同步启动第六世代Pentium Pro 研发案,完全没有承受失败的本钱与余裕。天底下没有白吃的午餐Pentium 是如假包换的x86 世界首款超标量处理器,可在同一个时钟周期内执行最多两个指令,整体结构近似双重流水线的「放大发展版」80486,但也因扛着x86指令集的原罪,由外到内付出了不少代价。爬文至此,建议各位回头复习一次Mike Johnson 评论的3 个重点,你一定会更有感触。我们光从初代Pentium(P5)的晶粒图,即可清楚看到x86 兼容性的代价:大型化的快取存储器(尽管实质容量不高,但内部结构却出奇复杂)、巨大的指令撷取单元、解码与复杂指令微码控制单元(Complex Instruction Support),这也是日后所有高效能x86 处理器的共同特色。
指令/数据分而治之的第一阶快取存储器:x86指令集因缺乏足够的数据寄存器,且包含了大量「寄存器/存储器互通有无」与直接以存储器为运算目标的指令,不像RISC的「载入/储存」架构「一次从存储器抓了大量数据进来,算完后再一次性丢回存储器」,特别需要强力的存储器子系统,所以采用指令/数据分开的第一阶快取存储器,确保两边足以喂饱个别的需求。而Pentium的指令快取和数据快取,更是各自大有文章。
前面有提及「x86 指令最大长度是17Bytes」,Pentium 第一阶指令快取的内部结构,是以两个最小存取单位16Bytes 区块,组成单一32Bytes 的快取线(Cache-Line),假若发生最糟糕的情况,17Bytes 长度的指令「横跨」了两条32Bytes 快取线,但仍希望一次撷取到指令流水线,那该怎么办?Pentium 导入跨指令线分离式撷取(Split Fetch),可连续读取横跨边界的两条16Bytes 最小区块,而指令读取缓冲区也是4 倍于80486 的128Bytes,以确保撷取指令的效率可「喂饱」两条流水线的指令解码器。
顺便对照一下从NexGen Nx686 发展而来的AMD K6。AMD 取消了两倍核心时钟频率的第一阶快取存储器,但除了既有的预先解码位元(Pre-decoded Bits)用来标定指令边界,再追加第二个指令存取埠,以应付这种状况。反正各家厂商都各显神通,直到可降低指令解码器使用率的微指令快取(uOp cache)同时成为英特尔和AMD 的制式武装为止。
数据快取亦不遑多让,数据快取具备3 个存取埠,可同时应付来自快取数据一致性协定与双重执行流水线的需要,更进一步将每条32Bytes 快取线,切成彼此交错的8 个独立4Bytes「Bank」,只要两个数据存取需求不会同时使用同一个Bank,即可在单一时钟周期内搞定。这是计算机工业史上的第一次尝试,但这也大幅加重了快取存储器的复杂度,也连带不得不强化配置数据快取的虚拟/实体位址转换缓冲区(TLB,Translation-Lookaside Buffer ),并新增判断Bank 是否发生冲突的功能电路。巨大的微程序只读存储器:基于「加速常用的简单指令」的理念,Pentium 的指令解码器可直接硬体解码大多数「寄存器→存储器」与「存储器→寄存器」之类的「相对简单」运算指令,但 x86 历代累积下来的庞大复杂指令,还是需要动用微码组成微程序产生控制讯号。Pentium 的单一微码字元长度是 92Bits,总共存放了 4K 数量。换言之,产生了高达 47kB 容量的只读存储器(ROM)空间,还远多于第一级快取记忆体的容量(8kB+8kB),相当惊人,老旧指令兼容性带来的巨大负担,由此可见一斑,这就是维持回溯兼容性,所必须付出的昂贵代价。4 个输入的位址计算单元:一套所谓「复杂」的指令集,除了不规则的指令编码长度,乱无章法的运算元定址模式和存储器定址,更是必备的条件(可回顾一下 AMD Mike Johnson 讲过的话)。实现高效能的超标量流水线 x86 处理器,并非只需弄好流水线前端的指令撷取、解码,与执行阶段的存取存储器,高效率的有效位址计算(Address Calculation)能力,更是 x86 有别于 RISC 体系的一大差异点,坊间人云亦云、积非成是的「x86 处理器只有指令解码器比较难做」完全是大错特错的误解。为了加速存储器位址计算,让执行单位尽快得到「运算目标」,Pentium 的两条指令流水线个别有一套 4 个输入值的加法器(4-Input Address Adder),对应 x86 指令集产生有效位址的 4 个数字:节区描述器(Segment Descriptor)提供的基底值(Base)。来自通用寄存器的基底位址(Base Address)。取自通用寄存器的索引值(Index,再加上scale)。指令编码附上的的移位值(Displacement)。因此部分仅支持 3 个输出值的 486,需耗费两个时钟周期完成位址计算的复杂指令,Pentium 只需一个时钟周期週期即可,更利于流水线化执行指令。
但惨剧尚未划下句点,x86 的节区存储器(Segment),必须强制检验每个节区的大小,确保存储器运算元落在节区描述器所定义的存储器范围内。80286 时代的保护模式,节区描述器会影响节区位置与体积的参数,总计有:
- 32 位元基底值(Base)。
- 20 位元范围值(Limit)。
- 范围值单位Page 或Byte(前者上限4GB,后者则1MB)。
- 针对堆叠(Stack)数据结构的向下扩展(Expand-Down)栏位。
处理器需采取不同的方式计算最高与最低位址,结果Pentium的两条指令流水线,为此个别又得「再」加上一套4 个输入值的加法器(4-Input Segment-Check Adder) ,为检查节区正确性之用,而486的情况如上述位址产生器,须耗费更多时钟周期做这件事。
为何「位址计算单元」一向是历代x86 处理器增加执行单元的重头戏,原因就在此。Windows 95 刺激个人电脑普及的年代,「32 位元最佳化」的Pentium Pro 被批评「16 位元效能不佳」就因动到数据节区寄存器的指令,无法被非循序预测执行,会让随后的指令上演大塞车,到了Pentium II 才修正。即使假以时日,这些老旧包袱的使用率只会越来越低,也早不再是改善效能的重点,但也没人胆敢冒着牺牲软件兼容性的风险,根除这些历史遗迹。正面对决PowerPC 且落居下风
相较于同时期且较早推出的超标量RISC 处理器,就可明显看出x86 指令集的复杂度造成的负面影响。以1993 年秋季上市的IBM PowerPC 601 为例,晶体管数目仅280 万,采用0.6um(600nm)制程时的晶粒面积仅121 平方公厘,却有比晶体管310 万的Pentium 更高的80MHz时钟频率、更大一倍的32kB 指令/数据共用式第一阶快取存储器,与1.5 倍的指令执行能力,而采用0.8um(800nm)制程的初代Pentium是一颗16.7×17.6mm、294平方公厘的巨大芯片,完全瞠乎其后。英特尔当时就表示,相较于同等级RISC 处理器,Pentium 有约30% 晶体管都「贡献」给x86 指令集的兼容性。不难想见那时候的「RISC 十字军」有多high。而Pentium 的双重超标量指令流水线也是限制重重,只有主流水线「U Pipe」可以执行所有的x86 指令,副流水线「V Pipe」仅能负责比较简单者,而要这两条流水线一起动,还需要依循指令配对规则,讲白了就是「两边都要跑简单指令」,且涉及寄存器和存储器两边数据互相搬移的指令也无能为力,就是要强迫其中一条流水线「发呆」两个时钟周期给你看。PowerPC 601「理所当然」比较没有这样的烦恼。
80×87 浮点指令集更是x86 处理器追求高效能浮点运算的罩门,因「英特尔内部沟通不良」(英特尔美国加州总部和以色列海法之间实在太远了,1970 年代末期的联络手段又没像今天这么方便)诞生的「极度愚蠢」堆叠式(Stack)寄存器架构(附赠让人摸不着头绪的80 位元延伸双倍精确度浮点格式),强迫多数浮点指令的运算元,其中一个非得指定放在堆叠寄存器的顶端不可。
英特尔在Pentium 加入FXCH 指令用来交换置顶寄存器,原本仅内建一组浮点运算单元,流水线不能同时执行两个浮点运算指令的Pentium,简单的浮点运算指令可和FXCH 一同塞进两条指令流水线,但实际上也只有执行一个有效浮点运算,况且后头接连着的整数指令,都会被延误最少一个时钟周期。
判断分支条件需「借用」整数运算通用寄存器与执行单元,则是80×87 另一个弱点,从一个浮点运算设定条件码、将浮点运算的执行资讯搬移至通用寄存器、传送至条件码寄存器,再依据其结果,启动正常的分支处理流程,Pentium 整整耗时9 个时钟周期。当然可透过「插入」其他整数指令来降低效能损失,但无法弥补当执行条件判断密集的程序,整数浮点单元之间反覆「踢皮球」的伤害。这些在今天只会让人觉得很荒谬的往事,让Pentium 的浮点性能仍远远不及同时期的RISC 处理器,只能在x86 的世界当大王,这宿疾到了新一代Pentium Pro 依旧无解,同期MIPS R10000 的SPECfp92 浮点效能还是Pentium Pro 的「3 倍」以上,还因为PowerPC「外挂」AltiVec 而一度被拉开差距到差点看不见车尾灯的程度。直到Pentium III(Katmai)开始扩充SIMD(单一指令,多重数据流)浮点指令集SSE、初代Pentium 4(Willamette)的SSE2 新增双倍精确度浮点格式,一路到Sandy Bridge 的AVX,引入VEX (VectorExtension)标头,一口气解放了过去x86 指令编码带来的重重枷锁,才算功德圆满。
但即使看似出师不利,x86 指令集的沉重包袱,并未让英特尔就此停下脚步,依然持续精进Pentium 处理器,就算没有一鼓作气打开天堂大门,却也让紧闭已久的门缝渗出充满希望的曙光。
从企图杀入很长一段时间内「可远观不可亵玩焉」的服务器市场,预期Windows 95 激发个人电脑市场爆发性成长时,补足高效能桌机和笔记型电脑需要的基本功能,到迎合「多媒体」的新潮技术营销名词,无不是英特尔1990 年代初期念兹在兹的技术发展重点。这些努力的痕迹,统统一字不漏深深刻在Pentium和整个计算机工业的历史上。原汁原味的多处理器支持性「多处理器支持性」是进入工作站与服务器市场的最低门槛入门票,而Pentium 则是x86 历史上首度「原生支持(Glueless)多处理器」的先行者,但严格说来,这到了0.5 um 制程的第二代Pentium(P54C)才实现,而在此之前,也并不是没有「多处理器x86」的存在,只是需要外挂特制的系统芯片组,或连操作系统都要特殊版本。一个便于实作的「无需外挂额外芯片」(Glueless)的多处理器(或多核心)环境,需具以下条件:
- 分配、协调各I/O 周边装置存取处理器需求的能力,发出中断(Interrupt)时,知道该由哪个处理器负责:标准化的中断处理机制。
- 快取存储器数据一致性协定(Cache Coherence Protocol):回写式(Write-Back)快取存储器常见的MESI(Modified, Exclusive, Shared, Invalid)协议。
- 低成本多处理器系统的根基:可让多处理器共享的系统总线。
3 项条件之一,最重要者莫过于第一项。1983 年,17 名因英特尔极具野心的「32 位元微电脑大型主机」iAPX432 计划失败而离职的员工,创立的Sequent ComputerSystems(1999 年被IBM 购并,研发高阶英特尔处理器的系统芯片组),就曾推出一系列采用80386 与80486 的多处理器产品线,但这些花费不菲的专属方案,仰赖特制系统芯片组与定制化过的操作系统,才能正确的将系统中断(System Interupt)传送到各处理器,多处理器x86 平台仍缺标准化的中断处理机制,并非可长可久的解决之道。
1993 年10 月27 日,也是初代Pentium 发表后的半年,英特尔首度公开第一版「多处理器规范」(MPS,Multi-Processor Specification)与最重要的「处理器本地端先进可程序化中断控制器」(Local APIC,Local Advanced
ProgrammableInterrupt Controller)与I/O 专属的I/OAPIC,取代老旧的8259 PIC。
每个Pentium 或80486 处理器起码要有一个Local APIC,与系统I/O 芯片组的I/O APIC,透过独立于系统总线的3 位元APIC Bus,I/OAPIC 将周边装置的中断需求传递给处理器的Local APIC,以决定中断服务需求该指派给那些处理器,踏出了低成本多x86 处理器系统的第一步。英特尔的竞争对手并非没有替代方案,1996 年上市的Cyrix 6×86 依据OpenPIC规范,支持自家定义的SLiC,但也只有VIA 的Apollo 芯片组对应此规格,基本上有跟没有一样,而AMD的x86 多处理器环境,更是要等到1999
年采用Alpha EV6 总线、理论上最多支持14 颗处理器的K7了。
不过初代Pentium
并未内建Local APIC,同时期系统芯片组也没有I/O
APIC,要打造多颗Pentium 平台,每一颗Pentium
需外挂一颗单价高达26 美元、兼具Local APIC 与I/OAPIC 两者功能的82498DX,I/O 也需动用一颗。换句话说,双处理器系统就需要用到3 颗,怎么看都不算便宜,还会占用不少主机板空间。
后来0.5um 制程的第二代Pentium(P54C)变成史上第一颗整合Local APIC 的x86 处理器,对应的系统芯片组也陆续在南桥(South Bridge)内建I/O APIC(未内建者,可选配专用的82093AA I/O APIC),总算让双Pentium 摇身一变,成为「Glueless」的多处理器平台。
受制于缺陷重重的系统架构,如效率不足的系统总线、处理器缺乏非循序存储器存取能力、处理器共享外部的第二级快取存储器让总线问题更加雪上加霜等等,并未让Pentium 在服务器市场取得重大突破,到了Pentium Pro 面世后才迎刃而解,开启Xeon 统治服务器市场之路,那又是另一段截然不同的故事了。决定处理器核心/线程上限的Local APIC 与闯祸的操作系统支持性处理器核心持续激增的今日,Local APIC 最重要的角色在于决定处理器的核心与线程上限。原先最早的APIC 上限是15,2000 年Pentium 4 开始出现的xAPIC(将APIC 的3 位元专属总线直接「融入」系统总线的通讯协定,避免APIC 运作时影响存储器存取效能)增加到255,2008 年Nehalem 的x2APIC更多达4294967295,可视为「无限大」。假如各位想多学些对亲朋好友炫耀的「无用知识」,稍微花点脑筋,牢记一下这3 个数字的由来:
- APIC:Pentium和Pentium Pro(与Pentium II、Pentium III、P6核心的Xeon)动用Local APIC的ID寄存器24-27四个位元,16进位的0xF(10进位制的15)用做广播,所以2 4 −1=15。
- xAPIC:Pentium 4到Penryn用到Local APIC的ID寄存器24-31八个位元,16进位的0xFF(10进位制的255)用做广播,所以2 8 −1=255。
- x2APIC:Nehalem开始使用存于MSR(Model-Specific Register)的32位元x2APIC ID,16进位的0xFFFFFFFF(10进位制的4294967295)用做广播,所以2 32 −1=4294967295。
但帐面上的「理论值」让人看得很爽是一回事,微软这些操作系统厂商是否乖乖买单又是另一回事。很不幸的,AMD Zen2 世代EPYC 与Threadripper将单颗处理器的实体核心术/逻辑处理器,一举推进到64 核/128绪,就变成微软Windows 的灾难了。一台2 颗EPYC 7742 或7702 的服务器,拥有128 个处理器核心和256 条线程,但是Windows Server 2016 和2012 R2 并不支持「AMD 新型平台的x2APIC」,无法吃下这么多逻辑处理器。事实上,根据微软的EPYC 性能调校文件,Windows Server 2019 之前的旧版Windows Server,只能支持2 颗48 核心的EPYC 和192 个逻辑处理器。安装2019 年9月前的Windows Server 2019,也需要事先在BIOS关闭x2APIC 和多线程,安装完毕并装完所有的系统更新档,重新开机进BIOS 恢复功能,才能在工作管理员的效能选单看到全部CPU。再次同场加映AMD。刚好前阵子Anandtech 有特别报导的EPYC 在Windows 10 发生的灾情(尽管这和APIC 没有关系)。Windows 系统核心会预设64 个CPU组成一个「Windows Processor Group」,当逻辑处理器超过64 个,会将多出余数包成另一群,像一颗64 核/128 绪的Threadripper,就会变成一颗实体CPU 有「两包」64 个逻辑处理器。
但Windows 10 要企业版(Enterprise)才提供此功能,家用版(Home)和专业版(Professional)会将「满出来的部分」,误判为占用另一个处理器脚位的实体CPU,意思就是误解成「两颗」处理器的系统,将误导操作系统的线程排程,降低系统效能,这时关掉多线程,很可能表现还比较好。
在计算机的世界,任何「看起来很棒」的技术和功能,无不是「软硬兼备」的成果,当入手顶规的硬件时,也请多多关心手上的软件环境是否可发挥最高效益。笔者现在都可以猜到花大钱买TR 3990X 的「长辈」急着升级Windows 10 企业版的画面了。「让人比较有感」的指令集扩展如果能让笔者选择,其实x86 指令集扩张史中最重要的一幕,绝对是迈向32 位元的80386、虚拟86 模式(Virtual 8086 Mode)与具备分页表的虚拟存储器。但事隔多年,印象最深刻的,依旧对个人电脑市场规模爆炸式成长、使用者急速增加的1990 年代,英特尔在Pentium 家族干的一堆好事,包括极具历史意义的第一次SIMD 扩张:MMX。相信各位不可能不知道CPUID 这经常用来辨识处理器厂牌、功能、版本与规格的好工具,但你们知道背后作这件事的「CPUID」指令,就是从Pentium 开始登场的吗?众多程序设计师计算指令执行周期数的RDTSC(Read Time-Stamp Counter),也伴随着Pentium 而生。用在操作系统避免不同线程同时对共用资源读写「互斥锁」的CMPXCHG8B(Compare and Exchange 8 Bytes),也是小有名气,WindowsXP 就是因这个必备指令,无法执行于Pentium 之前的所有x86处理器。
前面有提到MSR(Model-Specific Register),意指在x86 架构处理器中,一系列用于控制处理器执行、功能开关、除错、追踪程序执行、监测处理器效能等功能的寄存器。MSR 的雏形始于80386 和80486,到了Pentium,英特尔新增RDMSR(ReadMSR)和WRMSR(Write MSR)指令用于读写MSR,使其真正的实用化。此外,软件可透过前述的CPUID 指令,查询处理器可支持的功能,并确认这些功能对应的MSR 是否存在。同时英特尔在Pentium「复刻」笔电专用的80386SL 和80486SL 处理器,那独立于真实模式和保护模式,干了哪些好事,连操作系统都不知情的系统管理模式(SMM,System Management Mode)。英特尔制定SMM 的初衷,在于让笔电OEM 厂商自订必备的电源管理与周边装置管理,如为了省电,动态关闭用不到的周边设备,需要时再重新启动等,将SMM 从「特殊武器」提拔成「制式装备」,暗示英特尔认定笔电即将普及化的未来。顺道一题,相对于x86 指令集在节区定址定义4 层权限的Ring 0 到Ring 3(数字越小权力越大),SMM 的权限经常戏称为Ring -2,那Ring -1 跑到哪去了?答案是x86 硬件虚拟化技术用来拦截「在使用者模式仍会更动系统底层的危险指令」的Hypervisor 权限。这些指令集扩张看似微不足道,远不如那票SIMD(MMX、SSE、SSE2、SSE3、SSE4、AVX、AVX-512)「华丽壮大」,却也是不可或缺的基本功,同为「高效能处理器不可被分割的一部分」。但Pentium 史上最知名的指令集MMX,就是一场欢乐异常的连续剧了。
为了MMX 让Pentium 被迫大兴土木、脱胎换骨
Windows 95 带动个人电脑的多媒体需求,英特尔自然不能免俗,势必要让处理器跟「多媒体」沾上边,试图推动主机板加挂一颗来自第三方的数位讯号处理器(DSP),专门处理即时性影音应用程序。但因为NSP 的运作模式是独立于操作系统的化外之民,等于需要操作系统开后门,微软为此拒绝买单,因此胎死腹中,才出现了MMX。打从英特尔在1996 年,抛出MMX 这从未讲清楚说明白的「无意义技术营销商标」,全名一直众说纷纭、莫衷一是,还先后出现3 个版本:
- MultiMedia eXtension
- Multiple Math eXtension
- Matrix Math eXtension
名称怎样不重要,各位只要记得一件事:为了操作系统兼容性,MMX指令集借用x87 浮点运算寄存器(80 位元中的64 位元)的SIMD「整数」运算,这样就够了。英特尔定义全新SIMD 寄存器,从Pentium III「Kaimai」的SSE(KNI,Katmai New Instructions)才开始。指令集的编码空间毕竟有限,英特尔要从哪里挤出这57 个指令的位置?英特尔将脑袋动到「0Fh」开头的运算码(Opcode),这却造成前所未见的麻烦:过去0Fh 的主要用途「当处理器的解码器收到时,自动将该指令执行流程跳到外挂的辅助处理器」,当初英特尔就靠这招来处理8087 浮点辅助处理器,0Fh 开头的x86指令都不是什么「需要追求效率」者,也因此,Pentium 的指令解码器也没有特别「关照」它们,意味着难以迅速完成解码MMX 指令的重责大任。主导Pentium MMX(P55C)研发的以色列海法团队,不得不大兴土木,将指令流水线深度从五阶延长到六阶,争取足够的指令解码时间。多这一阶并非有害无益,因为执行单元将有更充裕的时间存取数据快取,并缩短电路的关键路径,利于提高时钟频率,让Pentium MMX 最终可到达300MHz,比前代P54C 多出整整50%。
但延长指令流水线也带来更严重的分支预测错误代价,英特尔索性将「流水线深度长达12 阶」的第六世代Pentium Pro 搭载的双层动态分支预测与副程序返回位址缓冲区等先进技术,原封不动的逆向移植到Pentium MMX,亦倍增快取存储器和数据写回缓冲区,转换虚拟和实体存储器位址的TLB,也强化为可同时处理两种不同分页大小的版本,种种改进项目仿佛威而刚,让吃下蓝色小药丸的Pentium MMX 摇身一变成「5.5 代」x86 处理器。
为了减少耗电与发热,英特尔将MMX 执行单元与实体寄存器独立于x87 浮点运算器,执行MMX 指令时,因指令集定义「逻辑上MMX 和x87 浮点无法同时执行」,可关闭「吃电如喝水」般的浮点单元以节约电力,可是结合加倍的快取存储器和种种增强方案,P55C 晶体管数从P54C 的330 万激增到450 万,制程从「不计多出10%芯片面积以追求最高时钟频率」的350 纳米BiCMOS 改进为「自此英特尔转向追求更低成本并降低耗电」的280 纳米CMOS,芯片面积和制造成本仍足足比P54C 多50%。Pentium MMX 在1997 年1 月上市没多久,同年5 月同样支持MMX的Pentium II 就以「塑胶大弹夹」外观,现身于各地电脑卖场的玻璃柜,无论怎么看,Pentium MMX 都是过渡期强烈的尴尬产物。
(Source:Flickr/AndyRogers CC BY 2.0)但Pentium MMX 对英特尔在以色列海法的研发团队而言,却是极为重要的历史里程碑,建立起「擅长精炼现有架构压榨更多价值」的名号,接连重塑P6 微架构成为Centrino 心脏的PentiumM (Banias, Dothan)、当英特尔在Pentium4(NetBurst)惨遭滑铁卢的危急存亡之秋端出Core 2(Merom, Conroe, Woodcrest)救驾成功、融合P6 与NetBurst 之长的Sandy Bridge 终结AMD K8 的辉煌岁月、直到「奋六世之余烈」集大成的「终极x86 微架构」Skylake,清一色都是出自以色列海法团队的不朽杰作。x86 指令集长期欠缺标准造成竞争对手与软件开发商的困扰Pentium 的「历史地位」倒是值得另外添一笔:x86 指令集欠缺公开业界标准,搞死不少人的陈年旧帐,终于正式浮上台面。Pentium Pro 总工程师之一的Robert Colwell 回忆录《The Pentium Chronicles》说过,开发一颗x86 处理器,最艰巨的挑战在于「如何保证可兼容所有旧程序」。特别早期x86 处理器,很多未定义的运算码(Opcode)并没有遮掉,被人发现又拿来用了,以后的处理器开发人员就只能乖乖想办法「塞」进去,前提是你也要知道这些陷阱到底藏在哪里。各位是否天真的以为所有x86 处理器厂商的产品,都保证彼此兼容,可执行一模一样的软件?很遗憾的,这种好事从来就不存在英特尔统治的x86 世界(最起码,前阵子让Linus Torvalds 大暴走的AVX-512,AMD 现有产品也是付之阙如),英特尔在Pentium 时代的所作所为就是最好例证,让初版使用者手册描述新增指令的「附录H」故意保持完全空白,英特尔的竞争者与软件开发商纷纷变成倒霉的苦主。英特尔并不像那票会定期推出版本演进与相关规范的RISC 指令集(有关心ARM 的读者应该很清楚)、积极推广自家指令集给其他潜在竞争对手,而是完全不管其他人死活。想研发x86 兼容处理器的有志之士,假若没有跟英特尔签订互相授权协议,只有两条路可选:乖乖用电子显微镜默默研究英特尔处理器的晶粒,尝试逆向工程,要不然就干脆不支持不顾兼容性,碰到就视为非法指令,剩下的烂摊子就丢给操作系统厂商伤透脑筋了。像Cyrix 在被National Semiconductor 购并前,压根没有英特尔技术授权,只能闷着头逆向工程慢慢搞,自然也无法100% 兼容,让号称「第六世代」的6×86,连CPUID 和RDTSC都残缺不全,指令集兼容水准只有80486 等级,甚至还得逼迫软件厂商撰写修正程序。同时期的AMD 则是不计代价拼死拼活,都要藉由逆向工程挤出100% 兼容性,下场就是产品上市延误,错失商机,还亏当初Compaq 傻傻不肯推出Pentium 个人电脑,宁愿痴痴等待AMD K5,更惨的是,撑到最后还是等不到。英特尔竞争者也都不是省油的灯,不遑多让抢着跳出来扮演「麻烦制造者」,自行定义「兄弟独有之创见」的自家指令,不仅企图争夺x86 指令集的主导权,并强化产品效能及营销筹码,像AMD K6 的3DNow!、AMD K8 的x86-64,Cyrix 6x86MX 的EMMI 与CyrixIII 的MMX-FP,Centaur 一度想不开的57 个SIMD 浮点指令和22 个自定义浮点寄存器,都是斑斑可考的历史陈迹。AMD 还一度想不开,抢先注册「SSE5」,摆明跟英特尔AVX 打对台,还好在2009 年5 月6 日紧急采煞车,宣布「皈依」AVX,但还是忍不住捞过界「补完」被英特尔废除的四运算元指令格式(xmm1=xmm2×xmm3+m32)。AMD 要开始支持乱成一团的AVX-512并完全兼容,大概也是很久以后的未来了。
回顾这些年来的x86 指令集扩充战争,唯一从英特尔手上抢下先机的,也只有AMD x86-64 那次,还是微软私下威胁「不打算支持两种不同的64 位元x86」(英特尔本来有自己的Yamhill,但为了保护IA-64 迟迟不肯拿出来)强迫英特尔接受的结果。
最初英特尔多心不甘情不愿、打死都不承认 64 位元 x86 存在的「IA-32e」和 AMD x86-64 也并非一模一样,英特尔独占 CMPXCHG16B(Pentium 那个 CMPXCHG8B 的进阶版)和 SSE3,AMD 多出分页表 NX(No Execute)保护位元和 3DNow!。谈到 x86 处理器厂商要彼此 100% 水乳交融,说有多麻烦就有多麻烦,说微软有多火大就有多火大。不过乱象还是持续延烧,还烧到虚拟化领域,近十多年来虚拟化应用快速普及,然后 2005 年英特尔 VT-x(Vanderpool)和 2006 年 AMD AMD-V(Pacifica),双方根本就是各搞各的,老死不相往来,让 VMware vMotion 此类不停机的虚拟机动态迁移技术,迟迟跨越不了不同 x86处理器厂商的边界,无形中也侷限了 x86 处理器「向上发展」的潜力,目睹此景,IBM 应该会继续开心下去。重塑x86 处理器世界观的历史认知行文至此,想必各位看官脸上已挂著颤抖的嘴角与充满劫后馀生的表情,或多或少逐步解构并重组过往对个人电脑市场与 x86 处理器演进史的认知,有可能瞬间茅塞顿开,也有可能继续满头问号。本文并非教科书,而是经由複习坊间甚少重视的历史背景与不容易注意到的细节,体认到平日陪伺在旁、习以为常的 x86 处理器,能走到今天是多麽不容易的一件事。罗马不是一天造成,英特尔的霸权也不是平白从天上掉下来,这些年来我们一同挤过这麽多条牙膏,更不是毫无苦衷。身为英特尔 1990 年代「Landmark」芯片与无数潜藏已久历史暗流的缩影,Pentium 带来 x86 世界太多「第一次」,承先启后,奠定 25 年技术演进与市场发展的基础逻辑。毕竟电脑是人类创造的东西,背后的人性和思维远远超越技术。英特尔可靠开创新局的 Pentium 与继往开来的 Pentium Pro,在众多敌人环伺下杀出一条通往全新市场血路过程中「产生的价值」,如「技术领先无法保证商业胜利」和「成功的产品往往是折衷妥协后的产物」,统统很有意思,更值得各位细细品味。关于Pentium,似乎笔者不小心遗漏了什麽,听说跟浮点除法(FDIV)有关?算了,就当作提醒世人吧。
美国新规之下,多数国产先进制程芯片海外代工暂不受影响!
10月21日消息,虽然美国政府于10月7日出台了针对中国大陆的出口管制新规,一些中国大陆的芯片制造商受到了较大的影响,但是对于多数的中国大陆芯片设计厂商来说,目前的影响相对有限。比如据彭博社报道,中国GUP厂商壁仞科技最新的7nm GPU芯片BR100依然能够继续由台积电代工。
根据之前美国公布的限制规则显示,美国已宣布将某些先进计算芯片,以及包括先进计算芯片的计算机商品添加到商业控制清单(CCL)中。
例如美国此前已经要求NVIDIA暂停向大陆出货的A100、H100 GPU卡,要求AMD暂停出货的MI200 GPU。此次新规还以A100的性能指标作为限制标准。即同时满足以下两个条件的即为受管制的高性能计算芯片:(1)芯片的I/O带宽传输速率大于或等于600 Gbyte/s;(2)“数字处理单元 原始计算单元”每次操作的比特长度乘以TOPS 计算出的的算力之和大于或等于4800TOPS。
所谓原始计算单元包括:NPU(神经网络专用计算单元)、MAC(矩阵运算单元)、FPU(浮点运算单元)、AMU(模拟乘法器运算单元),使用忆阻器、自旋电子学、磁子电子学的处理单元,使用光子学、非线性光子学的处理单元,使用模拟、多级非易失性(multi-level nonvolatile weights)的处理单元,使用多级存储器或者模拟存储器的处理单元,Multi-value units、spiking架构芯片(例如IBM的TrueNorth)。
美国新规当中有指出,一个或多个模拟、多值或多级“原始计算单元”,其处理性能将以TOPS乘以8来衡量,即8bit下的算力需要乘以8换算成1bit下的算力,再跟限定的4800TOPS比较。
美国对于受限的超级计算机的定义为:“在体积41600立方英尺以内具备FP64计算能力在100Petaflops以上,或者FP32在200Petaflops以上算力的计算系统”。
超过以上阈值高性能计算芯片或超算系统都将受到新的出口管制规则的限制,除非获得美国商务部的许可证。
同时,新规还针对在中国大陆进行超级计算机或所需的超级计算机芯片开发或生产的项目提出增加新的许可证的要求,并且出口管制的范围还将扩大到中国大陆以外的利用美国技术来为中国大陆厂商制造的可以被用于超算的高性能芯片或被用于超级计算机的器件的外国厂商。
即在中国大陆设计此类芯片的设计厂商、制造超算芯片的晶圆厂,以及有用到美国技术的海外晶圆代工厂在为中国大陆芯片设计厂商代工此类芯片时,都将受到美国的出口管制新规限制,除非获得美国商务部的许可。但是这里的前提跟前面一样,芯片的峰值算力大于或等于4800TOPS*1bit(相当于INT8算力600TOPS),I/O双向带宽大于或等于600GB/s才会受到限制。
BR100是壁仞科技于2022年8月9日发布的旗下首款通用GPU芯片,基于台积电7nm工艺,CoWoS(Chip-on-Wafer-on-Substrate)的2.5D封装,其INT8算力达2048 TOPS,BF16算力达1024 TFLOPS,TF32+算力达512 TFLOPS,FP32算力达256 TFLOPS。BR100还支持 PCIe 5.0接口技术与 CXL通信协议,双向带宽为 128 GB/s。根据公开的资料也显示,BR100也是目前所有的国产GPU当中性能最强的一款芯片。
按照美国新规给出的限制“阈值”来看,壁仞科技BR100算力是超过“阈值”的,但是BR100的带宽则小于“阈值”。而美国新规的给出的限定条件是,同时达到算力和带宽“阈值”的产品才会受到限制。所以,壁仞科技的BR100确实不会受到新规的限制,台积电可以为其代工。
正如前面所提及的,目前壁仞科技的BR100是国内目前最强的国产GPU芯片,其他的已经发布的国产芯片GPU,比如天数智芯天垓100、摩尔线程的苏堤等不论是算力还是带宽都是远在美国限定的“阈值”之下。
同样,其他的芯片设计厂商(除华为以及此次扩大限制的28家实体)也无需对于禁令过于紧张。因为美国此次禁令主要限制的是前面提及的“阈值”之上的高性能计算和超算芯片及系统的对中国大陆出口,以及大陆在这方面的制造能力,包括通过限制相关半导体设备对大陆的出口,以限制大陆制造16/14nm及以下先进制程逻辑芯片、128层及以上NAND闪存芯片、18nm半间距或更小的DRAM内存芯片的制造能力。但是其他例如基于先进制程的国产智能手机芯片、IOT芯片、汽车芯片等,台积电、三星等海外晶圆代工厂依然是可以为相关大陆芯片设计厂商代工的。
对于这一点,此前笔者也曾与某头部晶圆代工厂内部相关人士进行交流,对方看法亦是如此。
但是,这也并不什么值得高兴的事,因为从美国方面的一系列动作来看,套在我们“脖子”上的绳子已经是收的越来越紧。谁也不知道,下一次,美国是否又会再出新规来限制国内设计的先进制程芯片在海外的代工。所以,核心问题还是在于国内半导体制造业何时能够突破?个人认为,如果在未来五六年内能够实现7nm的去美化产线(关键还是在于核心设备和材料),那么很多问题将迎刃而解。
为什么是7nm的去美化产线?因为随着制程工艺向着更微观的3nm、2nm尺度推进,晶体管的微缩开始变得越来越困难,不仅推进的脚步越来越慢,能够带来的性能提升也越来越有限,但成本也越来越高,摩尔定律正趋于失效,同时其所带来的经济效益正在消失。有研究显示,5nm的单位晶体管的成本已经几乎与7nm持平(即制程更先进,单位面积的晶体管密度更高、数量更多,但是摊薄到单个晶体管上的成本并没有带来成本的降低),3nm的单位晶体管成本甚至已经要高于7nm。因此,7nm的去美化产线的建立,对于我们来说更具实际意义。美国后续即使在更先进的3/2nm层面保持围堵,对于我们整个科技产业发展的压制作用将比较有限。
转转监控系统的内部原理及实践
一、背景
转转早期的监控系统zzmonitor是纯自研的,其数据上报方式比较简单,有且仅有四种数据上报方式:SUM、MAX、MIN、AVG。示例如下:
public
void
test
() {
long
start = System.currentTimeMillis();
//do something
long
cost = System.currentTimeMillis() - start;
ZMonitor.sum(
"
执行次数"
,
1
);
ZMonitor.max(
"
最大耗时"
, cost);
ZMonitor.min(
"
最小耗时"
, cost);
ZMonitor.avg(
"
平均耗时"
, cost);
}
数据在客户端会每分钟做一次聚合,并以异步、批量的方式发送到zzmonitor服务端。另外,zzmonitor的存储选型是MySQL,分了128张表也仅能支持7天的数据存储。
zzmonitor监控系统
以聚合方式来上报数据的目的是为了减少监控系统的数据存储量,但是同时也牺牲了太多,随着转转业务的发展,zzmonitor的弊端也逐渐显露:
- 功能少,业务反馈大
仅提供这四个函数,无法监控QPS、TP99等。
- API设计不合理
以聚合方式来上报数据不够灵活,同样的数据,业务需要按照聚合方式多次上报;同时,部分数据无法二次加工,如:只能监控1分钟的平均值,无法监控到一天的平均值。
- 架构设计不合理
监控数据通常与时间强相关,使用MySQL存储性能差,数据压力也较大。存储数据常适合存储于时序数据库,时序数据库会带来良好的读写性能与数据压缩比。
- 维护&开发成本高
随着业务量的上升,系统的很多地方出现了性能瓶颈,需要人持续的维护、排查问题。新功能的迭代也需要持续投入人力。
另外,除了给业务使用的zzmonitor,运维层面还有各式各样的监控系统,如:Falcon、夜莺、ZABBIX、Prometheus等,这给业务线同学使用上带来一定的困惑。
二、调研选型
基于以上背景,我们决定落地一套全新的立体式监控系统,能够集业务服务、架构中间件、运维资源于一体,简单易用,同时便于维护。落地前期主要针对Cat、夜莺、Prometheus做了一部分调研选型。
Cat |
夜莺 |
Prometheus |
|
Contributors |
77 |
56 |
643 |
Star |
16.3k |
4k |
40.2k |
偏向性 |
链路监控、日志监控 |
资源监控 |
任何业务指标与中间件监控 |
功能 |
一般 |
简单,但不够强大、不够灵活 |
接入Grafana后功能丰富,十分灵活 |
存储 |
HDFS |
官方强烈推荐M3DB |
任何实现Prometheus远端存储协议的存储 |
开源时间 |
2011 |
2020 |
2012 |
数据上报方式 |
埋点构建消息树 |
调用Http接口推送本地Agent |
Prometheus server主动pull |
社区活跃度 |
一般 |
一般 |
活跃 |
总体来说,Cat更适合链路监控,夜莺更像一个简化版的Prometheus+Grafana,而Prometheus拥有非常灵活的PromQL、完善的Exporter生态,我们最终选择了Prometheus。
三、Prometheus能力
3.1 生态模型
Prometheus自带一个单机的TSDB,他以pull模式抓取指标,被抓取的目标需要以Http的方式暴露指标数据。对于业务服务,服务需要将地址注册到注册中心,Prometheus做服务发现,然后再做指标抓取。
Prometheus架构图
Prometheus拥有非常完善的Exporter生态,大部分我们使用的中间件都有成熟的Exporter,利用这些Exporter,我们可以快速的搭建起监控体系。
Exporter生态
Prometheus的黄金搭档Grafana在可视化方面表现也十分不错,Grafana拥有非常丰富的面版、灵活易用。以下是我们落地的实际效果图。
Grafana落地效果
Prometheus从客户端角度区分出了几种指标类型:Counter、Gauge、Histogram、Summary(转转内部不建议使用)。
3.2 Counter
Counter是一个只增不减的计数器,Prometheus抓取的是Counter当前累计的总量。常见的如GC次数、Http请求次数都是Counter类型的监控指标。
Counter counter = Counter.build().name(
"upload_picture_total"
).help(
"
上传图片数"
).register();
counter.inc();
以下是Counter的样本数据特点,我们可以在查询时计算出任意一段时间的增量,也可以计算任意一段时间的增量/时间,即QPS。
Counter计数器
如果服务器重启,Counter累计计数归零,Prometheus还能计算出准确的增量或QPS吗?Prometheus称这种情况为Counter重置,Counter在重置后总是从0开始,那么根据这个假设,在给定的时间窗口计算增量时,只需将重置后的样本值叠加上重置前的值,以补偿重置,就像重置从未发生过一样。
Counter重置
3.3 Gauge
Gauge是个可增可减的仪表盘,Prometheus抓取的是Gauge当前时刻设置的值。常见的如内存使用量、活跃线程数等都是Gauge类型的监控指标。
Gauge gauge = Gauge.build().name(
"active_thread_num"
).help(
"
活跃线程数"
).register();
gauge.set(
20
);
以下是Gauge类型的样本数据特点,我们一般不做二次计算,直接展示Prometheus抓取的原始值。
Gauge仪表盘
3.4 Histogram
Histogram通常用于数据分布统计,Histogram需要定义桶区间分布,根据用户上报的数据,来决定具体落到哪个桶内。
Histogram histogram = Histogram.build().name(
"http_request_cost"
).help(
"Http
请求耗时"
).buckets(
10
,
20
,
30
,
40
).register();
histogram.observe(
20
);
以下是部分源码,各个桶内存储上报的总次数,Prometheus抓取的是各个桶的当前状态。
public
void
observe
(
double
value) {
for
(
int
i =
0
; i < bucket.length; ++i) {
//
遍历所有桶,如果数据小于桶上限,对应桶+1
if
(value <= bucket[i].le) {
bucket[i].add(
1
);
break
;
}
}
//
增加sum总值
sum.add(value);
}
基于各个时刻的桶内数据,我们可以计算出任意一段时间的数据分布情况。如下为例,我们可以利用桶内数据计算出10:01~10:02的数据分布情况。
10ms |
20ms |
30ms |
40ms |
|
2021-10-01 10:01 |
0 |
5 |
10 |
20 |
2021-10-01 10:02 |
1 |
10 |
10 |
20 |
increase[1m] |
1 |
5 |
0 |
0 |
这个计算结果可视化后可能如下图所见,实际意义可能是一天的RPC调用耗时分布。基于这个计算结果,还可以计算出TP值,即n%的值都不高于x。
Histogram桶分布
如果我们每个时间点都计算一次分布,我们就得到了这样的面板:分布折线图
。
分布折线图
利用这个数据,我们还可以换一种表现形式:热力图
。热力图每个时刻都是一个直方图,热力图通过颜色深浅区分出数据分布的占比。
热力图
Histogram除了buckets,还记录了sum(上报数据的总值)、count(上报数据的总次数)。我们通过一段时间内sum的增量/count的增量,即可算出上报数据的平均值。
3.5 多维标签
Prometheus的指标可以定义多个标签,比如对于以下指标:
Counter counter = Counter.build().name(
"http_request"
).labelNames(
"method"
,
"uri"
).help(
"Http
请求数"
).register();
counter.labels(
"POST"
,
"/addGoods"
).incr();
counter.labels(
"POST"
,
"/updateGoods"
).incr(
2
);
counter.labels(
"GET"
,
"/getGoods"
).incr();
数据上报后,类比MySQL表,表名为http_request,某一时刻的表数据如下:
method |
uri |
value |
POST |
/addGoods |
1 |
POST |
/updateGoods |
2 |
GET |
/getGoods |
1 |
有了这些结构化的数据,就可以在任意维度进行查询和聚合。比如,查询uri=/addGoods的数据;再比如,按照method维度做SUM聚合,查看总数。
3.6 误差
Prometheus 在设计上就放弃了一部分数据准确性,得到的是更高的可靠性,架构简单、数据简单、运维简单、节约机器成本与人力成本。通常对于监控系统,数据拥有少量的误差是可以接受的。
如计算增量时,给定的时间窗口中的第一个和最后一个样本,永远不会与真实的采样点100%重合,Prometheus会做数据线性外推,估算出对应时间的样本值。
数据外推
四、架构设计
4.1 远端存储
Prometheus自带一个单机的TSDB,显然这是远远不够的,好在Prometheus提供了存储的扩展,自定义了一套读写协议。当发送读写事件时,Prometheus会将请求转发到三方存储中。
远端协议
我们经过选型对比,最终选定M3DB作为远端存储。M3DB是Uber开源的专为Prometheus而生的时序数据库,拥有较高的数据压缩比,同时也是夜莺强烈推荐的三方存储。
M3DB架构
- M3 Coordinator(M3 协调器) :M3协调器是一个协调Prometheus和 M3DB 之间的读写的服务。它是上下游的桥梁,自身无状态。
- M3DB(存储数据库) :M3DB是一个分布式时间序列数据库,是真正的存储节点,提供可扩展的存储和时序索引。
- M3 Query (M3 查询引擎) :M3DB专用查询引擎,兼容Prometheus查询语法,支持低延迟实时查询和长时间数据的查询,可以聚合更大的数据集。
- M3 Aggregator (M3 聚合器) :M3聚合器是一个专用的指标聚合器,会保证指标至少会聚合一次,并持久化到M3DB存储中,可用于降采样,提供更长久的存储。
4.2 官方路线
以下是完全以官方路线设计出的系统架构,转转的线上环境比较复杂,并没有完全容器化,我们需要单独引入注册中心。各个业务的服务在启动时将地址注册到注册中心,Prometheus再从注册中心做服务发现,然后再做指标的拉取,最终将数据推送到M3DB中。
官方路线
这套架构模型比较复杂,主要体现在以下几点:
- 架构复杂、层级太深、模块太多,运维成本高
- 客户端较重,需要引入注册中心
- Prometheus的作用仅仅是个指标抓取的中转,即没必要也容易增加问题点,还需要考虑分片的问题
4.3 客户端设计
面对以上架构,我们做了一些思考,既然Prometheus可以将拉取到的指标数据推送到三方存储,为什么我们不能在业务服务上绕过Prometheus而直接推送到三方存储呢?
于是,我们调研了Prometheus远端存储协议,改进了客户端的设计。客户端遵循Prometheus远端存储协议(ProtoBuf + Http),并以异步、批量的方式主动将指标推送到M3DB。
改进之后客户端非常轻量,近乎零依赖,并且完全兼容原生客户端用法,因为我们只修改了数据上报的内核逻辑,对API无任何修改。
客户端设计
4.4 最终架构
最终,我们的系统架构如下图所示,对于业务服务,我们会通过客户端主动将指标推送到M3DB中;而对于各个中间件,由于服务IP变动不频繁,继续沿用Exporter生态。
这样,我们省去了注册中心,省去了服务发现,也省去了抓取分片。对于业务服务我们甚至省去了Prometheus,我们只用了Prometheus的协议,而没有使用Prometheus的服务。
最终架构
4.5 性能测试
由于Prometheus需要在客户端埋点上报数据,我们对客户端的性能也做了重点测试。
Counter |
Gauge |
Histogram |
||
QPS |
单线程 |
4300W |
4100W |
2600W |
10线程 |
3030W |
2600W |
3030W |
|
50线程 |
2940W |
2940W |
2380W |
|
平均耗时 |
单线程 |
23ns |
24ns |
38ns |
10线程 |
33ns |
38ns |
33ns |
|
50线程 |
34ns |
34ns |
42ns |
|
内存 |
100个标签 |
20KB |
20kb |
77kb |
200个标签 |
39KB |
39KB |
153KB |
|
500个标签 |
96KB |
96KB |
381KB |
QPS不存在瓶颈,千万级以上,耗时纳秒级别,内存占用取决于标签数量,总体来说资源消耗比较小。
五、落地实现
5.1 Grafana规划
第一个规划是多环境统一,不管线上、沙箱、测试环境,都统一使用同一个Grafana,避免维护多套面板、避免业务记多套地址。
第二个是维度划分,我们目前对Dashboard划分四个维度:
- 业务大盘
我们每个研发同学、每个部门都会负责很多服务,业务大盘摘取了大家负责服务的重点指标,提供一个全局的视角。
- 业务服务
这里是以业务服务为维度,拥有我们内置的各种监控面板,是一个All In One的视角。
- 架构组件
以各个组件为维度,监控所有服务整体使用情况,如线程池监控、日志监控。
- 运维组件
以运维视角来看到各个中间的监控,如Redis、Nginx等。
立体监控
5.2 数据打通
对于监控数据,尤其是业务指标监控,有些数据是比较敏感的,我们打通公司内部用户系统,就可以实现认证、鉴权权限控制。除此之外,我们还打通了服务信息系统、服务权限系统。
数据打通
5.3 企业微信认证
转转的内部系统统一走的企业微信扫码认证,我们基于Grafana的一个小功能Auth Proxy实现了企业微信认证。简单来说,当开启了Auth Proxy,访问Grafana的请求如果携带了Header用户名,Grafana就认为是对应用户访问了系统。
对于转转,当用户访问grafana.xxx.com时,Nginx会做拦截跳转企业微信扫码认证,认证之后由Nginx种入对应Header访问Grafana。
有些同学看到这里可能会担心会不会有安全问题,无需担心。
- Header是由Nginx层种入,不会泄露到前端;
- 如果走域名访问,Nginx会做统一拦截认证;
- 如果走IP访问,我们封禁了非80端口的IP访问。
认证流程
5.4 组件面板自动初始化
转转的架构中间件基于客户端做了各种埋点,包括不限于:JVM、线程池监控、数据库连接池、Codis、Web监控、日志监控、Docker监控等。
Grafana的Dashboard是一整个大JSON,我们只需提前定义好我们的模板,向Dashboard JSON内添加一行模板即可。
{
"panels"
: [
{
"title"
:
"
业务指标"
,
"panels"
: []
},
{
"title"
:
"JVM"
,
"panels"
: []
},
{
"title"
:
"
日志监控"
,
"panels"
: []
}
]
}
5.5 自动建图
光有组件监控还不够,业务也有监控需求,但是PromQL与Grafana的复杂却让人望而却步。没关系,有问题找架构,架构给解决。我们会按照指标类型为业务自动在Grafana上创建出不同的面板。
自动建图
Counter类型自动创建三张面板:
- QPS
- 增量
- 区间增量:根据所选时间所计算的增量
Counter counter = Counter.build().name(
"upload_picture_count"
).help(
"
上传图片数"
).register();
Counter可视化效果
Gauge类型展示一张面板:
- 15秒上报一次的原始点
Gauge gauge = Gauge.build().name(
"active_thread_size"
).help(
"
活跃线程数"
).labelNames(
"threadPoolName"
).register();
Gauge可视化效果
Histogram类型自动创建八张面板,分别为:
- 上报数据平均值
- 上报数据TP99
- 上报次数QPS:即调用Histogram observe函数的QPS
- 上报次数增量:即调用Histogram observe函数的增量
- 上报次数区间增量:即调用observe函数的所选时间的增量
- 分布统计:根据自定义桶与所选时间展示各个区间的上报次数
- 分布折线图:根据自定义桶展示各个区间的分布趋势图
- 分布热力图:根据自定义桶用颜色表示数据的分布区间
Histogram histogram = Histogram.build().name(
"http_request_cost"
).help(
"Http
请求耗时"
).labelNames(
"method"
,
"uri"
).buckets(
10
,
20
,
30
,
40
).register();
Histogram可视化效果
5.6 样板间
除此之外,我们还在Grafana上提供了一个样板间,样板间拥有着业务同学所需要的大部分面板,只需简单复制,按照提示做略微的修改即可做出同样的效果。
样板间
六、报警系统
6.1 背景
监控系统的另一个重要领域是报警系统,Grafana8.0之前的报警系统诟病较多,2021年6月,Grafana8.0之后推出了一个新报警系统ngalert。不过ngalert在我们实测过程中也不太理想,主要体现在以下几个问题:
- 业务自己写PromQL,学习成本较高
- 业务需要理解我们内置的指标的含义
- 发布时间短,我们实测8000个报警就会出现性能瓶颈
Grafana ngaler
6.2 设计
基于以上背景,我们决定自研报警系统。我们参考了夜莺的设计与源码,当用户新建报警后,我们会自动生成PromQL持久化到MySQL中,报警服务通过xxl-job的分片广播调度去加载任务,对于每个任务,报警服务直接查询M3DB判断是否触发报警。
报警流程
6.3 效果
对于业务自定义的指标,只需要点点点就可以设置好报警,报警系统会自动生成对应的PromQL。
报警效果
对于我们内置的中间件指标,只需填个阈值即可。
内置指标报警
七、最终效果
7.1 业务服务维度
以服务为维度,内置丰富的组件面板,提供All In One的视角。
业务服务
业务服务
业务服务
7.2 架构组件维度
以转转的架构中间件为维度,监控各个服务的使用详情,如日志、Codis连接池、线程池等。
架构组件
架构组件
7.3 运维组件维度
利用Prometheus丰富的Exporter监控运维资源的整体使用情况,如Nginx、机器、MySQL等。
运维组件
运维组件
7.4 业务大盘
业务大盘提供一个全局的业务监控、分析视角。
业务大盘
业务大盘
八、总结
至此,我们通过借助开源社区的力量,并结合转转的业务场景做了简单二次扩展,落地了一套集业务服务、架构中间件、运维资源于一体的立体式监控平台,为全公司的各个业务提供一站式的监控报警服务。
这套监控系统在落地时核心扩展点主要围绕以下几块:
- 简化链路,
Pull
模型修改为Push+Pull
模型 - 自动建图、面板自动初始化
- 自研报警系统,可视化PromQL
- 打通转转内部信息系统,如服务信息系统、用户系统等
整体上来说,这套系统架构简洁、功能丰富、简单易用、维护成本低,还能借助开源社区的力量不断迭代。自上线三个月以来,各业务线积极接入,广受业务好评。
视觉3D目标检测,从视觉几何到BEV检测
1. 前言
做为被动传感器的相机,其感光元件仅接收物体表面反射的环境光,3D场景经投影变换呈现在2D像平面上,成像过程深度信息丢失了。而当我们仅有图片时,想要估计物体在真实3D场景中所处的位置,这将是一个欠约束的问题。
2. 几何求解
分类、2D目标检测等图像任务已经在工业界得到广泛应用,可以认为是已经解决了的问题,并且数据价格低廉。但2D目标框无法满足自动驾驶、机器人等对障碍物有定位需求的领域。传统算法利用2D检测框的底部中心点,基于平面假设,求解近似三角形来获得目标离自车的距离。这类方法简单轻量,数据驱动的部分仅限于2D目标检测部分,但对地面有较强的假设,面对车辆颠簸敏感(俯仰角变化),且对2D检测框的完整性有较强的依赖。下图来自apollo lite。
3. 单目3D目标检测
随着标注方法的升级,目标的表示由原来的2D框对角点表示 进化成了3D坐标系下bounding box的表示 ,不同纬度表示了3D框的位置、尺寸、以及地面上的偏航角。有了数据,原本用于2D检测的深度神经网络,也可以依靠监督学习用于3D目标框检测。
这样的3D数据业界目前主要有两种获取方式,一种是车辆除了配备了相机,同时安装了LiDAR这样的3D传感器,经扫描,目标轮廓以点云的形式被记录下来,标注员主要看点云来标注。另一种是像特斯拉这样仅配备相机的车辆,收集的只有图像数据,依靠多种交叉验证的离线算法,辅以人工来生成3D标注数据。
焦距适中的相机,FOV是有限的,想要检测车身 目标,就要部署多个相机,每个相机负责一定FOV范围内的感知。最终将各相机的检测结果通过相机到车身的外参,转换到统一的车辆坐标系下。
但在有共视时,会产生冗余检测,即有多个摄像头对同一目标做了预测,现有方法,如FCOS3D,会在统一的坐标系下对所有检测结果做一遍NMS,有重合的目标框仅留下一个分类指标得分最高的。
冗余问题得到缓解,但要命的是被截断的目标往往在任一个相机里都只出现了一部分,多数情况是每个相机下的检测质量都堪忧。原因是多相机的图片在深度神经网络是以 的形式传递的,传统网络中会有纬度 的特征间交互,也会有纬度 的空间交互,但唯独没有不同图片间batch纬度的交互。简单来说就是下图中左边图片在检测黑色客车时,是无法用到右边图片的信息的。
4. 统一多视角相机的3D目标检测
4.1 看到哪算哪
自下而上的方法,手头的信息看到哪算哪。下图来自CaDNN这篇文章,很好的描述了这一类方法,包括Lift、BEVDet、BEVDepth。这类方法预测每个像素的深度/深度分布,有的方法隐式的预测,有的方法利用LiDAR点云当监督信号(推理时没有LiDAR),虽然只用在训练阶段,但不太能算在纯视觉的方法里比较精度,工程使用的时候可能涉及部署车辆和数据采集车辆割裂的尴尬。总之,有了深度就可以由相机内外参计算此像素在3D空间中的位置,然后把图像特征塞入对应位置。可以理解为由图片生成3D“点云”,多视角相机形成的“点云”拼在一起,有了“点云”就可以利用现有的点云3D目标检测器了(如PointPillars, CenterPoint)。
4.2 先决定看哪
自上而下的方法,先确定关注的地方(但可能手头不宽裕,不配关注这个地方... 比如想关注自车后方,可后方视野完全被一辆大车遮挡了的情况)。关于这类的方法,下图碰瓷一下特斯拉,简单来说就是先确定空间中要关注的位置(图中网格代表的车身周围的地方),由这些位置去各个图像中“搜集”特征,然后做判断。根据“搜集”方式的不同衍生出了下面几种方法。
4.2.1 关键点采样
下图来自DETR3D,其作为将DETR框架用于3D目标的先锋工作,由一群可学习的3D空间中离散的位置(包含于object queries),根据相机内外参转换投影到图片上,来索引图像特征,每个3D位置仅对应一个像素坐标(会提取不同尺度特征图的特征)。
4.2.2 局部注意力
下图来自BEVFormer,该方法预先生成稠密的空间位置(含不同的高度,且不随训练更新),每个位置投影到各图片后,会和投影位置局部的数个像素块发生交互来提取特征(基于deformable detr),相比于DETR3D,每个3D点可以提取到了更多的特征。最终提取的3D稠密特征图在高度纬度会被压扁,形成一张BEV视角下稠密的2D特征图,后续基于此特征图做目标检测。BEVFormer相比DETR3D在精度上有提升(结构上也多了额外的BEV decoders),在BEV视角下,目标尺度被统一了,不会出现图像视角下目标近大远小的问题。一张稠密的BEV特征图还可以做车道线检测/道路分割等任务,缺点是计算量大,显存占用大。
4.2.3 全局注意力
典型方法如PETR,该方法强调保持2D目标检测器DETR的框架,探索3D检测需要做哪些适配。PETR同样利用稀疏的3D点(来自object queries)来“搜索”图像特征,但不像DETR3D或BEVFormer把3D点投影回图片,而是基于标准的attention模块,每个3D点会和来自全部图片的所有像素交互。相似度(attention matrix)计算遵循 ,其中 来自object queries,里面包含的信息和3D bounding box的信息强相关(暂不讨论query也包含的表观信息),而 来自图像(可以理解为和RGB信息强相关,原生DETR中还会加入像素位置编码),这两个向量计算相似度缺乏可解释性(直接训练也不怎么work)。可以理解为下图描述的场景,很难说一个3D框和哪个像素相似。
PETR对矩阵下手,为每个像素编码了3D位置相关的信息,使得相似度得以计算。实现上简单来说是相机光心到像素的射线上每隔一段距离采样一个点的 ,并转换到query坐标系下。相比之下,DETR3D和BEVFormer都遵循了deformable detr的方式,由query预测权重来加权“搜集”来的特征,规避掉了点积相似度的计算,PETR是正面硬刚这个问题了属于是。下图是PETR单位置编码相似度效果图(达到了跨相机相似的效果),只是这个相似度是“虚假”的,跟真实场景没关系,也不会变化。很快,PETRv2中加上了图像特征,效果也有提升。不过全局注意力算力消耗巨大,PETR只用了单尺度特征图,一般显卡还需利用混合精度、checkpoint等降显存的方法才能训练起来。
5. 参考文献
[1] nuscenes: A multimodal dataset for autonomous driving. CVPR 2020.
[2] Lift, splat, shoot: Encoding images from arbitrary camera rigs by implicitly unprojecting to 3d. ECCV 2020.
[3] Categorical Depth DistributionNetwork for Monocular 3D Object Detection. CVPR 2021.
[4] BEVDet: High-performance Multi-camera 3D Object Detection in Bird-Eye-View. arXiv:2112.11790 2021.
[5] DETR3D: 3D Object Detection from Multi-view Images via 3D-to-2D Queries. CoRL 2021.
[6] Deformable DETR: Deformable Transformers for End-to-End Object Detection. ICLR 2021.
[7] BEVFormer: Learning Bird’s-Eye-View Representation from Multi-Camera Images via Spatiotemporal Transformers. ECCV 2022.
[8] Petr: Position embedding transformation for multi-view 3d object detection. ECCV 2022.
参考文献链接
https://mp.weixin.qq.com/s/iPJCdag-Q-MTqVKFx4O9RQ
https://mp.weixin.qq.com/s/9NH1UgkiyhHNJVpyXQns9Q
https://mp.weixin.qq.com/s/h-cjDJq5_4qFD3aS3mqw7g
https://mp.weixin.qq.com/s/IOywVJ5fhc2pJTnVL6USOQ