关于cpu:x64-和-arm64-处理器架构的区别

在深入探讨 x64 和 arm64 这两个处理器架构之前,让咱们先明确它们在计算机科学和硬件设计畛域中的基本概念和重要性。了解这些概念对于把握它们的区别至关重要,而且能够帮忙咱们意识到为什么古代计算设施,从服务器到智能手机,会抉择这些架构。 x64,也被称为 AMD64 或 Intel 64,是对经典的 32 位 x86 指令集架构(ISA)的扩大。这种架构最后由 AMD 开发,并迅速被 Intel 驳回,其次要目标是容许计算机解决更大的内存空间(超过 4GB),同时放弃对旧 x86 应用程序的兼容性。x64 架构反对的是 64 位计算,这意味着它能够应用更宽的数据通道和更大的寄存器,这对于进步数据处理能力和运行简单的应用程序十分重要。 另一方面,arm64,亦称为 AArch64,是 ARM 架构的 64 位版本,由 ARM Holdings 设计。它用于 ARM 的 v8-A 架构中,标记着从 32 位转向 64 位解决能力的重大转变。arm64 架构特地重视能效比,这使得它在挪动设施、嵌入式零碎以及越来越多的服务器和桌面平台上变得十分受欢迎。与 x64 相比,arm64 提供了更高的能源效率和老本效益,局部起因是其精简指令集(RISC),这种设计缩小了每条指令的复杂度,使得硬件实现更为简略。 当初,让咱们探讨 x64 和 arm64 之间的要害区别: 架构设计哲学x64 架构遵循简单指令集计算(CISC)设计准则,这意味着它设计有简单的指令,能够执行多步操作。这种设计初衷是为了缩小编译器的工作量,间接在硬件层面实现简单的操作。然而,这也使得 x64 处理器的设计和实现更加简单,耗费更多的电力,并且在某些状况下升高了处理速度。 相同,arm64 遵循精简指令集计算(RISC)准则,强调应用较少、更简略的指令集来执行操作。这种办法旨在通过进步指令的执行速度来晋升性能,同时升高处理器的能耗和老本。arm64 的这种设计理念使得它在挪动设施和须要高能效的场合十分受欢迎。 利用和生态系统x64 架构长期以来始终是桌面计算机和服务器的主导架构,得益于其与旧 x86 应用程序的兼容性,以及其在解决高性能计算工作方面的能力。这意味着,对于运行简单的桌面操作系统、大型数据库和高端游戏等,x64 提供了弱小的反对。 而 arm64,因为其杰出的能效比和对低功耗的优化,次要用于智能手机、平板电脑、嵌入式零碎和轻薄笔记本电脑。近年来,随着 Apple 推出基于 arm64 的 M1 芯片,以及微软和 Qualcomm 合作开发的 Windows on ARM 我的项目,arm64 架构开始进入高性能计算和桌面计算畛域,挑战 x64 的主导地位。 ...

March 4, 2024 · 1 min · jiezi

关于cpu:什么是GPU与CPU有什么不同哪个更好

如果您以前应用过计算机,您可能遇到过对于 GPU 与 CPU 的探讨。这两个术语有什么区别,哪个更好?计算机依附 GPU 和 CPU 来执行不同的性能,有时两者互相补充。本文将定义 GPU 和 CPU,并概述它们的优缺点。咱们还将概述两者如何协同工作以执行各种计算机性能。 一、什么是图形处理单元(GPU)?GPU 是一种计算机处理器,可依据给定的数学计算执行图形和成像工作。如果您在计算机、平板电脑或智能手机上查看任何图像或图形,GPU 将对此负责。只管大多数计算机都带有3D 图形,但它们须要 GPU 的性能能力更快更好地解决这些图形。GPU 在视频编辑过程中也很重要,因为它容许人们在不影响其余计算机过程的状况下解决简单的动画。为确保您计算机的 CPU 不会变慢,GPU 应用“并行处理”性能,多个处理器同时解决工作的不同局部。 二、什么是中央处理器(CPU)?CPU 也称为“中央处理器”或“微处理器”,是负责大多数解决流动的计算机单元。CPU 依据来自硬件和软件的特定计算机指令工作,以提供所需的后果(输入)。它被宽泛地称为计算机的“大脑”,因为它执行简单的数学计算以无效地解决不同的工作。CPU 在 ALU(算术逻辑单元)和 CU(管制单元)的帮忙下高效地执行其性能。管制单元治理所有解决操作,而 ALU 执行所有数学和逻辑性能。除了 ALU 和 CU 之外,CPU 还与主存储器同时工作,为特定工作获取指令并存储后果。 三、GPU与CPU有何区别?尽管人们有时会把 GPU 和 CPU 了解为同一个货色,但两者是不同的。上面是一个比拟表,概述了 GPU 和 CPU 之间的差别。GPU VS CPU 四、GPU的长处和毛病GPU的劣势:高数据吞吐量,因为它蕴含 100 个内核,可同时解决同一工作的不同局部。通过并行计算执行大量计算。具备很高的计算能力,可用于比特币挖矿。在生成深度学习算法时在机器学习中很有用。GPU 实用于数据迷信畛域的分析程序。GPU的毛病:它被认为比 CPU 更低廉。它不适宜多任务处理,因而不适宜通用计算。   因为其无限的性能和复杂性,它很难解决简单的工作。 五、CPU的长处和毛病CPU具备以下长处:具备高度的灵活性来解决各种工作。因为其高上下文能力,优于 GPU。它能够拜访大内存空间,能够并发解决更多任务。在执行简单的计算工作时具备很高的精度。它具备老本效益且易于取得。中央处理器的毛病:不善于并行处理;因而无奈解决须要数百万个相似操作的大型工作。CPU 的倒退也很迟缓。与所有零碎或软件不兼容,即用于 x86 Intel 处理器的应用程序不能在 ARM 处理器上运行。 六、GPU和CPU如何协同工作?CPU 和 GPU 协同工作以进步给定应用程序的数据吞吐量和并发计算。只管 GPU 最后仅反对计算机图像和图形,但您也能够应用它来进步须要大量数据的 CPU 速度。GPU 能够通过并行运行反复计算来补充 CPU 架构,而其余流动则在 CPU 上串行运行。这反过来又进步了 CPU 的处理速度。此外,GPU 和 CPU 协同工作,因为前者运行专门的数学工作,而后者协调计算机中同时运行的各种流动。因为 GPU 反对并行性,它能够帮忙 CPU 在同一时间范畴内执行更多任务。 ...

July 5, 2023 · 1 min · jiezi

关于cpu:云服务器的vCPU和CPU有什么区别

CPU是指中央处理器,CPU代表物理CPU核数,是实在存在的,CPU不是虚构的,vCPU是物理CPU的根底上通过超线程HT技术虚构进去的,一般来讲,云服务器的CPU指的是vCPU。 首先一台云服务器ECS实例的CPU选项由CPU物理外围数和每核线程数决定,以ecs.g6.xlarge实例为例,阿里云默认提供2个物理核CPU,开启超线程,将每核线程数设置为2,则该实例规格有2*2=4个vCPU,默认状况下该实例规格开启超线程配置。如果敞开超线程配置,则1个物理核只能运行1个线程,实例的vCPU数量等于物理核数,为2。 残缺内容请点击下方链接查看: https://developer.aliyun.com/ask/507098%20?utm_content=g_1000... 版权申明:本文内容由阿里云实名注册用户自发奉献,版权归原作者所有,阿里云开发者社区不领有其著作权,亦不承当相应法律责任。具体规定请查看《阿里云开发者社区用户服务协定》和《阿里云开发者社区知识产权爱护指引》。如果您发现本社区中有涉嫌剽窃的内容,填写侵权投诉表单进行举报,一经查实,本社区将立即删除涉嫌侵权内容。

June 6, 2023 · 1 min · jiezi

关于cpu:SRAM-和-DRAM-的区别

SRAM(Static Random-Access Memory)和DRAM(Dynamic Random-Access Memory)是计算机中两种常见的存储器类型。它们在工作原理、性能特色和应用领域上存在着显著的区别。上面将具体介绍SRAM和DRAM之间的区别。 工作原理: SRAM:SRAM是一种基于触发器的存储器,应用稳固的存储电路来存储和保持数据。每个存储单元由一个存储器单元和控制电路组成,其中存储器单元由多个触发器形成,可能存储比特数据。因为采纳了触发器构造,SRAM在一直刷新的过程中保持数据的稳定性。DRAM:DRAM是一种基于电容的存储器,应用电容来存储和示意数据。每个存储单元由一个电容和一个拜访晶体管组成。电容在存储器中充电或放电来示意数据的0和1。因为电容会逐步漏电,DRAM须要定期刷新以保持数据的正确性。存储密度: SRAM:因为SRAM采纳了稳固的存储电路,每个存储单元须要更多的晶体管来实现,因而SRAM的存储密度绝对较低。每个存储单元通常须要6个晶体管。DRAM:因为DRAM采纳了电容存储构造,每个存储单元只须要一个电容和一个拜访晶体管,因而DRAM的存储密度较高。每个存储单元通常只须要1个晶体管和1个电容。刷新需要: SRAM:因为SRAM的存储单元采纳稳固的触发器构造,不须要进行定期刷新操作。数据能够始终保持稳定,无需周期性刷新。DRAM:因为DRAM的电容逐步漏电,数据须要定期刷新以放弃其正确性。DRAM须要通过刷新操作周期性地从新写入数据,否则数据会失落。访问速度: SRAM:SRAM的访问速度十分快,因为数据存储在触发器中,能够立刻读取和写入。SRAM具备较低的拜访提早和高速的读写性能。DRAM:DRAM的访问速度绝对较慢,因为数据存储在电容中,须要通过拜访晶体管的操作。DRAM具备较高的拜访提早和绝对较慢的读写性能。

May 18, 2023 · 1 min · jiezi

关于cpu:什么是计算机-cpu-的地址总线

计算机的CPU(中央处理器)的地址总线是用于传输内存地址的一组导线或线路。它将内存地址从CPU发送到内存控制器或其余外部设备,用于定位和抉择要读取或写入的内存单元或外设。地址总线的宽度决定了CPU能够寻址的内存空间大小。 上面将具体介绍计算机CPU的地址总线的作用、性能以及相干概念: 地址示意:地址总线用于示意内存地址。内存地址是用来惟一标识计算机内存中的每个存储单元的数字值。地址总线的位数决定了能够示意的地址数量,也就是内存地址空间的大小。例如,32位地址总线能够寻址的内存空间为2^32,即4GB。内存拜访:CPU通过地址总线向内存发送地址信息,以读取或写入数据。当CPU执行指令时,须要拜访特定的内存地址来获取指令或操作数。地址总线将指定的内存地址发送到内存控制器,从而实现对内存的拜访。外设拜访:地址总线不仅用于内存拜访,还用于拜访连贯到计算机的外部设备,如硬盘、显卡、网络接口等。这些外设通常与CPU通过一组地址进行通信。CPU将特定的地址发送到外设控制器,以申请对外设进行读取或写入操作。寻址范畴:地址总线的宽度决定了CPU能够寻址的内存范畴。宽度为n位的地址总线能够寻址2^n个不同的内存单元。例如,32位地址总线能够寻址2^32个不同的内存单元,每个内存单元通常为1字节(8位)。地址解码:地址总线还参加地址解码过程。在计算机系统中,内存和外设通常被划分为不同的地址范畴。地址总线的不同位对应不同的地址范畴或设施。通过地址解码电路,能够将CPU发送的地址与特定的内存区域或外设关联起来,从而抉择正确的指标。性能和扩展性:地址总线的宽度还会影响计算机系统的性能和扩展性。较宽的地址总线容许CPU间接寻址更多的内存,进步零碎的存储容量。它还能够反对更大的物理地址空间,适应更简单和大规模的利用。须要留神的是,地址总线仅负责传输地址信息,而不负责数据传输。数据的读取和写入是通过数据总线来实现的.

May 18, 2023 · 1 min · jiezi

关于cpu:什么是计算机的数据总线宽度

计算机的数据总线宽度是指计算机体系结构中用于传输数据的总线的宽度,也称为数据通路宽度(Data Path Width)。它示意计算机系统中数据传输的并行性,即每个时钟周期内能够同时传输的数据位数。数据总线宽度通常以位(bit)为单位进行示意。 数据总线宽度对计算机的性能和数据传输速度有重要影响,它间接决定了每个时钟周期内可能传输的数据量。以下是对于计算机数据总线宽度的一些重要概念和作用: 并行传输:数据总线宽度决定了计算机能够同时传输的数据位数。较宽的数据总线意味着更多的数据位能够一次性传输,从而实现更高的数据传输速率。例如,一个32位的数据总线能够一次性传输32位的数据,比一个16位的数据总线传输速度快一倍。数据传输效率:较宽的数据总线能够在更短的工夫内传输更多的数据,从而进步数据传输效率。这对于高性能计算和大规模数据处理工作十分重要。较宽的数据总线能够缩小数据传输的等待时间,进步零碎的响应速度。内存带宽:数据总线宽度与内存零碎的带宽密切相关。较宽的数据总线能够反对更大的内存带宽,从而放慢数据在内存和处理器之间的传输速度。这对于大规模计算、图形处理和多媒体利用十分要害。外设连贯:数据总线宽度也对连贯到计算机的外部设备的数据传输速度产生影响。例如,连贯到计算机的硬盘驱动器、显卡、网络接口等外部设备,它们的数据传输速度受到计算机数据总线宽度的限度。零碎扩展性:较宽的数据总线能够提供更大的扩展性。它能够反对更多的设施连贯和更高的数据吞吐量,使计算机系统可能解决更简单和更大规模的工作。须要留神的是,数据总线宽度并不是惟一影响数据传输速度的因素。其余因素包含处理器的性能、存储器速度、总线协定和数据传输方式等,都会对系统性能产生影响。因而,在设计和抉择计算机系统时,须要综合思考各种因素,以实现最佳的性能和数据传输效率。

May 18, 2023 · 1 min · jiezi

关于cpu:寄存器寻址和寄存器间接寻址的区别

寄存器寻址(Register Direct Addressing)和寄存器间接寻址(Register Indirect Addressing)是计算机体系结构中两种不同的寻址模式。它们用于确定指令中操作数所在的地位或获取操作数的值。上面将具体介绍寄存器寻址和寄存器间接寻址的区别以及它们的利用。 寄存器寻址:寄存器寻址是指指令中间接应用寄存器作为操作数的寻址模式。在寄存器寻址中,操作数的值存储在一个或多个特定的寄存器中,并且指令间接指定要应用的寄存器。这种寻址模式能够通过寄存器号或寄存器名称来示意。寄存器寻址的长处是速度快,因为数据间接存储在寄存器中,无需额定的内存拜访。它能够进步程序执行的效率,特地实用于须要频繁拜访和操作数据的场景,如算术运算和逻辑运算。寄存器间接寻址:寄存器间接寻址是指指令中应用寄存器存储的地址来获取操作数的寻址模式。在寄存器间接寻址中,寄存器中存储的是数据的地址,而不是理论的数据值。指令通过寄存器中的地址来拜访内存中的数据。寄存器间接寻址的长处是灵活性,它容许程序在执行过程中动静地扭转寻址的指标地址。这对于实现数据结构、数组、函数调用等简单的内存拜访操作十分有用。此外,寄存器间接寻址还能够缩小指令的长度,因为操作数不须要显式地呈现在指令中。区别和利用: 访问速度:寄存器寻址间接从寄存器中读取数据,速度十分快,而寄存器间接寻址须要额定的内存拜访,速度较慢。灵活性:寄存器寻址在编译时确定操作数的寄存器,实用于固定的操作数,而寄存器间接寻址容许在运行时动静扭转指标地址,实用于灵便的内存拜访。存储空间:寄存器寻址不须要调配额定的内存空间来存储操作数,而寄存器间接寻址须要在寄存器中存储地址值。

May 18, 2023 · 1 min · jiezi

关于cpu:CPU-中通用寄存器的作用

在计算机的中央处理器(CPU)中,通用寄存器(General-Purpose Registers)是其中的一个重要组件。通用寄存器是一种高速的外部存储器,用于存储和操作计算机中的数据。它们是CPU的一部分,用于长期存储指令和数据,以反对计算、逻辑操作和数据传输等操作。上面将具体介绍通用寄存器的作用和性能。 存储数据:通用寄存器用于存储数据。它们提供了一组可供程序应用的存储单元,用于临时保留计算过程中的数据。寄存器是CPU外部最疾速的存储器,能够迅速拜访和更新其中的数据,放慢计算速度。数据传递:通用寄存器用于在CPU外部传递数据。它们能够作为数据的长期存储地位,将数据从一个计算单元传递到另一个计算单元。通过将数据存储在寄存器中,能够缩小对内存的拜访次数,进步数据传输的效率。算术和逻辑操作:通用寄存器用于执行算术和逻辑操作。CPU能够间接从寄存器中获取数据,并对其进行加法、减法、乘法、除法和逻辑运算等操作。这些寄存器作为操作数和后果的暂存区域,反对计算机中各种运算的执行。寄存器间数据传输:通用寄存器能够用于在寄存器之间传输数据。CPU能够将一个寄存器的值间接传送到另一个寄存器中,而不须要通过内存来进行直达。这种寄存器间的数据传输能够更快地实现数据的挪动和操作。地址计算:通用寄存器用于反对地址计算。在程序执行过程中,CPU须要计算内存中的数据地址。通用寄存器能够存储和操作地址计算所需的两头后果和长期值,以便进行无效的地址计算。存储长期后果:通用寄存器用于存储长期计算的后果。在程序执行过程中,CPU须要执行一系列的计算操作,这些操作的两头后果能够临时存储在寄存器中,供后续指令应用。这样能够防止频繁地拜访内存,进步计算效率。参数传递:通用寄存器能够用于函数调用中的参数传递。当一个函数被调用时,参数能够被存储在寄存器中,而后传递给被调用函数。这样能够进步参数传递的速度。

May 18, 2023 · 1 min · jiezi

关于cpu:CPU-中控制器的作用

在计算机的中央处理器(CPU)中,控制器(Control Unit)是其中的一个重要组件。控制器是负责指挥和协调整个CPU工作的外围局部,它管制着指令的执行和数据的传输,是计算机执行程序的要害局部。上面将具体介绍控制器的作用和性能。 指令解码:控制器负责解码指令。它从内存中读取指令,并将其解析为对应的操作码和操作数。指令解码是将指令翻译成CPU能够了解和执行的管制信号的过程。时序管制:控制器生成和治理计算机外部的时序信号。它确定和控制指令的执行程序、数据传输的时序和各个组件之间的协调。时序管制保障计算机各个部件依照正确的程序和工夫执行操作。程序计数器(PC):控制器维护程序计数器,它保留着以后正在执行的指令的地址。控制器依据指令的执行程序,逐渐更新程序计数器的值,使得CPU可能依照程序的程序执行指令。分支和跳转:控制器负责解决分支和跳转指令。当遇到条件分支或无条件跳转指令时,控制器会依据条件或跳转指标更新程序计数器的值,以跳转到新的指令地址继续执行。这样,控制器实现了程序的流程管制。中断解决:控制器可能响应和解决中断信号。当产生硬件故障、内部事件或特定条件满足时,控制器会暂停以后的指令执行,保留现场状态,解决相应的中断服务程序,并在中断解决实现后返回到原来的执行点继续执行。数据传输管制:控制器协调和控制数据在CPU外部的传输。它将指令须要的数据从内存或寄存器中取出,依据指令要求进行数据传输和操作,并将后果写回内存或寄存器。控制器确保数据的正确传输和存储。管制信号生成:控制器生成管制信号,用于管制CPU外部各个部件的工作。这些管制信号包含时钟信号、使能信号、读写信号、操作抉择信号等。通过管制信号,控制器可能准确地管制和调度CPU外部各个部件的操作。异样解决。

May 18, 2023 · 1 min · jiezi

关于cpu:CPU-中运算器的作用

在计算机的中央处理器(CPU)中,运算器(Arithmetic Logic Unit,简称ALU)是其中的一个重要组件。运算器是负责执行各种算术和逻辑运算的外围局部,它是计算机进行计算和决策的要害局部。上面将具体介绍运算器的作用和性能。 算术运算:运算器可能执行各种算术运算,如加法、减法、乘法和除法。它能够对数字进行加减乘除等数学运算,以实现各种简单的计算工作。算术运算是计算机进行数值计算和数据处理的根底。逻辑运算:运算器也可能执行逻辑运算,如与(AND)、或(OR)、非(NOT)和异或(XOR)等。逻辑运算用于判断条件、比拟数据和执行布尔逻辑操作。它在管制流程和决策制定中起着重要作用。比拟运算:运算器还可能执行比拟运算,用于比拟两个数值的大小关系。通过比拟运算,能够判断两个数是否相等、大小关系以及逻辑条件是否满足。比拟运算是计算机程序中罕用的操作之一。位运算:运算器反对位级的运算,如按位与(AND)、按位或(OR)、按位非(NOT)和按位异或(XOR)等。位运算能够间接对二进制数据进行操作,对于位级解决和位掩码操作十分有用。数据移位:运算器能够执行数据的移位操作,包含逻辑左移、逻辑右移、算术右移和循环移位等。数据移位罕用于解决二进制数据、位操作和数据编码等畛域。数据处理:运算器不仅能够解决整数数据,还能够进行浮点数运算、定点数运算和逻辑数据处理。它反对各种数据格式和数值示意,可能解决不同精度和格局的数据。状态标记:运算器能够设置和更新一些状态标记,如零标记(Zero Flag)、进位标记(Carry Flag)、溢出标记(Overflow Flag)等。这些状态标记用于记录运算后果的一些属性和状态信息,供后续指令和程序判断和应用。管制单元交互:运算器与计算机的管制单元进行严密的交互。它接管管制单元收回的指令和数据,并执行相应的计算操作。

May 18, 2023 · 1 min · jiezi

关于cpu:从CPU的视角看-多线程代码为什么那么难写

 当咱们提到多线程、并发的时候,咱们就会回想起各种诡异的bug,比方各种线程平安问题甚至是利用解体,而且这些诡异的bug还很难复现。咱们不禁收回了灵魂拷问 “为什么代码测试环境运行好好的,一上线就不行了?”。 为了解决线程平安的问题,咱们的先辈们在编程语言中引入了各种各样新名词,就拿咱们相熟的Java为例,不仅java语言自带了synchronized、volatile、wait、notify… ,jdk中各种工具包也是层出不穷,就比方单一个Lock,就能够有很多种实现,甚至很多人都谈锁色变。 为什么会呈现这种状况,咱们得先从CPU和主存(RAM)的关系说起。 上个世纪80年代,PC机衰亡的时候,CPU的运算速度只有不到1MHz。放当初你桌上的计算器都能够吊打了它了。那时候就是因为CPU运算慢,它对数据存取速度的要求也不那么高,顶多也就1微秒(1000ns)取一次数据,一次访存100ns对CPU来说也算不上什么。 然而这么多年过来了,CPU始终在沿着摩尔定律的路线一路狂奔,而内存拜访提早的速度却始终止步不前。(当然存储也有十分大的倒退,但次要体现在容量方面,而拜访延时自诞生初就没什么变动)。 咱们来比照下CPU和内存过来几十年之间的倒退速率: 能够看出,在过来40年里, CPU的运算速度增量了上千倍,而内存的拜访延时却没有太大的变动。 咱们就拿当今最先进CPU和内存举例,目前商用的CPU主频根本都是3GHz左右的(其实十多年前基本上就这个程度了),算下来CPU每做一次运算仅需0.3ns(纳秒)。而以后最先进的内存,拜访提早是100ns左右的,两头相差300倍。如果把CPU比作一个打工人的话,那么他的工作状态就会是干一天活而后休一年,这劳动的一年里等着内存里的数据过去(真是令人羡慕啊)。 其实CPU的设计者早就意识到了这点,如果CPU真是干1休300的话,未免也太不高效了。在说具体解决方案前,我这里先额定说下内存,很多人会好奇为什么主存(RAM)的访问速度始终上不来? 这个精确来说其实只是DRAM内存的速度上不了。存储芯片的实现形式有两种,别离是DRAM和SRAM,SRAM的速度其实也始终尽可能跟着CPU在跑的。那为什么不必SRAM来制作内存?这个也很简略,就是因为它存储密度低而且巨贵(绝对于DRAM),所以出于老本考量当初内存条都是采纳DRAM的技术制作的。 SRAM容量小老本高,但速度快,DRAM容量大成本低,但速度慢。这俩能不能搭配应用,舍短取长?论断是必定的,在计算机科学里有个”局部性原理“,这个原理是计算机科学畛域所有优化的基石。我这里就单从数据拜访的局部性来说,某个地位的数据被拜访,那么相邻于这个地位的数据更容易被拜访。那么利用这点,咱们是不是能够把以后最可能被用到的小局部数据存储在SRAM里,而其余的局部持续保留在DRAM中,用很小的一块SRAM来当DRAM的缓存,基于这个思路,于是CPU芯片里就有了Cache,CPU的设计者们感觉一层缓存不够,那就给缓存再加一层缓存,于是大家就看到当初的CPU里有了所谓的什么L1 Cache、L2 Cache, L3 Cache。 存储示意图如下,实在CPU如右图(Intel I7某型号实物图): 多级缓存的呈现,极大水平解决了主存访问速度和CPU运算速度的矛盾,但这种设计也带来了一个新的问题。CPU运算时不间接和主存做数据交互,而是和L1 Cache交互,L1 cache 又是和L2 Cache交互…… 那么肯定意味着同一份数据被缓存了多份,各层存储之间的数据一致性如何保障? 如果是单线程还好,毕竟查问同一时间只会在一个外围上运行。但当多线程须要操作同一份数据时,数据一致性的问题就凸显进去了,如下图,咱们举个例子。 在上图中3个CPU外围各自的Cache别离持有了不同的a0值(先疏忽E和I标记),实际上只有Cache0里才持有正确的数值。这时候,如果CPU1或者CPU2须要拿着Cache中a0值去执行某些操作,那后果可想而知。如果想保障程序在多线程环境下正确运行,就首先得保障Cache里的数据能在"失当"的工夫生效,并且无效的数据也能被及时回写到主存里。 然而CPU是不晓得以后时刻下哪些数据该生效、哪些该回写、哪些又是能够接着应用的。这个时候其实CPU的设计者也很犯难,如果数据频繁生效,CPU每次获取必须从主存里获取数据,CPU理论运算能力将回到几十年前的程度。如果始终不给不生效,就会呈现数据不统一导致的问题。于是CPU的设计者不干了:”这个问题我解决不了,我给你们提供一些能够保证数据一致性的汇编指令,你们本人去解决”。 于是大家就在intel、arm的开发手册上看到了像xchg、lock、flush……之类的汇编指令,C/C++语言和操作系统的开发者将这些封装成了volatile、atomic……以及各种零碎调用,JVM和JDK的开发者又把这些封装了我在文首说的那一堆关键词。 于是CPU的设计者为了晋升性能导致数据一致性的问题,最终还是推给了下层开发者本人去解决。 作为下层的开发者们(比方咱们)就得判断,在多线程环境下那些数据操作必须是原子操作的,这个时候必须应用Unsafe.compareAndSwap()来操作。还有那些数据是不能被CPU Cache缓存的,这个时候就得加volatile关键词。极其状况下,你能够所有的操作搞成原子操作、所有的变量都申明成volatile,尽管这样确实能够保障线程平安,但也会因为主存拜访延时的问题,显著升高代码运行的速度。这个时候局部性原理又施展出其神奇的价值,在理论状况下,绝大多数场景都是线程平安的,咱们只须要保障某些要害操作的线程安全性即可。举个简略的例子,咱们在工作向多线程散发的时候,只须要保障一个工作同时只被分发给一个线程即可,而不须要保障整个工作执行的过程都是齐全线程平安的。 作为Java开发者,Java和JDK的开发者们曾经帮咱们在很多场景下封装好了这些工具,比方咱们就拿ReentrantLock实现一个多线程计数器的例子来看。 其中increment() 自身不是一个线程平安的办法,如果多个线程并发去调用,依然会呈现count值增长不精确的问题。但在lock的加持下,咱们能保障increment()办法同时只能有一个线程在执行。设想下,如果咱们把上述代码中的counter()办法换成一些更简单的办法,而齐全不须要在办法中去思考线程平安的问题,这不就实现了仅在要害操作上保障准确性就能保障全局的线程平安吗!而当咱们去深究lock的实现时,就会发现它底层也只是在tryAcquire中应用CAS设置了state值。 在多线程编程中,加锁或加同步其实是最简略的,然而在什么时候什么中央加锁却是一件非常复杂的事件。你须要思考锁的粒度的问题,粒度太大可能影响性能,粒度过小可能导致线程平安的问题。还须要思考到加锁程序的问题,加锁程序不当可能会导致死锁。还要思考数据同步的问题,同步的数据越多,CPU Cache带来的性能晋升也就越少…… 从下面CPU的倒退变动咱们能够看到,古代CPU的实质其实也是一个分布式系统,很多时候仍须要编程者手动去解决数据不一致性的问题。当然随着编程语言的倒退,这些底层相干的货色也逐步对一般程序员变得更透明化,咱们是不是能够料想,将来是不是会有一门高性能、并且齐全不须要程序员关注数据一致性的编程语言呈现? 最初下面计数器代码给大家留一个思考题: 代码中的counter变量申明是否须要加volatile关键字?

April 30, 2023 · 1 min · jiezi

关于cpu:CPU基础知识详解

冯·诺依曼计算机 冯·诺依曼计算机由存储器、运算器、输出设施、输出设备和控制器五局部组成。 哈佛构造 哈佛构造是一种将程序指令存储和数据存储离开的存储器构造,它的次要特点是将程序和数据存储在不同的存储空间中,即程序存储器和数据存储器是两个独立的存储器,每个存储器独立编址、独立拜访,目标是为了加重程序运行时的访存瓶颈。哈佛架构的中央处理器典型代表ARM9/10及后续ARMv8的处理器,例如:华为鲲鹏920处理器。 组成计算机的根底硬件都须要与主板(Motherboard)连贯 计算机根底硬件 (2) Opening the Box(Apple IPad2) 手机的内部结构 – 华为Mate30 Pro 主板(来自于 Tech Insights) 主板 反面 射频板 Inside the Processor (CPU) Datapath(数据通路): performs operationson dataControl: sequences datapath, memory, ...Register 寄存器Cache memory 缓存 Small, fast: SRAM(动态随机拜访存储器)memory for immediate access to data Intel Core i7-5960X 毅力号CPU曝光:250nm工艺、23年旧架构、主频仅233MHz 毅力号搭载的处理器是20多年前技术的产品。处理器型号为PowerPC 750处理器,与1998年苹果出品的iMac G3 电脑同款,PowerPC 750 处理器最高主频速度仅233MHz,且晶体管数量也只有600 万个,但单价仍高达20 万美元(约130万元)。抗辐射、耐凛冽-55~125℃ 比照苹果最近推出的M1ARM 架构处理器领有最高主频3.2GHz,晶体管数量达160 亿个。 ...

March 19, 2023 · 6 min · jiezi

关于cpu:从一次CPU打满到ReDos攻击和防范

作者:京东物流 刘海茂 近期碰到一起值班报警事件,web 应用服务器 CPU 耗费打到 99%,排查后发现是因为 ReDoS 导致了服务器产生了资源被耗尽、拜访零碎迟缓的问题,通过排查过程从而分享下 ReDos 攻打的原理、常见场景以及防备和解决方案,如果有谬误欢送斧正。 背景值班的时候忽然报警,web 应用服务器 CPU 耗费打到 99%,同时现场反馈系统拜访迟缓 登录泰山平台,查看 ump 监控发现零碎耗费 CPU 耗费忽然被打满 通过 java 自带的 dump 工具,下载 jstock 文件,发现有大量雷同工作线程在运行,具体的堆栈信息如下 认真查看这些线程的执行代码,发现都调用了 UrlUtil.extractDomain 这个办法 依据堆栈信息查看业务代码,发现是 joybuy 登录拦截器用正则表达式匹配拜访 url 解析主域的办法呈现了阻塞,至此,能够判断是因为 ReDoS 导致了服务器产生了资源被耗尽、拜访零碎迟缓的问题,那么,什么是 ReDoS 呢? ReDos 简介ReDoS 攻打(正则表达式拒绝服务攻打 (Regular Expression Denial of Service)),攻击者可结构非凡的字符串,导致正则表达式运行会耗费大量的内存和 cpu 导致服务器资源被耗尽。无奈持续响应,那为何不确定的正则表达式会导致 redos 攻打呢?这得从正则表达式的实现原理说起 原理 目前实现正则表达式引擎的形式有两种 DFA 自动机(Deterministic Finite Automaton,确定无限状态自动机)NFA 自动机(Nondeterministic Finite Automaton,非确定无限状态自动机)DFA 自动机的结构代价远大于 NFA 自动机,但 DFA 自动机的执行效率高于 NFA 自动机假如一个字符串的长度为 n,如果采纳 DFA 自动机作为正则表达式引擎,则匹配的工夫复杂度为 O (n)如果采纳 NFA 自动机作为正则表达式引擎,NFA 自动机在匹配过程中存在大量的分支和回溯,假如 NFA 的状态数为 s,则匹配的工夫复杂度为 O(ns)NFA 自动机的劣势是反对更多高级性能,但都是基于子表达式独立进行匹配因而在编程语言里,应用的正则表达式库都是基于 NFA 自动机实现的NFA 的个性: ...

March 1, 2023 · 2 min · jiezi

关于cpu:DatenLord前沿技术分享-No13

1、演讲题目异步事件驱动的电路机制 & 基于RISC-V的全异步超标量CPU体系结构 2、演讲工夫2022年12月25日上午10:30 3、演讲人何安平 副教授 4、引言在传统硅基芯片畛域,随着芯片规模的急剧增大和半导体工艺的继续提高,解决超大规模集成电路中带来的跨时钟域、时钟抖动等引起的问题日益突出,为解决以上问题,一种勾销时钟机制的电路状态异步电路产生。异步电路类型泛滥,基于束缚绑定握手协定的新型绑定型异步电路是以后异步电路的最新进展,咱们将联合基于RISC-V的全异步CPU体系结构展开讨论。 5、内容简介异步电路的工作机制由电路逻辑产生的事件来实现,不依赖于晶振产生的时钟 。特定工作合成为事件流(序列),事件流驱动异步芯片执行工作,事件与数据可对立编码,也可拆散解决,最终造成具备特定性能的电路子系统,子系统受事件驱动而履行其性能。咱们课题组基于异步机制已研发7颗全异步芯片其中40nm制程下的1512颗轻量级众核芯片的典型功耗仅98毫瓦,面积96平方毫米,蕴含3.5亿颗晶体管。 6、直播预约欢迎您预约直播,或者登陆腾讯会议观看直播: 会议号:581-8301-3525

December 23, 2022 · 1 min · jiezi

关于cpu:性能透明提升-50SMC-ERDMA-云上超大规模高性能网络协议栈

编者按:以后内核网络协议栈有什么问题?新的协定栈是不是从新创造轮子?一个协定栈是否解决所有问题?适配所有场景?本文整顿自2022 年阿里巴巴开源凋谢周技术演讲,这里咱们将本人的思考分享进去,和大家一起交换。视频回放已上线至龙蜥官网(首页-动静-视频),欢送大家观看。 本文次要分为三局部:第一局部是咱们为什么须要一个新的内核网络协议栈,咱们是不是在反复创造轮子?第二局部是 SMC + ERDMA 的原理、优劣等等,疾速为大家理解 SMC 技术。第三局部是 SMC-R 在网易 Curve 分布式系统的实际。 一、咱们为什么须要一个新的内核网络协议栈?以后内核网络协议栈有什么问题?新的协定栈是不是从新创造轮子?一个协定栈是否解决所有问题?适配所有场景?这里咱们将本人的思考分享进去,和大家一起交换。 首先我想要抛出一个观点,没有一个网络栈是万能的,之于此没有银弹。要谈现状,离不开背景: 第一是 100G/400G 网络的遍及,此时 CPU 是瓶颈。第二是云、规模,以后越来越多的业务迁徙到云上,云上反对传统 RDMA 网卡老本很高。第三是 DPU,硬件卸载,承接第一点,CPU 成为瓶颈后,是否能够让网络栈将越来越多的逻辑卸载到网卡和 DPU 上,让 CPU 做更多更重要的事件。咱们如何均衡吞吐和时延?或者说如何用起码的 CPU 达到雷同的吞吐,如何尽可能地升高时延。首先 Linux 内核的网络栈偏向于吞吐,而不是时延。晋升吞吐很重的一点是,升高拷贝的开销。在大包吞吐的场景,咱们很容易看到拷贝占据了 CPU 的绝大部分工夫。而且内核网络栈的 context switch 开销,拷贝开销也会减少额定的时延。 那么这个问题变成了抉择在内核态还是用户态实现一个网络栈?我想很多利用,或者说云上 99% 以上的利用应用的是 socket 接口,如果侵入性革新,对于用户态计划最大的壁垒。比方 DPDK 等,此时不仅仅是革新老本,也包含了运维老本、部署老本等等。当然用户态也能够劫持 socket 实现用户态协定栈,但此时 zero copy 等劣势不在。并且同样须要革新,此处的革新是运维革新,调度革新和环境革新,这也是额定的老本。此时用户态的劣势也不再显著。 软件 vs 硬件卸载?一开始 Linux 网络栈,例如 TCP 协定是从纯软件实现到越来越多的将性能卸载到网卡,甚至 TOE 齐全卸载到网卡。如果提到卸载到网卡,是不是能够用一种更加成熟的硬件卸载计划?也就是 RDMA,也就是传统以太网卡 vs RDMA 网卡,局部卸载到成熟的齐全卸载。RDMA 网络自身有规模的限度,咱们能够在小规模把 RDMA 网络玩得很好。那是否有一种大规模 RDMA 的能力。咱们当初看到的是阿里云公布的 ERDMA 网络,一种普惠、高性能、齐全兼容 RDMA 的生态。咱们借助 SMC + ERDMA 能够实现硬件卸载 RDMA 、大规模部署,二者相辅相成。 ...

September 16, 2022 · 3 min · jiezi

关于cpu:如何理解处理器CPU多处理器内核多核

最早的时候,人们制作了一台计算用的集成电路(Circuit,或者IC) ,用树脂包起来,做成一个小片片(Chip),把它装在电路板(PCB)上,做成一台计算机(Computer),他们把这个小片片和其它小片片做了辨别,叫它中央处理器(CPU),写软件的人简略叫它处理器(Processor)。 这个阶段,不同的词,它们指的根本是同一个货色。 起初技术倒退了,为了提供更强的能力,他们首先把多个Chip装到同一个PCB中,这样,就呈现一台计算机,有多个Chip的状况,他们把这个叫做Multi-Processor,MP。如果这些Processor能够一样看待,不须要每个非凡解决,就叫同构多解决(SMP),否则就叫异步多解决(AMP)。 MP要把电路拉远,没有封装在一台计算机外面高效,所以又有人在一个Chip外面,把计算的电路做了多份,每份叫一个“外围”或者你这里翻译为内核了(Core),而仍把封装起来的整个Chip叫一个CPU。 但软件曾经把一个执行的硬件单元叫CPU了(也就是硬件认为的Core),从这里开始,软件和硬件用的名字指的就不是一个货色了。而对中文来说,软件把操作系统用来治理所有资源的那个被爱护的软件部件,也叫内核(Kernel),而把每个被治理的软件部件,叫解决者(Process,中文翻译为“过程”),这就更加容易引起人们的误会。实际上,无论Kernel还是Process,都是软件,是运行在CPU/Core上的货色,只是软件外部的辨别。就好比咱们人的躯体和思考的关系一样,CPU是躯体,软件是魂魄,Kernel是魂,Process是魄(当然咱们不细究两者确切的语义) Core越来越多,一个电路曾经很难制作了,他们开始离开造,再封装在一个Chip外面,那个独立制作的电路,有人叫它一个DIE,也有人叫它一个Cluster。软件其实不关怀,软件只关怀一共有多少“CPU”(芯片说的“Core”),但CPU们地位不一样,导致拜访内存的速度不一样,所以软件会认为他们在不同的“区域”(zone)上。 为了进一步优化Core,设计师发现Core的很多电路大部分时候都闲着,他们就让多个核复用这些电路。这样看起来一个硬件的Core,提供了多个软件看到的CPU,他们把这样的CPU,叫“硬线程”(hyper-thread)。 好了,不晓得你作为软件工程师晕掉没有?软件工程师就要求:别那么多废话了,就说你有多少个“假CPU”吧,我都对立解决好了。 但硬件说,我有30个A cpu,10个B CPU,4个C CPU,15个MPU(微处理器,了解为更简略的CPU吧),其中A CPU里有2连个硬线程,三种连法,B有一个硬线程,四种配置… 软件说,打住打住……算了,这样吧,咱们离开说,你CPU都一样给我的,咱们叫它SMP(留神这个名字的意思曾经和后面不同了),这种CPU我对立用,内存也对立拜访。如果你不是这样的,我叫它“非对称多解决”(AMP),那个你再一个个跟我说怎么用好了。 读者晕掉没有?有没有都来让我来洗一下脑,都用咱们服务器芯片上的定义吧:) 一个独立的硬件处理单元,被软件看做是“CPU”的货色,咱们叫Core。但有例外:Core能够有多个超线程(hyper-thread,HT),如果有HT,每个HT就是一个软件看到的CPU,但它并不具备所有计算资源。 多个共享一级cache的Core,咱们叫一个Cluster。 多个cluster组合能够被自由组合封装的电路,咱们叫一个超级 CPU CLUSTER(SCCL)。咱们还能够把网卡,sas等货色封装成超级IO cluster(SICL),从而提供超过PCIE速度的更靠近CPU的IO设施(比方100G的网口)。 把多个sccl和sicl组合在一起,封在树脂里,咱们称为一个chip。 多个chip放到一个电路板中,外面的core作为SMP应用,每个chip称为一个Processor,或者从PCB的角度,又叫一个插槽(Socket)。 电路板加上各种外网电路,封装成一台服务器,咱们叫一台Machine,多个Machine组合成一个柜子,咱们称为一个Rack。对外做广告,咱们称咱们一个Rack能够提供超过1万个,工作主频2.6G+的Core:

September 11, 2022 · 1 min · jiezi

关于cpu:性能提升1倍成本直降50基于龙蜥指令加速的下一代云原生网关

文 / Intel Arch SIG 技术背景网络信息传输的可靠性、机密性和完整性要求日渐晋升,HTTPS 协定曾经广泛应用。HTTPS 的 SSL/TLS 协定波及加解密、校验、签名等密码学计算,耗费较多 CPU 计算资源。因而 CPU 硬件厂商推出过多种减速卸载计划,如 AES-NI、QAT、KAE、ARMv8 平安扩大等。 业界软件生态在优化 HTTPS 的性能上也做了诸多摸索(参考[1]),传统的软件优化计划有 Session 复用、OCSP Stapling、False Start、dynamic record size、TLS1.3、HSTS 等, 但软件层面的优化无奈满足流量日益增长的速度,CPU 硬件加速成为业界一个通用的解决方案。 CPU 新个性不久前公布的第三代英特尔®至强®可扩大处理器(代号 Ice Lake),单核性能晋升 30%,整机算力晋升 50% 以上(参考[9])。 ISA 指令集在传统的 AES-NI 减速指令根底上,Ice Lake 新增了基于 Intel® Advanced Vector Extensions 512(Intel® AVX-512)的 Intel® Crypto Acceleration 个性,包含 Vector AES(VAES)、Integer Fused Multiply Add(IFMA 大数计算)、Galois Field New Instructions(GFNI)等(参考[2])。同时也补充反对了 SHA extension(SHA-NI),补足了上一代的 SHA256 性能短板。 (图 1/ Ice Lake 新增 Intel AVX512 指令集) ...

August 31, 2022 · 2 min · jiezi

关于cpu:关于-Intel-在-microvm-快速启动的探索及实例演示-第-3638-期

本周「龙蜥大讲堂」预报来啦!邀请了龙蜥云原生 SIG 成员及 Intel 资深云计算软件工程师臧锐来分享《云原生 SIG 组件引入》《利用 micro-VM 快照机制对 FaaS 冷启动减速的摸索与实际》、鉴释科技 CEO 梁宇宁分享《RISC-V 高性能编译器》、龙蜥云原生 SIG 成员分享《RunD ATC 论文分享与开源介绍》,快来扫码入群,预约前排小板凳观看直播吧! 直播主题及内容介绍一、利用 micro-VM 快照机制对 FaaS 冷启动减速的摸索与实际 直播工夫:2022 年 08 月 02 日 (周二)14:00-16:00 直播内容: 议题 1:龙蜥云原生 SIG 组件引入标准 议题 2:技术分享:利用 micro-VM 快照机制对 FaaS 冷启动减速的摸索与实际 听众受害: 本次双周会会进一步探讨 SIG 组件引入的标准,同时由 Intel 资深云计算软件架构师介绍 Intel 在 micro-vm 疾速启动的摸索,听众能够获取云原生函数计算场景下,实例疾速置备、疾速启动的解决方案。 适宜人群:容器、函数计算、云原生畛域的开发者、爱好者。 讲师介绍: 臧锐:龙蜥云原生 SIG 成员、Intel 资深云计算软件工程师 如何加入直播? 钉钉扫描下方海报二维码入群即可加入本次直播。 二、RISC-V 高性能编译器 直播工夫:2022 年 08 月 03 日 (周三)16:00-17:00 直播内容: ...

August 3, 2022 · 2 min · jiezi

关于cpu:SPDK对接Ceph性能优化

作者:天翼云 谭龙关键词:SPDK、NVMeOF、Ceph、CPU负载平衡 SPDK是intel公司主导开发的一套存储高性能开发套件,提供了一组工具和库,用于编写高性能、可扩大和用户态存储利用。它通过应用一些关键技术实现了高性能:1.将所有必须的驱动程序移到用户空间,以防止零碎调用并且反对零拷贝拜访2.IO的实现通过轮询硬件而不是依赖中断,以升高时延3.应用消息传递,以防止IO门路中应用锁SPDK是一个框架而不是分布式系统,它的基石是用户态(user space)、轮询(polled-mode)、异步(asynchronous)和无锁的NVMe驱动,其提供了零拷贝、高并发间接用户态拜访SSD的个性。SPDK的最后目标是为了优化块存储落盘性能,但随同继续的演进,曾经被用于优化各种存储协定栈。SPDK架构分为协定层、服务层和驱动层,协定层蕴含NVMeOF Target、vhost-nvme Target、iscsi Target、vhost-scsi Target以及vhost-blk Target等,服务层蕴含LV、Raid、AIO、malloc以及Ceph RBD等,driver层次要是NVMeOF initiator、NVMe PCIe、virtio以及其余用于长久化内存的driver等。 SPDK架构Ceph是目前利用比拟宽泛的一种分布式存储,它提供了块、对象和文件等存储服务,SPDK很早就反对连贯Ceph RBD作为块存储服务,咱们在应用SPDK测试RBD做性能测试时发现性能达到肯定规格后无奈持续晋升,影响产品的综合性能,通过多种定位办法并联合现场与代码剖析,最终定位问题起因并解决,过程如下。1.测试方法:启动SPDK nvmf_tgt并绑定0~7号核,./build/bin/nvmf_tgt -m 0xff,创立8个rbd bdev,8个nvmf subsystem,每个rbd bdev作为namespace attach到nvmf subsystem上,开启监听,initiator端通过nvme over rdma连贯每一个subsystem,生成8个nvme bdev,应用fio对8个nvme bdev同时进行性能测试。2.问题:咱们搭建了一个48 OSD的Ceph全闪集群,集群性能大概40w IOPS,咱们发现最高跑到20w IOPS就上不去了,无论减少盘数或调节其余参数均不见效。3.剖析定位:应用spdk_top显示0号核绝对其余核更加繁忙,持续加压,0号核繁忙水平减少而其余核则减少不显著。 查看poller显示rbd只有一个poller bdev_rbd_group_poll,与nvmf_tgt_poll_group_0都运行在id为2的thread上,而nvmf_tgt_poll_group_0是运行在0号核上的,故bdev_rbd_group_poll也运行在0号核。[root@test]# spdk_rpc.py thread_get_pollers{  "tick_rate": 2300000000,  "threads": [    {      "timed_pollers": [        {          "period_ticks": 23000000,          "run_count": 77622,          "busy_count": 0,          "state": "waiting",          "name": "nvmf_tgt_accept"        },        {          "period_ticks": 9200000,          "run_count": 194034,          "busy_count": 194034,          "state": "waiting",          "name": "rpc_subsystem_poll"        }      ],      "active_pollers": [],      "paused_pollers": [],      "id": 1,      "name": "app_thread"    },    {      "timed_pollers": [],      "active_pollers": [        {          "run_count": 5919074761,          "busy_count": 0,          "state": "waiting",          "name": "nvmf_poll_group_poll"        },        {          "run_count": 40969661,          "busy_count": 0,          "state": "waiting",          "name": "bdev_rbd_group_poll"        }      ],      "paused_pollers": [],      "id": 2,      "name": "nvmf_tgt_poll_group_0"    },    {      "timed_pollers": [],      "active_pollers": [        {          "run_count": 5937329587,          "busy_count": 0,          "state": "waiting",          "name": "nvmf_poll_group_poll"        }      ],      "paused_pollers": [],      "id": 3,      "name": "nvmf_tgt_poll_group_1"    },    {      "timed_pollers": [],      "active_pollers": [        {          "run_count": 5927158562,          "busy_count": 0,          "state": "waiting",          "name": "nvmf_poll_group_poll"        }      ],      "paused_pollers": [],      "id": 4,      "name": "nvmf_tgt_poll_group_2"    },    {      "timed_pollers": [],      "active_pollers": [        {          "run_count": 5971529095,          "busy_count": 0,          "state": "waiting",          "name": "nvmf_poll_group_poll"        }      ],      "paused_pollers": [],      "id": 5,      "name": "nvmf_tgt_poll_group_3"    },    {      "timed_pollers": [],      "active_pollers": [        {          "run_count": 5923260338,          "busy_count": 0,          "state": "waiting",          "name": "nvmf_poll_group_poll"        }      ],      "paused_pollers": [],      "id": 6,      "name": "nvmf_tgt_poll_group_4"    },    {      "timed_pollers": [],      "active_pollers": [        {          "run_count": 5968032945,          "busy_count": 0,          "state": "waiting",          "name": "nvmf_poll_group_poll"        }      ],      "paused_pollers": [],      "id": 7,      "name": "nvmf_tgt_poll_group_5"    },    {      "timed_pollers": [],      "active_pollers": [        {          "run_count": 5931553507,          "busy_count": 0,          "state": "waiting",          "name": "nvmf_poll_group_poll"        }      ],      "paused_pollers": [],      "id": 8,      "name": "nvmf_tgt_poll_group_6"    },    {      "timed_pollers": [],      "active_pollers": [        {          "run_count": 5058745767,          "busy_count": 0,          "state": "waiting",          "name": "nvmf_poll_group_poll"        }      ],      "paused_pollers": [],      "id": 9,      "name": "nvmf_tgt_poll_group_7"    }  ]}再联合代码剖析,rbd模块加载时会将创立io_channel的接口bdev_rbd_create_cb向外注册,rbd bdev在创立rbd bdev时默认做bdev_examine,这个流程会创立一次io_channel,而后销毁。在将rbd bdev attach到nvmf subsystem时,会调用创立io_channel接口,因为nvmf_tgt有8个线程,所以会调用8次创立io_channel接口,但disk->main_td总是第一次调用者的线程,即nvmf_tgt_poll_group_0,而每个IO达到rbd模块后bdev_rbd_submit_request接口会将IO上下文调度到disk->main_td,所以每个rbd的线程都运行在0号核上。 ...

June 2, 2022 · 2 min · jiezi

关于cpu:故障分析-大量短时进程导致-cpu-负载过高案例一则

作者:任坤 现居珠海,先后负责专职 Oracle 和 MySQL DBA,当初次要负责 MySQL、mongoDB 和 Redis 保护工作。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 1、背景某我的项目的开发环境,单台虚拟机装了1套mongo集群用于测试,1个mongos + 3节点config + 1shard * 3正本,总计7个mongo实例。 mongo版本4.2.19,OS为centos 7.9。 测试完结后cpu负载始终维持在50%左右,而此时mongo的qps曾经降落为0。 这台机器上只装置了mongo,将所有mongo实例敞开,cpu负载立刻恢复正常,再将mongo实例开启,过了一会cpu负载又开始飙升。场景能复现,且确认是跟mongo实例有关系。 2、诊断执行top命令,cpu的usr曾经达到了40%,然而前几个过程的%cpu加起来远远凑不够数。 查看mongos的qps,的确没有执行用户命令了。 dstat查看整体负载(vmstat格式化做的不好,最初几列总是对不齐整)。 除了cpu负载不失常,其余指标均失常,中断和上下文切换也不算高,不太可能是这两个引发的。perf record -ag -- sleep 10 && perf report 查看cpu执行状况。 的确有大量mongo调用,然而API命名不直观,无奈猜想对应的执行逻辑。 至此,确认是mongo实例引发的问题,然而mongo的利用连贯为0,看调用API栈也找不到有用信息。 回到本文结尾,top过程的cpu利用率加起来远远小于cpu总体负载,大概率是有频繁短时过程偷走了这部分CPU资源,导致top命令来不及捕捉统计。 sar -w 1 查看每秒生成的过程数,均匀每秒新建80多个过程,应该就是它了。 要抓出频繁建设短时过程的利用,能够采纳execsnoop,该工具通过 ftrace 实时监控过程的 exec() 行为,并输入短时过程的根本信息, 包含过程 PID/PPID、命令行参数。 #下载execsnoop#cd /usr/binwget https://raw.githubusercontent.com/brendangregg/perf‐tools/master/execsnoopchmod 755 execsnoop 以下是输入内容,全是监控零碎在执行,不停的连贯mongo并对输入后果执行grep过滤,每个操作都会衍生一个新线程/过程,10s捕捉 了400多条记录。 将zabixx过程敞开,cpu马上恢复正常,找到了首恶。 咱们其余环境也采纳了zabbix监控,然而都没有遇到相似问题。 该节点部署了7个mongo实例,zabbix默认对每个mongo实例都进行监控,相当于执行损耗放大了7倍,而该机器是一台只有4核CPU的虚拟机。 这些因素凑齐了就会暴发问题。 这是个开发环境,临时敞开了zabbix监控,后续要对监控逻辑进行优化,尽量减少连贯db的次数以及grep调用链的长度。 3、小结当机器cpu负载继续低落却抓取不到top过程时,能够采纳execsnoop抓取短时过程,相似工具还有iosnoop、opensnoop。 ...

May 26, 2022 · 1 min · jiezi

关于cpu:嵌入式系统文件系统开发缓存的应用

一、前言最近开发一基于嵌入式零碎下的“文件系统”,前期测试呈现一些离奇的BUG,经审查和排故发现是开启了缓存加上DMA搬运数据导致的CACHE不统一问题。当初就来分享一下。 二、缓存改文件系统的利用场景是基于嵌入式操作系统,实现数据的无效记录。硬件设计基于Z7,BSP默认关上了CPU和主存之间的cache, 且写数据形式为写贯通。利用运行中,CPU通过cache拜访操作主存,而读写电子盘的驱动默认采纳DMA搬运数据到主存,这是cache不统一的根本原因。本文先介绍一下几个概念: cache line CPU cache的构造是由很多Cache Line组成的。每条cache line都有两个标记位。无效位(valid bit) 示意cache line的数据是否无效。零碎刚启动时,数据都置有效。脏位(dirty bit) 示意cache line的数据是否和下一级缓存统一。0统一,1不统一命中(Hit) CPU要拜访的数据在Cache中有缓存。成为命中(Hit),反之为缺失(Miss)DMA(Direct Memory Acess) 间接存储器拜访,是一种不通过CPU而间接从内存存取数据的数据交换形式。否则,CPU须要从起源把每一片段的材料复制到暂存器,而后把它们再次写回新的中央。三、缓存的读写形式写中转(write through) 任一从CPU收回的写信号送到cache的同时,也写入主存,以保障主存的数据可能同步更新。写回(write back) 如果当产生写操作时,数据曾经在 CPU Cache 里的话,则把数据更新到 CPU Cache 里,同时标记 CPU Cache 里的这个 Cache Block 为脏(Cache Block 的数据和内存是不统一);如果当产生写操作时,数据所对应的 Cache Block 里寄存的是「别的内存地址的数据」的话,就要检 查这个 Cache Block 里的数据有没有被标记为脏的,如果是脏的话,咱们就要把这个 Cache Block 里的数据写回到内存,而后再把以后要写入的数据,写入到这个 Cache Block 里,同时也把它标记为 脏的;如果 Cache Block 外面的数据没有被标记为脏,则就间接将数据写入到这个 Cache Block 里,而后再把这个 Cache Block 标记为脏。 读贯通(read through)CPU的所有对主存的数据申请都先送到cache,如果命中,则不申请拜访主存,并将数据送出;如果不命中,则向主存申请数据。读旁路(read aside) CPU收回数据申请时,并不是单通道地穿过Cache。而是向Cache和主存同时发出请求。因为Cache速度更快,如果命中,则Cache在将数据回送给CPU的同时,还来得及中断CPU对主存的申请;不命中。则Cache不做任何动作。由CPU间接拜访主存。四、利用场景文件系统是基于vxWorks开发的。针对CACHE不统一问题,剖析vxWorks零碎提供的cacheLib,提出两种解决办法: 所有通过DMA操作的数据都用cacheDmaMalloc申请内存空间 默认用malloc申请的内存不是缓存平安的。用cacheDmaMalloc能够为DMA设施和驱动调配缓存平安的内存缓冲。调用cacheFlush和cacheInvalidate解决问题 cacheFlush强制将缓冲的数据更新到内存。对于写贯通类型,cacheFlush什么都不须要做因为内存和缓存条目是匹配的。cacheInvalidate将所有的缓冲条目都设置为有效,齐全切断内存和缓冲之间的分割。本我的项目采纳的是第二种形式,间接在调用磁盘的读写驱动处减少cacheFlush和cacheInvalidate。在调用写驱动之前,调用cacheFlush强制将缓冲的数据更新到内存,保障写入磁盘的数据是从cache中拿到的最新的数据。在调用读驱动后,调用cacheInvalidate切断以后内存区域和cache的分割,保障后续CPU拜访该区域的时候,可能间接拜访内存而不是缓存中可能存在的旧数据。 int rawFsBlkWrt(unsigned int startsector,int nsectors,char *pdata, const char *devname){ STATUS stats = ERROR; BLK_DEV * pdev = NULL; int ldrs_num = -1; int i = 0; for(i=0;i<LDRS_NUM;i++) { if(ldrs_handle[i].ldrs_valid_flag == LDRS_HANDLE_VALID_FLAG) { if(0==strcmp(devname,ldrs_handle[i].ldrs_name)) { ldrs_num = i; break; } } } if(ldrs_num<0) { ldrs_errno = ERR_BLKDEV_INVALID; printf("ERR_BLKDEV_INVALID %s",(char *)devname); return -1; } if(pdata == NULL) { ldrs_errno = ERR_ADDR_IS_NULL; return ldrs_errno; } if(ERROR == semTake(ldrs_handle[ldrs_num].sem_blkdev,sysClkRateGet()*30)) { ldrs_errno = ERR_SEMPHONE_TAKE; return ldrs_errno; } cacheFlush(DATA_CACHE, (void*)pdata, nsectors); stats = fsBlkWrt(pdev,startsector,nsectors,pdata); semGive(ldrs_handle[ldrs_num].sem_blkdev); if(stats == ERROR) { ldrs_errno = ERR_BLK_WRITE; return ldrs_errno; } return DISC_OK; }int rawFsBlkRd(unsigned int startsector,int nsectors,char *pdata, const char *devname){ STATUS stats = ERROR; BLK_DEV * pdev = NULL; int ldrs_num = -1; int i = 0; for(i=0;i<LDRS_NUM;i++) { if(0==strcmp(devname,ldrs_handle[i].ldrs_name)) { ldrs_num = i; break; } } if(ldrs_num<0) { ldrs_errno = ERR_BLKDEV_INVALID; printf("ERR_BLKDEV_INVALID %s",(char *)devname); return -1; } if(pdata == NULL) { ldrs_errno = ERR_ADDR_IS_NULL; return ldrs_errno; } if(ERROR == semTake(ldrs_handle[ldrs_num].sem_blkdev,sysClkRateGet()*30)) { ldrs_errno = ERR_SEMPHONE_TAKE; return ldrs_errno; } stats = fsBlkRd(pdev,startsector,nsectors,pdata); cacheInvalidate(DATA_CACHE, pdata, nsectors); semGive(ldrs_handle[ldrs_num].sem_blkdev); if(stats == ERROR) { ldrs_errno = ERR_BLK_READ; return ldrs_errno; } return DISC_OK; }

January 28, 2022 · 2 min · jiezi

关于cpu:程序猿之电脑CPU的妙用

过期的电脑除了卖给废品收购站外,还有别的用处吗?:用电脑的CPU当作电热器来热早餐!无奈晓得理论的加热成果如何,不过想必厂商是不会心愿看到他们的处理器还有充当电热器的作用吧? CPU充当电热器 这个所谓的“早餐盘”具备一台规范电脑的所有组件,包含显示器、主板、显卡、硬盘、处理器等。不同于惯例电脑的是,它没有机箱,取而代之的是一块槽形木板扣在主板上。木板上在处理器的地位开了一个圆形的洞,洞的大小跟咖啡杯差不多。 AMD K5处理器 这台“早餐盘电脑”采纳的处理器为133MHZ的 AMD K5。整个零碎的工作原理异样简略,只有关上电脑失常工作,而后把咖啡杯间接放在处理器上加热就行了。同时在小木板上还能够放上面包和小菜,这样就能够边打电脑边吃早点啦!CPU上煮咖啡滋味看起来着实不错

September 17, 2021 · 1 min · jiezi

关于cpu:干掉讨厌的-CPU-限流让容器跑得更快

简介: 让人厌恶的 CPU 限流影响容器运行,有时人们不得不就义容器部署密度来防止 CPU 限流呈现。本文介绍的 CPU Burst 技术能够帮忙您既能保障容器运行服务质量,又不升高容器部署密度。文章分为高低两篇,该文为上篇,下篇将分析应用 CPU Burst 和其余防止限流伎俩的区别,以及如何配置 CPU Burst 性能以达到最佳成果。 在 K8S 容器调度中,容器的 CPU 资源下限是由 CPU limits 参数指定。设置 CPU 资源下限能够限度个别容器耗费过多的 CPU 运行工夫,并确保其余容器拿到足够的 CPU 资源。CPU limits 限度在 Linux 内核中是用 CPU Bandwidth Controller 实现的,它通过 CPU限流限度 cgroup 的资源耗费。所以当一个容器中的过程应用了超过 CPU limits 的资源的时候,这些过程就会被 CPU 限流,他们应用的 CPU 工夫就会受到限制,过程中一些要害的提早指标就会变差。 面对这种状况,咱们应该怎么办呢?个别状况下,咱们会联合这个容器日常峰值的 CPU 利用率并乘以一个绝对平安的系数来设置这个容器的 CPU limits ,这样咱们既能够防止容器因为限流而导致的服务质量变差,同时也能够兼顾 CPU 资源的利用。举个简略的例子,咱们有一个容器,他日常峰值的 CPU 使用率在 250% 左右,那么咱们就把容器 CPU limits 设置到 400% 来保障容器服务质量,此时容器的 CPU 利用率是 62.5%(250%/400%)。 然而生存真的那么美妙么?显然不是!CPU 限流的呈现比预期频繁了很多。怎么办?仿佛看上去咱们只能持续调大 CPU limits 来解决这个问题。很多时候,当容器的 CPU limits 被放大 5~10 倍的时候,这个容器的服务质量才失去了比拟好的保障,相应的这时容器的总 CPU 利用率只有 10%~20%。所以为了应答可能的容器 CPU 应用顶峰,容器的部署密度必须大大降低。 ...

August 11, 2021 · 2 min · jiezi

关于cpu:带你探索CPU调度的奥秘

摘要:本文将会从最根底的调度算法说起,一一剖析各种支流调度算法的原理,带大家一起摸索CPU调度的神秘。本文分享自华为云社区《摸索CPU的调度原理》,作者:元闰子。 前言软件工程师们总习惯把OS(Operating System,操作系统)当成是一个十分值得信赖的管家,咱们只管把程序托管到OS上运行,却很少深刻理解操作系统的运行原理。的确,OS作为一个通用的软件系统,在大多数的场景下都体现得足够的优良。但仍会有一些非凡的场景,须要咱们对OS进行各项调优,能力让业务零碎更高效地实现工作。这就要求咱们必须深刻理解OS的原理,不仅仅只会使唤这个管家,还能懂得如何让管家做得更好。 OS是一个十分宏大的软件系统,本文次要摸索其中的冰山一角:CPU的调度原理。 说起CPU的调度原理,很多人的第一反馈是基于工夫片的调度,也即每个过程都有占用CPU运行的工夫片,工夫片用完之后,就让出CPU给其余过程。至于OS是如何判断一个工夫片是否用完的、如何切换到另一个过程等等更深层的原理,理解的人仿佛并不多。 其实,基于工夫片的调度只是泛滥CPU的调度算法的一类,本文将会从最根底的调度算法说起,一一剖析各种支流调度算法的原理,带大家一起摸索CPU调度的神秘。 CPU的上下文切换在摸索CPU调度原理之前,咱们先理解一下CPU的上下文切换,它是CPU调度的根底。 现在的OS简直都反对"同时"运行远大于CPU数量的工作,OS会将CPU轮流调配给它们应用。这就要求OS必须晓得从哪里加载工作,以及加载后从哪里开始运行,而这些信息都保留在CPU的寄存器中,其中行将执行的下一条指令的地址被保留在程序计数器(PC)这一非凡寄存器上。咱们将寄存器的这些信息称为CPU的上下文,也叫硬件上下文。 OS在切换运行工作时,将上一工作的上下文保留下来,并将行将运行的工作的上下文加载到CPU寄存器上的这一动作,被称为CPU上下文切换。 CPU上下文属于过程上下文的一部分,咱们常说的过程上下文由如下两局部组成: 用户级上下文:蕴含过程的运行时堆栈、数据块、代码块等信息。零碎级上下文:蕴含过程标识信息、过程现场信息(CPU上下文)、过程管制信息等信息。这波及到两个问题:(1)上一工作的CPU上下文如何保留下来?(2)什么时候执行上下文切换? 问题1: 上一工作的CPU上下文如何保留下来? CPU上下文会被保留在过程的内核空间(kernel space)上。OS在给每个过程调配虚拟内存空间时,会调配一个内核空间,这部分内存只能由内核代码拜访。OS在切换CPU上下文前,会先将以后CPU的通用寄存器、PC等过程现场信息保留在过程的内核空间上,待下次切换时,再取出从新装载到CPU上,以复原工作的运行。 问题2: 什么时候执行上下文切换? OS要想进行工作上下文切换,必须占用CPU来执行切换逻辑。然而,用户程序运行的过程中,CPU曾经被用户程序所占用,也即OS在此刻并未处于运行状态,天然也无奈执行上下文切换。针对该问题,有两种解决策略,合作式策略与抢占式策略。 合作式策略依赖用户程序被动让出CPU,比方执行零碎调用(System Call)或者呈现除零等异样。但该策略并不靠谱,如果用户程序没有被动让出CPU,甚至是歹意死循环,那么该程序将会始终占用CPU,惟一的复原伎俩就是重启零碎了。 抢占式策略则依赖硬件的定时中断机制(Timer Interrupt),OS会在初始化时向硬件注册中断解决回调(Interrupt Handler)。当硬件产生中断时,硬件会将CPU的处理权交给来OS,OS就能够在中断回调上实现CPU上下文的切换。 调度的掂量指标对于一种CPU调度算法的好坏,个别都通过如下两个指标来进行掂量: 周转工夫(turnaround time),指从工作达到至工作实现之间的工夫,即T_{turnaround}=T_{completiong}-T_{arrival}Tturnaround=Tcompletiong−Tarrival响应工夫(response time),指从工作达到至工作首次被调度的工夫,即T_{response}=T_{firstrun}-T_{arrival}Tresponse=Tfirstrun−Tarrival两个指标从某种程度上是对抗的,要求高的均匀周转工夫,必然会升高均匀响应工夫。具体谋求哪种指标与工作类型无关,比方程序编译类的工作,要求周转工夫要小,尽可能快的实现编译;用户交互类的工作,则要求响应工夫要小,防止影响用户体验。 工作负载假如OS上的工作负载(也即各类工作运行的情况)总是变幻无穷的,为了更好的了解各类CPU调度算法原理,咱们先对工作负载进行来如下几种假如: 假如1:所有工作都运行时长都雷同。假如2:所有工作的开始工夫都是雷同的假如3:一旦工作开始,就会始终运行,直至工作实现。假如4:所有工作只应用CPU资源(比方不产生I/O操作)。假如5:事后晓得所有工作的运行时长。筹备工作曾经做好,上面咱们开始进入CPU调度算法的微妙世界。 FIFO:先进先出FIFO(First In First Out,先进先出)调度算法以原理简略,容易实现著称,它先调度首先达到的工作直至完结,而后再调度下一个工作,以此类推。如果有多个工作同时达到,则随机选一个。 在咱们假如的工作负载情况下,FIFO效率良好。比方有A、B、C三个工作满足上述所有负载假如,每个工作运行时长为10s,在t=0时刻达到,那么任务调度状况是这样的: 依据FIFO的调度原理,A、B、C别离在10、20、30时刻实现工作,均匀周转工夫为20s( \frac {10+20+30}{3}310+20+30),成果很好。 然而事实总是残暴的,如果假如1被突破,比方A的运行工夫变成100s,B和C的还是10s,那么调度状况是这样的: 依据FIFO的调度原理,因为A的运行工夫过长,B和C长时间得不到调度,导致均匀周转工夫好转为110( \frac {100+110+120}{3}3100+110+120)。 因而,FIFO调度策略在工作运行工夫差别较大的场景下,容易呈现工作饿死的问题! 针对这个问题,如果运行工夫较短的B和C先被调度,问题就能够解决了,这正是SJF调度算法的思维。 SJF:最短工作优先SJF(Shortest Job First,最短工作优先)从雷同达到工夫的多个工作中选取运行时长最短的一个工作进行调度,接着再调度第二短的工作,以此类推。 针对上一节的工作负载,应用SJF进行调度的状况如下,周转工夫变成了50s( \frac {10+20+120}{3}310+20+120),相比FIFO的110s,有了2倍多的晋升。 让咱们持续突破假如2,A在t=0时刻,B和C则在t=10时刻达到,那么调度状况会变成这样: 因为工作B和C比A后到,它们不得不始终期待A运行完结后才有机会调度,即便A须要长时间运行。周转工夫好转为103.33s(\frac {100+(110-10)+(120-10)}{3}3100+(110−10)+(120−10)),再次出现工作饿死的问题! STCF:最短时间实现优先为了解决SJF的工作饿死问题,咱们须要突破假如3,也即工作在运行过程中是容许被打断的。如果B和C在达到时就立刻被调度,问题就解决了。这属于抢占式调度,原理就是CPU上下文切换一节提到的,在中断定时器达到之后,OS实现工作A和B的上下文切换。 咱们在合作式调度的SJF算法的根底上,加上抢占式调度算法,就演变成了STCF算法(Shortest Time-to-Completion First,最短时间实现优先),调度原理是当运行时长较短的工作达到时,中断以后的工作,优先调度运行时长较短的工作。 应用STCF算法对该工作负载进行调度的状况如下,周转工夫优化为50s(\frac {120+(20-10)+(30-10)}{3}3120+(20−10)+(30−10)),再次解决了工作饿死问题! 到目前为止,咱们只关怀了周转工夫这一掂量指标,那么FIFO、SJF和STCF调度算法的响应工夫又是多长呢? 无妨假如A、B、C三个工作都在t=0时刻达到,运行时长都是5s,那么这三个算法的调度状况如下,均匀响应时长为5s(\frac {0+(5-0)+(10-0)}{3}30+(5−0)+(10−0)): 更蹩脚的是,随着工作运行时长的增长,均匀响应时长也随之增长,这对于交互类工作来说将会是灾难性的,重大影响用户体验。该问题的本源在于,当工作都同时达到且运行时长相同时,最初一个工作必须期待其余工作全副实现之后才开始调度。 为了优化响应工夫,咱们相熟的基于工夫片的调度呈现了。 RR:基于工夫片的轮询调度RR(Round Robin,轮训)算法给每个任务分配一个工夫片,当工作的工夫片用完之后,调度器会中断当前任务,切换到下一个工作,以此类推。 须要留神的是,工夫片的长度设置必须是中断定时器的整数倍,比方中断定时器时长为2ms,那么工作的工夫片能够设置为2ms、4ms、6ms … 否则即便工作的工夫片用完之后,定时中断没产生,OS也无奈切换工作。 当初,应用RR进行调度,给A、B、C调配一个1s的工夫片,那么调度状况如下,均匀响应时长为1s(\frac {0+(1-0)+(2-0)}{3}30+(1−0)+(2−0)): ...

July 29, 2021 · 1 min · jiezi

关于cpu:CPU基础知识

CPU是整个计算机的大脑,管制和解决内部输出的工作。CPU是由运算器、寄存器和控制器三大模块组成。 运算器运算器(Arithmatic unit ALU)用于做算术计算和逻辑计算。算术计算包含加减乘除四则运算,逻辑运算次要包含异、或、非、与、比拟等运算。运算器解决的数据通常为二进制数据,古代计算机中运算器一次解决的数据长度通常是64位,一次解决的二进制位长度越大,示意解决性能越高。寄存器寄存器是运算器的数据仓库,运算器须要的数据是存储在寄存器中的,当运算器计算实现后将后果再回写到寄存器中,最初返回到输出设备。寄存器分为 控制器包含程序计数器、时序发生器、指令译码器、寄存器。 程序计数器program counter 简称PC,用于记录CPU指令的地址。当CPU执行指令时,须要从PC中获取指令的地址,依据地址再获取到指令。当该条指令执行实现后,PC的地址会指向下一个指令地址位。 时序发生器用于发送时序脉冲,CPU根据不同的时序脉冲有节奏地进行工作,相似于CPU的节拍器。 指令译码器翻译指令 寄存器寄存器又分为多种不同类型数据寄存器数据寄存器(Data Register,DR)又称数据缓冲寄存器,其次要性能是作为CPU和主存、外设之间信息传输的中转站,用以补救CPU和主存、外设之间操作速度上的差别。数据寄存器用来临时寄存由主存储器读出的一条指令或一个数据字;反之,当向主存存入一条指令或一个数据字时,也将它们临时寄存在数据寄存器中。指令寄存器存储CPU的指令,PC中的指令地址就是指向的指令寄存器空间。当执行一条指令时,首先把该指令从主存读取到数据寄存器中,而后再传送至指令寄存器。主存地址寄存器主存地址寄存器(Address Register,AR),存储着数据的内存地址,CPU通过IO总线和内存替换数据。因为在主存和CPU之间存在操作速度上的差别,所以必须应用地址寄存器来临时保留主存的地址信息,直到主存的存取操作实现为止。当CPU和主存进行信息替换,即CPU向主存存入数据/指令或者从主存读出数据/指令时,都要应用地址寄存器和数据寄存器。累加寄存器累加寄存器通常简称累加器(Accumulator,AC),是一个通用寄存器。当运算器的算术逻辑单元ALU执行算术或逻辑运算时,为ALU提供一个工作区,能够为ALU临时保留一个操作数或运算后果。程序状态寄存器程序状态字(Program Status Word,PSW)用来表征以后运算的状态及程序的工作形式。程序状态字寄存器还用来保留中断和零碎工作状态等信息,以便CPU和零碎及时理解机器运行状态和程序运行状态。因而,程序状态字寄存器是一个保留各种状态条件标记的寄存器。 CPU高速缓存CPU在拜访内存时,首先从CPU高速缓存中查问数据,如果存在间接获取,否则拜访内存中的数据,并把数据放到CPU缓存中,最初返回给CPU应用。下图为CPU的缓存架构如图,CPU缓存分为一级缓存、二级缓存和三级缓存。越凑近CPU存取数据速度越快,其老本也会越高。每个CPU外围独自领有L1和L2缓存,同一颗物理CPU的多个外围共享同一个L3缓存。CPU在获取数据时,先从L1获取,如果没有再从L2、L3获取,直到从主存中获取数据。当L1中蕴含了所需的数据时,速度是最快的,反之每次都要到主从中获取数据时,就会很慢。 缓存行CPU和主存替换数据的最小单位是cache line(缓存行),每个缓存行的大小是64字节。思考到空间局部性,邻近的数据将来被拜访的可能性较大,因而当CPU装载主存数据到缓存时,会将邻近的64个字节一起载入到缓存中,这64个字节正好占了一个cache line。当一个缓存行的数据有变动时会清空该缓存行,并从主从中从新载入。多线程缓存共享问题多个线程同时操作cache line中的不同字节时,会呈现缓存伪共享状况。每个线程的操作都会导致缓存的生效,进而须要从主从读取,从新载入到CPU缓存中,这样导致缓存频繁的装载、生效,读取的数据理论都要从主存取得,大大降低了CPU的性能。缓存行对齐为了防止缓存行的伪共享问题,咱们能够将不同线程操作的数据分布到不同的缓存行中,互不烦扰。A线程批改A数据只须要对A数据缓存行生效,而不会影响到B线程的B数据。缓存行对齐有两种形式1、将共享变量数据左右填充有效的数据,保障总长为64个字节2、在JDK8中应用@Contended注解标注共享变量

July 13, 2021 · 1 min · jiezi

关于cpu:融云技术Native-CC-服务适配多指令集-CPU-漫谈

背景介绍 因为近些年,CPU 行业的摩尔定律生效了,很多厂商都纷纷从指令集架构层面寻找代替解决方案。在生产产品畛域,苹果推出了 ARM 指令集的 Apple Silicon M1,大获好评;在云服务行业,华为云和 Amazon 前些年就曾经在自研并上线了 ARM CPU 服务器,在老本和性能方面颇有建树。 而对于国产 CPU 行业而言,除了北公众志、海光、兆芯等少数几家手上领有 x86_64 指令集受权外,其余厂家根本都专一于非 x86_64 指令集。如:华为和飞腾在研发 ARM CPU,龙芯长年专一于 MIPS CPU;近些年衰亡的 RISC-V 也吸引了泛滥厂家的眼光。 对于各种非 x86_64 CPU,行业软件的移植和适配次要会波及嵌入式端、手机端、桌面端和服务器。嵌入式端思考到功耗,个别逻辑较为简单,代码移植和适配复杂程度不高。手机端个别都是 Android ARM,不波及太多适配问题。 桌面端分为三种状况: 如果利用基于浏览器即可满足所有性能,国产化零碎发型版本,个别内置了 Firefox 浏览器,利用对 Firefox 浏览器进行适配即可。如果利用是一个轻度的桌面利用,能够思考应用 Electron 的计划。Electron(原名为 Atom Shell)是 GitHub 开发的一个开源框架。它通过应用 Node.js(作为后端)和 Chromium 的渲染引擎(作为前端)实现跨平台的桌面 GUI 应用程序的开发。这种状况下,首先能够看下国产化零碎的软件源是否有对应的 Electron 依赖(个别都有);如果没有,须要进行编译。如果利用是一个重度的 Native 利用,则须要把代码在对应的指令集和零碎依赖上进行编译,工作量较大。 服务器,也分为三种状况: 如果应用的是面向虚拟机的语言,比方 Java 或基于 JVM 的各种语言(Kotlin、Scala 等),则服务不须要进行非凡的适配。个别国产化零碎的软件源中个别都会自带已实现好的 OpenJDK;如果没有,参见的指令集个别也都能找到对应的 OpenJDK 开源实现,能够自行装置。近些年呈现的一些对 C 库无强依赖的语言,如 Go 等。编译体系在设计之初就思考了多种指标零碎和指令集架构,只须要在编译时指定指标零碎和架构即可,如 GOOS=linux GOARCH=arm64 go build,如果应用了 CGO 还须要指定 C/C++ 的编译器。如果服务应用的是 C/C++ 等 Native 语言,且对系统 C 库有强依赖,则须要把代码在对应的指令集和零碎依赖上进行编译,工作量较大。 ...

June 17, 2021 · 2 min · jiezi

关于cpu:芯片圈也内卷英伟达推出-ARM-架构-CPU比英特尔-x86-快十倍

4 月 13 日凌晨,NVIDIA 创始人兼首席执行官黄仁勋又一次在自家厨房「举办」了一年一度的 GTC 2021 大会,会上黄教主围绕芯片、软件、服务、边缘计算、数据中心以及云,为粉丝们形容了NVIDIA对计算产业的将来愿景。 会上呈现了十分多让人惊喜的产品,特地是基于 Arm 架构寰球首款专为 TB 级减速计算而设计的 CPU NVIDIA Grace、全新 BlueField-3 DPU,以及业界首款 1000TOPS 算力的主动驾驶汽车 SoC-Atlan。 一、英伟达的第一款 CPU NVIDIA Grace NVIDIA Grace 是英伟达的第一款 CPU,它以美国海军少将、计算机编程先驱 Grace Hopper 的名字命名,不同于咱们日常常常应用的利用于电脑、手机等商业产品上的 CPU,Grace 的定位是一款高度专用型处理器,次要面向大型数据密集型 HPC(数据中心) 和 AI 利用。 NVIDIA 官网称基于 Grace 的零碎与 NVIDIA GPU 紧密结合,性能将比目前最先进的 NVIDIA DGX 零碎(在 x86 CPU 上运行)高出 10 倍,值得注意的是 NVIDIA DGX A100是当今世界最为先进的 AI 零碎。 Grace 在创新性上,能够总结为以下三点: 内置下一代 Arm Neoverse 内核,每个CPU能在 SPECrate2017_int_base 基准测试中单位工夫运行超过300个实例;采纳第四代 NVIDIA NVLink,从 CPU 到 GPU 连贯速度超过 900GB/s,相当于目前服务器 14 倍的带宽速度;从 CPU 到 CPU 的速度超过 600GB/s。领有最高的内存带宽,采纳的新内存 LPDDR5x 技术,带宽是 LPDDR4 的 2 倍,能源效率进步了 10 倍,能提供更多计算能力。联合 GPU 和 DPU,Grace 让 NVIDIA 领有了第三种根底计算技术,并具备从新构建数据中心以推动技术的能力,NVIDIA 无望扭转英特尔占据服务器处理器 90% 以上市场的格局,甚至有可能取代英特尔「老大」的位置。 ...

April 13, 2021 · 2 min · jiezi

关于cpu:一次CPU占用1600问题的定位过程-与-反思

通过一次略微大的改版后,零碎上线,上线后测试没发现问题,第二天反馈系统卡顿,下线。 查看零碎问题 ,优化接口速度 上线,上线后发现没问题,第二天仍旧呈现卡顿。此时察看CPU占用1600%.此时想到的时先回滚。没有保留现场。 测试环境测试,发现cpu闲暇时占用100% 找到问题 修复。然而能够确定,这里100%不是引起1600%的起因。 再次上线,人工实时监控cpu占用率。此时呈现了1600%的状况。此时占用cpu 1600%的过程对应的线程如下. 6619 6625 等是占用最高的过程Id. 对JVM栈信息进行打印 并输入到文件。 6619转成16进制后为19db 依据过程号在栈文件中 最初发现占用cpu高的全副为gc过程 ,此时能够断定。有局部代码逻辑内存占用过高。或者呈现内存透露。 寻找问题 此时曾经间断三次上线失败,没方法在从线上测试。那么想的是在灰度环境模拟这个景象 而后dump堆信息 这样必定能够找到起因。 第一天 转移很少一部其余零碎流量,以及很少一部分用户流量过来,没发现问题。 第二天 其余零碎申请的流量放弃不变,减少更多的用户流量,没有复现问题。 第三天 减少局部其余零碎申请的流量,没有复现问题。 第n天 减少其余零碎申请的流量 内存调整小,没有复现问题。 第n+1天 灰度环境服务与正式环境平分流量,持续减少用户量。没呈现问题。 此时外围流程代码批改过的局部 曾经查看了n遍 没发现问题。 那么 须要思考一下,为什么灰度环境没有问题。而线上有问题。他们的用户有什么不同? 此时发现灰度环境全是权限最低的用户,而管理员没有在灰度环境上工作,想到这里 问题曾经离假相很近了。能够说曾经定位到问题所在了,只须要验证一下本人的猜测。 其中有个性能,是查看本人所治理人的数据,这个性能因为不是外围性能 ,并且申请的量很小很小,起初并没有向这个方向思考。逻辑是 : 查找本人下一级别,如果有数据,在持续查找,恰好 数据库有一条异样的数据 ,他的下一级就是本人! 导致产生了死循环,导致内存里的数据越来越多。 并且 只有那一个异样用户才会引起这个问题! 又因为是IO密集的操作,所以 这个循环占用的cpu很低。在线程栈中并没有发现他。 解决问题 找到问题解决就是很容易的事件了,不再详细描述。 反思 第一次零碎呈现卡顿,正确的解决形式大略应该如下 发现cpu占用高,查看该过程对应的线程在执行什么操作 发现大量的线程远程桌面在执行gc操作,此时应该dump堆信息 应用jmap等工具查看哪些对象占用内存占用高 找到对应代码 解决问题 这种bug不应该存在,即便存在了呈现问题也不要太慌 应该疾速的保留能保留下来的信息。 大的改变上线前须要灰度公布,大量用户先应用。 完结

January 27, 2021 · 1 min · jiezi

关于cpu:电脑硬件基本常识

次要对电脑硬件包含cpu,显卡,主板 ,内存等DIY硬件进行一些简略通俗易懂的介绍,老手必看,高手飘过。 一、处理器CPU常识: ①CPU的分类 CPU品牌有两大阵营,别离是Intel(英特尔)和AMD,这两个行业老大简直垄断了CPU市场,大家拆开电脑看看,无非也是Intel和AMD的品牌(当然不排除极极少山寨的CPU)。而Intel的CPU又分为Pentium(奔流)、Celeron(赛扬)和Core(酷睿)。其性能由高到低也就是Core>Pentium>Celeron。AMD的CPU分为Semporn(闪龙)和Athlon(速龙),性能当然是Athlon优于Semporn的了。 Intel与AMD标记意识 ②CPU的主频意识 提CPU时,常常听到2.4GHZ、3.0GHZ等的CPU,这些到底代表什么?这些相似于2.4GHZ的东东其实就是CPU的主频,也就是主时钟频率,单位就是MHZ。这时用来掂量一款CPU性能十分要害的指标之一。主频计算还有条公式。主频=外频×倍频系数。 单击“我的电脑”→“属性”就能够查看CPU类型和主频大小 如下图: 我的电脑-属性查看cpu信息 ③CPU提到的FSB是啥玩意? FSB就是前端总线,简略来说,这个货色是CPU与外界替换数据的最次要通道。FSB的处理速度快慢也会影响到CPU的性能。 4.CPU提及的高速缓存指的又是什么呢?高速缓存指内置在CPU中进行高速数据交换的储存器。分一级缓存(L1Cache)、二级缓存(L2Cache)以及三级缓存(L3Cache)。 个别状况下缓存的大小为:三级缓存>二级缓存>一级缓存。缓存大小也是掂量CPU性能的重要指标。 ④常提及的 45nm规格的CPU又是什么货色? 相似于45nm这些呈现在CPU的字样其实就是CPU的制作工艺,其单位是微米,为秘制越小,制作工艺当然就越先进了,频率也越高、集成的晶体管就越多!当初的CPU制作工艺从微米到纳米,从90纳米---65纳米---45纳米---到当初的32纳米---未来的28纳米,再到将来的更低,工艺越小,产品做的越精,功耗低,体积越小。 ⑤CPU外围电压对CPU有什么影响? 一句话:更低的外围电压,更少的耗电和发热。 二:显卡常识: ①有人说GPU是显卡的灵魂,为何这样说? GPU是显卡的外围,负责大部分图形设计工作,间接决定了显卡的整体性能程度。说它是显卡灵魂,一点都不过分。当初酷睿i3等的CPU还集成了GPU,相当于cpu中集成了显卡。 ②显存是掂量显卡非常重要的指标,简略介绍一下显存对显卡性能施展很大影响。MHZ是显存的单位。显存也分为GDR、 GDR2和 GDR3,和当初的GDR5四种,未来还有更高的。显存速度单位是ns。显存位宽指显存在一个时钟周期内所能传递数据的位数,位数越大传输数据量越大。显存容量有共享内存和理论显存之分。共享显存是利用虚拟内存的容量,而虚拟内存则是应用硬盘的容量。理论显存性能大于共享显存的性能,这点很容易混同,也是JS忽悠咱们的中央。性能上目前 GDR5>GDR3>GDR2>GDR,目前市场上能看到的对数的GDR3与GDR5显卡,GDR3以下级别显卡均已淘汰。 ③显卡的外围频率是什么? 显卡的外围频率是指外围芯片的工作频率。显卡超频通常就是提供外围频率。 ④显卡接口类型分哪些? 显卡的接口类型分AGP和PCI-Express两种。PCI-Express的速度比AGP的速度快,AGP根本曾经退出历史舞台了。AGP接口的显卡目前曾经停产了,要买的渠道个别就只是二手买卖,而且性能上大大如前者。 ⑤独立显卡和集成显卡哪个好? 首先介绍下什么是独立显卡,与集成显卡,独立显卡就是独自购买的一块显卡,而集成显卡就是主板上集成了显卡,或者目前比拟新的cpu上集成显卡外围。个别游戏用户与大型软件电脑配置都选独立显卡,集成显卡因为受空间等流量交易限度,性能比拟差无奈满足支流游戏与大型利用需要,但能够满足个别影音娱乐与简略游戏或者办公需要,速度相对来说没独立显卡的快。 独立显卡与集成显卡在于后者须要共享零碎的内存作为显存,前者则独自配置显存。性能上,集成显卡无奈与独立显卡相比,前者贵。集成显卡和独立显卡根本就是两个品位! ⑥ 目前显卡的芯片品牌 目前电脑显卡品牌有很多,比方 影池,七彩虹,华硕等有很多,但选用的显卡显卡外围芯片都是NVidia]和ATI显卡芯片组,芯片决定显卡品位。 其中NVidia显卡 (寰球第一大显卡芯片研发和制造商)咱们喜爱称为N卡, ATI显卡 (寰球惟一能和NVidia显卡抗衡的显卡芯片制造商 )咱们简称为A卡。 三:内存常识: ①电脑弄个内存用来干什么的? 因为内存的速度比硬盘快,当CPU开始工作后,会将局部罕用的信息写入内存,须要应用时再从内存中读取,而不是从硬盘中读取。这样读取速度显著快去硬盘的读取速度,进步了效率,因而弄个内存是必要的! ②常常看到例如DDR2 800 以及 DDR3 1333 这些代表什么? DDR2指的是2代的内存,内存分为DDR(1代)、DDR2(2代)、DDR3(3代),当然性能比照,3代性能>2代的>1代的。至于相似于DDR2 800这里的800指的就是内存总线频率,内存总线频率决定主板前端总线频率,如DDR2 800内存,主板的前端总线也只能达到800MHZ的速度,DDR3 1333为内存总线频率1333MHZ。 ③内存的数据带宽都是指什么? 简略来说是指内存的数据 传输速度。有条公式是这样的,内存的数据带宽=总线频率×带宽位数÷8。举个例子,DDR800内存数据带宽=800(MHZ)×64(Bit)÷8=6.4(GB/s)。如果开双通道的话则乘以2,也就是12.8GB/s。目前应用的内存是DDR3 1333 1667等或更高频率。 四:主板常识①提到主板时不免要接触南 北桥芯片,这是干什么的?如何辨别? 北桥芯片次要性能是管制内存。通常状况,主板上离CPU最近的芯片就是北桥芯片了。还有个南桥芯片,很容易和北桥芯片混同啊!南桥芯片的性能是负责I/O总线之间的通信,如键盘控制器,当初支流的主板曾经不存在南桥芯片了。②COMS电池是什么回事? COMS电池为BIOS芯片供电,爱护其存在的信息。这个COMS电池是圆形的纽扣电池,在主板上,很容易辨别。其左右次要为电脑时钟和bios在断电的状况下供电,这也是为什么咱们电脑把电源插头拔里,下次开机电脑的工夫仍然的失常的起因。 BIOS(Basic Input/output System),中文全称根本输出/输入零碎,这是集成在主板上的一块Rom芯片。在开机时按del键看到蓝蓝的屏幕就是这个BIOS了(绝大部分为英文界面)。当须要U盘装零碎等都须要对bios进行设置才能够。 ③买主板有哪些品牌抉择好?还有那些一线品牌、二线品牌怎么辨别? ...

January 24, 2021 · 1 min · jiezi

关于cpu:CPU飙高系统性能问题如何排查

简介: 压测时或多或少都收到过CPU或者Load高的告警,如果是单机偶发性的,常常会认为是“宿主机抢占导致的”,那事实是否真是如此呢?是什么引起了这些指标的飙高?网络、磁盘还是高并发?有什么工具能够定位?TOP、PS还是vmstat?CPU高&Load高和CPU低&Load高,不同的表征又代表着什么? 一 背景常识LINUX过程状态LINUX 2.6当前的内核中,过程个别存在7种根底状态:D-不可中断睡眠、R-可执行、S-可中断睡眠、T-暂停态、t-跟踪态、X-死亡态、Z-僵尸态,这几种状态在PS命令中有对应解释。 D (TASK_UNINTERRUPTIBLE),不可中断睡眠态。顾名思义,位于这种状态的过程处于睡眠中,并且不容许被其余过程或中断(异步信号)打断。因而这种状态的过程,是无奈应用kill -9杀死的(kill也是一种信号),除非重启零碎(没错,就是这么头硬)。不过这种状态个别由I/O期待(比方磁盘I/O、网络I/O、外设I/O等)引起,呈现工夫十分短暂,大多很难被PS或者TOP命令捕捉(除非I/O HANG死)。SLEEP态过程不会占用任何CPU资源。R (TASK_RUNNING),可执行态。这种状态的过程都位于CPU的可执行队列中,正在运行或者正在期待运行,即不是在下班就是在下班的路上。S (TASK_INTERRUPTIBLE),可中断睡眠态。不同于D,这种状态的过程尽管也处于睡眠中,然而是容许被中断的。这种过程个别在期待某事件的产生(比方socket连贯、信号量等),而被挂起。一旦这些工夫实现,过程将被唤醒转为R态。如果不在高负载期间,零碎中大部分过程都处于S态。SLEEP态过程不会占用任何CPU资源。T&t (__TASK_STOPPED & __TASK_TRACED),暂停or跟踪态。这种两种状态的过程都处于运行进行的状态。不同之处是暂停态个别因为收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOUT四种信号被进行,而跟踪态是因为过程被另一个过程跟踪引起(比方gdb断点)。暂停态过程会开释所有占用资源。Z (EXIT_ZOMBIE), 僵尸态。这种状态的过程实际上曾经完结了,然而父过程还没有回收它的资源(比方过程的描述符、PID等)。僵尸态过程会开释除过程入口之外的所有资源。X (EXIT_DEAD), 死亡态。过程的真正完结态,这种状态个别在失常零碎中捕捉不到。Load Average & CPU使用率谈到零碎性能,Load和CPU使用率是最直观的两个指标,那么这两个指标是怎么被计算出来的呢?是否能相互等价呢? Load Average 不少人都认为,Load代表正在CPU上运行&期待运行的过程数,即 但Linux零碎中,这种形容并不齐全精确。 以下为Linux内核源码中Load Average计算方法,能够看进去,因而除了可执行态过程,不可中断睡眠态过程也会被一起纳入计算,即: 602staticunsignedlongcount_active_tasks(void)603 {604structtask_struct*p;605unsignedlongnr=0;606607read_lock(&tasklist_lock);608for_each_task(p) {609if ((p->state==TASK_RUNNING610 (p->state&TASK_UNINTERRUPTIBLE)))611nr+=FIXED_1;612 }613read_unlock(&tasklist_lock);614returnnr;615 }......625staticinlinevoidcalc_load(unsignedlongticks)626 {627unsignedlongactive_tasks; /* fixed-point */628staticintcount=LOAD_FREQ;629630count-=ticks;631if (count<0) {632count+=LOAD_FREQ;633active_tasks=count_active_tasks();634CALC_LOAD(avenrun[0], EXP_1, active_tasks);635CALC_LOAD(avenrun[1], EXP_5, active_tasks);636CALC_LOAD(avenrun[2], EXP_15, active_tasks);637 }638 }在前文 Linux过程状态 中有提到过,不可中断睡眠态的过程(TASK_UNINTERRUTED)个别都在进行I/O期待,比方磁盘、网络或者其余外设期待。由此咱们能够看出,Load Average在Linux中体现的是整体零碎负载,即CPU负载 + Disk负载 + 网络负载 + 其余外设负载,并不能齐全等同于CPU使用率(这种状况只呈现在Linux中,其余零碎比方Unix,Load还是只代表CPU负载)。 CPU使用率 CPU的工夫分片个别可分为4大类:用户过程运行工夫 - User Time, 零碎内核运行工夫 - System Time, 闲暇工夫 - Idle Time, 被抢占工夫 - Steal Time。除了Idle Time外,其余工夫CPU都处于工作运行状态。 ...

September 29, 2020 · 1 min · jiezi

关于cpu:CPU-缓存一致性协议-MESI

CPU 高速缓存(Cache Memory)CPU 为何要有高速缓存CPU 在摩尔定律的领导下以每 18 个月翻一番的速度在倒退,然而内存和硬盘的倒退速度远远不迭 CPU。这就造成了高性能能的内存和硬盘价格及其低廉。然而 CPU 的高度运算须要高速的数据。为了解决这个问题,CPU 厂商在 CPU 中内置了大量的高速缓存以解决 I\O 速度和 CPU 运算速度之间的不匹配问题。 在 CPU 拜访存储设备时,无论是存取数据抑或存取指令,都趋于汇集在一片间断的区域中,这就被称为局部性原理。 工夫局部性(Temporal Locality):如果一个信息项正在被拜访,那么在近期它很可能还会被再次拜访。比方循环、递归、办法的重复调用等。 空间局部性(Spatial Locality):如果一个存储器的地位被援用,那么未来他左近的地位也会被援用。比方程序执行的代码、间断创立的两个对象、数组等。 带有高速缓存的 CPU 执行计算的流程程序以及数据被加载到主内存指令和数据被加载到 CPU 的高速缓存CPU 执行指令,把后果写到高速缓存高速缓存中的数据写回主内存 目前风行的多级缓存构造因为 CPU 的运算速度超过了 1 级缓存的数据 I\O 能力,CPU 厂商又引入了多级的缓存构造。 多级缓存构造 多核 CPU 多级缓存一致性协定 MESI多核 CPU 的状况下有多个一级缓存,如何保障缓存外部数据的统一, 不让零碎数据凌乱。这里就引出了一个一致性的协定 MESI。 MESI 协定缓存状态MESI 是指 4 中状态的首字母。每个 Cache line 有 4 个状态,可用 2 个 bit 示意,它们别离是: 缓存行(Cache line): 缓存存储数据的单元。状态形容监听工作M 批改 (Modified)该 Cache line 无效,数据被批改了,和内存中的数据不统一,数据只存在于本 Cache 中。缓存行必须时刻监听所有试图读该缓存行绝对就主存的操作,这种操作必须在缓存将该缓存行写回主存并将状态变成 S(共享)状态之前被提早执行。E 独享、互斥 (Exclusive)该 Cache line 无效,数据和内存中的数据统一,数据只存在于本 Cache 中。缓存行也必须监听其它缓存读主存中该缓存行的操作,一旦有这种操作,该缓存行须要变成 S(共享)状态。S 共享 (Shared)该 Cache line 无效,数据和内存中的数据统一,数据存在于很多 Cache 中。缓存行也必须监听其它缓存使该缓存行有效或者独享该缓存行的申请,并将该缓存行变成有效(Invalid)。I 有效 (Invalid)该 Cache line 有效。无留神: 对于 M 和 E 状态而言总是准确的,他们在和该缓存行的真正状态是统一的,而 S 状态可能是非统一的。如果一个缓存将处于 S 状态的缓存行作废了,而另一个缓存实际上可能曾经独享了该缓存行,然而该缓存却不会将该缓存行升迁为 E 状态,这是因为其它缓存不会播送他们作废掉该缓存行的告诉,同样因为缓存并没有保留该缓存行的 copy 的数量,因而(即便有这种告诉)也没有方法确定本人是否曾经独享了该缓存行。从下面的意义看来 E 状态是一种投机性的优化:如果一个 CPU 想批改一个处于 S 状态的缓存行,总线事务须要将所有该缓存行的 copy 变成 invalid 状态,而批改 E 状态的缓存不须要应用总线事务。### MESI 状态转换了解该图的前置阐明: 1. 触发事件触发事件形容本地读取(Local read)本地 cache 读取本地 cache 数据本地写入(Local write)本地 cache 写入本地 cache 数据远端读取(Remote read)其余 cache 读取本地 cache 数据远端写入(Remote write)其余 cache 写入本地 cache 数据2.cache 分类: 前提:所有的 cache 独特缓存了主内存中的某一条数据。本地 cache: 指以后 cpu 的 cache。 触发 cache: 触发读写事件的 cache。 其余 cache: 指既除了以上两种之外的 cache。 留神:本地的事件触发 本地 cache 和触发 cache 为雷同。上图的切换解释:状态触发本地读取触发本地写入触发远端读取触发远端写入M 状态(批改)本地 cache:M触发 cache:M其余 cache:I本地 cache:M触发 cache:M其余 cache:I本地 cache:M→E→S触发 cache:I→S其余 cache:I→S同步主内存后批改为 E 独享, 同步触发、其余 cache 后本地、触发、其余 cache 批改为 S 共享本地 cache:M→E→S→I触发 cache:I→S→E→M其余 cache:I→S→I同步和读取一样, 同步实现后触发 cache 改为 M,本地、其余 cache 改为 IE 状态(独享)本地 cache:E触发 cache:E其余 cache:I本地 cache:E→M触发 cache:E→M其余 cache:I本地 cache 变更为 M, 其余 cache 状态该当是 I(有效)本地 cache:E→S触发 cache:I→S其余 cache:I→S当其余 cache 要读取该数据时,其余、触发、本地 cache 都被设置为 S(共享)本地 cache:E→S→I触发 cache:I→S→E→M其余 cache:I→S→I当触发 cache 批改本地 cache 独享数据时时,将本地、触发、其余 cache 批改为 S 共享. 而后触发 cache 批改为独享,其余、本地 cache 批改为 I(有效),触发 cache 再批改为 MS 状态 (共享)本地 cache:S触发 cache:S其余 cache:S本地 cache:S→E→M触发 cache:S→E→M其余 cache:S→I当本地 cache 批改时,将本地 cache 批改为 E, 其余 cache 批改为 I, 而后再将本地 cache 为 M 状态本地 cache:S触发 cache:S其余 cache:S本地 cache:S→I触发 cache:S→E→M其余 cache:S→I当触发 cache 要批改本地共享数据时,触发 cache 批改为 E(独享), 本地、其余 cache 批改为 I(有效), 触发 cache 再次批改为 M(批改)I 状态(有效)本地 cache:I→S 或者 I→E触发 cache:I→S 或者 I →E其余 cache:E、M、I→S、I本地、触发 cache 将从 I 有效批改为 S 共享或者 E 独享,其余 cache 将从 E、M、I 变为 S 或者 I本地 cache:I→S→E→M触发 cache:I→S→E→M其余 cache:M、E、S→S→I既然是本 cache 是 I,其余 cache 操作与它无关既然是本 cache 是 I,其余 cache 操作与它无关下图示意了,当一个 cache line 的调整的状态的时候,另外一个 cache line 须要调整的状态。MESIM×××√E×××√S××√√I√√√√举个栗子来说:假如 cache 1 中有一个变量 x = 0 的 cache line 处于 S 状态 (共享)。 那么其余领有 x 变量的 cache 2、cache 3 等 x 的 cache line 调整为 S 状态(共享)或者调整为 I 状态(有效)。### 多核缓存协同操作假如有三个 CPU A、B、C,对应三个缓存别离是 cache a、b、 c。在主内存中定义了 x 的援用值为 0。 #### 单核读取那么执行流程是: CPU A 收回了一条指令,从主内存中读取 x。 从主内存通过 bus 读取到缓存中(远端读取 Remote read), 这是该 Cache line 批改为 E 状态(独享). #### 双核读取那么执行流程是: CPU A 收回了一条指令,从主内存中读取 x。 CPU A 从主内存通过 bus 读取到 cache a 中并将该 cache line 设置为 E 状态。 CPU B 收回了一条指令,从主内存中读取 x。 CPU B 试图从主内存中读取 x 时,CPU A 检测到了地址抵触。这时 CPU A 对相干数据做出响应。此时 x 存储于 cache a 和 cache b 中,x 在 chche a 和 cache b 中都被设置为 S 状态 (共享)。 #### 批改数据那么执行流程是: CPU A 计算实现后发指令须要批改 x. CPU A 将 x 设置为 M 状态(批改)并告诉缓存了 x 的 CPU B, CPU B 将本地 cache b 中的 x 设置为 I 状态 (有效) CPU A 对 x 进行赋值。 #### 同步数据那么执行流程是:CPU B 收回了要读取 x 的指令。 CPU B 告诉 CPU A,CPU A 将批改后的数据同步到主内存时 cache a 批改为 E(独享) CPU A 同步 CPU B 的 x, 将 cache a 和同步后 cache b 中的 x 设置为 S 状态(共享)。 MESI 优化和他们引入的问题---------------缓存的一致性消息传递是要工夫的,这就使其切换时会产生提早。当一个缓存被切换状态时其余缓存收到音讯实现各自的切换并且收回回应音讯这么一长串的工夫中 CPU 都会期待所有缓存响应实现。可能呈现的阻塞都会导致各种各样的性能问题和稳定性问题。### CPU 切换状态阻塞解决 - 存储缓存(Store Bufferes)比方你须要批改本地缓存中的一条信息,那么你必须将 I(有效)状态告诉到其余领有该缓存数据的 CPU 缓存中,并且期待确认。期待确认的过程会阻塞处理器,这会升高处理器的性能。应为这个期待远远比一个指令的执行工夫长的多。#### Store Bufferes为了防止这种 CPU 运算能力的节约,Store Bufferes 被引入应用。处理器把它想要写入到主存的值写到缓存,而后持续去解决其余事件。当所有生效确认(Invalidate Acknowledge)都接管到时,数据才会最终被提交。 这么做有两个危险#### Store Bufferes 的危险第一、就是处理器会尝试从存储缓存(Store buffer)中读取值,但它还没有进行提交。这个的解决方案称为 Store Forwarding,它使得加载的时候,如果存储缓存中存在,则进行返回。 第二、保留什么时候会实现,这个并没有任何保障。`value = 3;void exeToCPUA(){ value = 10; isFinsh = true;}void exeToCPUB(){ if(isFinsh){ //value肯定等于10?! assert value == 10; }}`试想一下开始执行时,CPU A 保留着 finished 在 E(独享) 状态,而 value 并没有保留在它的缓存中。(例如,Invalid)。在这种状况下,value 会比 finished 更迟地摈弃存储缓存。齐全有可能 CPU B 读取 finished 的值为 true,而 value 的值不等于 10。即 isFinsh 的赋值在 value 赋值之前。这种在可辨认的行为中产生的变动称为重排序(reordings)。留神,这不意味着你的指令的地位被歹意(或者好心)地更改。它只是意味着其余的 CPU 会读到跟程序中写入的程序不一样的后果。~顺便提一下 NIO 的设计和 Store Bufferes 的设计是十分相像的。~### 硬件内存模型执行生效也不是一个简略的操作,它须要处理器去解决。另外,存储缓存(Store Buffers)并不是无穷大的,所以处理器有时须要期待生效确认的返回。这两个操作都会使得性能大幅升高。为了应酬这种状况,引入了生效队列。它们的约定如下:* 对于所有的收到的 Invalidate 申请,Invalidate Acknowlege 音讯必须立即发送* Invalidate 并不真正执行,而是被放在一个非凡的队列中,在不便的时候才会去执行。* 处理器不会发送任何音讯给所解决的缓存条目,直到它解决 Invalidate。即使是这样处理器未然不晓得什么时候优化是容许的,而什么时候并不容许。 罗唆处理器将这个工作丢给了写代码的人。这就是内存屏障(Memory Barriers)。> 写屏障 Store Memory Barrier(a.k.a. ST, SMB, smp_wmb) 是一条通知处理器在执行这之后的指令之前,利用所有曾经在存储缓存(store buffer)中的保留的指令。> 读屏障 Load Memory Barrier (a.k.a. LD, RMB, smp_rmb) 是一条通知处理器在执行任何的加载前,先利用所有曾经在生效队列中的生效操作的指令。`void executedOnCpu0() { value = 10; //在更新数据之前必须将所有存储缓存(store buffer)中的指令执行结束。 storeMemoryBarrier(); finished = true;}void executedOnCpu1() { while(!finished); //在读取之前将所有生效队列中对于该数据的指令执行结束。 loadMemoryBarrier(); assert value == 10;}`##### 援用文章http://www.importnew.com/1058...https://www.cnblogs.com/yanlo...

September 28, 2020 · 3 min · jiezi

页面CPU和内存占用监控可视化Chrome插件Graph-Process

写这个插件的原因是最近要对比一下页面的 cpu 和内存占用的性能,本来是想找看看有没有什么软件能够去可视化一下当前标签页的cpu和内存占用,但是发现却找不到这种软件,mac 上有个活动监视器,但是当你开很多标签页的话并不很好的监听当前标签页的 cpu 和内存占用,能看到谷歌浏览器的 rendered 进程,但是谷歌浏览器的 rendered 进程很多你并不知道是哪个,而且也没有可视化进行查看平均的 cpu 和内存占用,后来看到谷歌浏览器有个任务管理器可以查看当前标签页的 cpu 占用和内存占用,于是想到有没有人已经写了这种插件,但是遗憾的是并没有,后面仔细搜索了谷歌浏览器插件开发文档确定想要的功能能实现,于是这个插件就诞生了????效果图 主要功能对当前标签页点击插件图标,会开始对当前的标签页也就是页面的CPU和内存进行监控,并生成对应的变化折线图和平均值和表格,平均值如果超过一定范围会有颜色变化。 有人可能会问页面开启扩展后会对当前页面统计造成影响,其实是不会的,谷歌扩展是独立的进程,不会对当前的页面的 cpu 和内存占用造成影响。 安装插件注意,由于使用了谷歌浏览器的实验特性,因此插件需要运行在谷歌浏览器开发者版,由于使用了谷歌浏览器的实验特性,因此插件需要运行在谷歌浏览器开发者版,由于使用了谷歌浏览器的实验特性,因此插件需要运行在谷歌浏览器开发者版,重要的事情说三遍,可以在这里下载开发版谷歌浏览器开发者版插件地址:https://chrome.google.com/web...

October 15, 2019 · 1 min · jiezi

英特尔展示双-PCIe-插槽高度的-Element-模块化-PC

早在 2014 年的消费电子展(CES)上,雷蛇(Razer)首席执行官就展示过一款革命性的概念设计。那是一台带有主背板的 PC,允许用户以模块化的方式插入 CPU、GPU、电源、存储等零部件。时间快进到 2020 年,英特尔终于在伦敦举办的一场低调发布会上,带来了将这一设想变为现实的解决方案 —— Ed Barkhuysen 为大家介绍了该公司的“Element”模块化双 PCIe 插槽 PC 。 尽管只占用双 PCIe 插槽的高度,但 Element 仍然集成了板载的 CPU / DRAM / 存储等组件,且支持雷电、以太网、Wi-Fi、USB 等功能。 通过将多个模块化 PCIe 模块置入“主机”插槽,Element 能够与其它模块轻松配对,带来更强的 GPU 计算(以及其它加速器应用)体验。 显然,传说中的 Project Christina 项目确实存在,并将很快到来。在看到 Element 的第一眼,我们就想到了它与该公司的 NUC 部门有着怎样的联系,毕竟英特尔尚未公布它的正式名称。 舞台上展示的原型,采用了 BGA 封装、集成核显的至强(Xeon)处理器。整张模块化扩展卡仅占用单个 PCIe 3.0 x16 插槽,并通过 8-Pin 接口辅助供电。 板上还提供了两个 M.2 存储、2×SO-DIMM LPDDR4 内存插槽、覆盖整个组件的散热模块、Wi-Fi 附加控制器,以及 2×以太网、4×USB、1×HDMI、雷电 3 端口。 英特尔希望 Element 能够成就下一代计算解决方案,但它不见得很快能推向市场。展望未来,英特尔希望可以迁移到 PCIe 4.0 和 PCIe 5.0 / CXL 技术,以进一步提升这一概念的可行性。 ...

October 8, 2019 · 1 min · jiezi

MongoDB-sharding-集合不分片性能更高

最近云上用户用户遇到一个 sharding 集群性能问题的疑惑,比较有代表性,简单分享一下 测试配置mongos x 2、shard x 3测试1:集合不开启分片,批量 insert 导入数据,每个 batch 100 个文档测试2:集合开启分片,随机生成 shardKey,chunk 已提前 split 好,能确保写入均分到3个shard测试结果测试1:单个 shard cpu 跑满,insert qps 在 6w 左右测试2:3个 shard cpu 跑满,insert qps 在 7w 左右(平均每个分片2.4w左右)注:两个测试里,mongos 都不是瓶颈,能力足够 从测试结果看,每个shard都承担 1/3 的负载,的确达到横向扩张的目的,但为啥分片之后,单个shard的能力就下降了呢?如果是这样,sharding的扩展能力如何体现? 结果分析这里核心的问题在于 batch insert 在 mongos 和 mongod 上处理行为的差别 导入数据时,一次 insert 一条数据,和一次 insert 100 条数据,性能差距是很大的;首先减少了client、server 端之间的网络交互;同时 server 可以将 batch insert 放到一个事务里,降低开销;mongos 在收到 batch insert 时,因为一个 batch 里的数据需要根据 shardKey 分布到不同的shard,所以一个 batch 实际上需要被拆开的;这里 mongos 也做了优化,会尽量将连续的分布在一个shard上的文档做 batch 发到后端 shard。在集合不开启分片的情况,mongos 收到的 batch 肯定是转发给 primary shard,所以转发过去还是一整个 batch 操作; 而在集合开启分片的情况下,因为用户测试时,shardKey 是随机生成的,基本上整个 batch 被打散成单条操作,逐个往后端 shard 上发送,请求到后端 shard 基本已经完全没有合并了。所以在上述测试中,不分片的单个 shard 6w qps、与分片后每个 shard 2.4w qps,实际上就是请求是否 batch 执行的差别。 ...

July 11, 2019 · 1 min · jiezi

RPS和RFS网卡多队列性能调优实践

前言为了解决LVS ksoftirqd CPU使用率100%导致网卡软中断丢包,我和同事们一起搜索了大量的资料去分析问题,特别是感谢美团技术团队的分享帮助我们快速梳理优化思路,最后明确了如何重构RPS和RFS网卡多队列的优化脚本。个人认为这是一个大家可能普遍会遇到的问题,文章内的分析思路和解决方案未必是最优解,也欢迎各位分享自己的解决方法。 RPS和RFS网卡多队列性能调优实践更新历史2019年07月03日 - 初稿 阅读原文 - https://wsgzao.github.io/post... 扩展阅读 Redis 高负载下的中断优化 - https://tech.meituan.com/2018... 事故复盘我们遇到的问题属于计划外的incident,现象是某产品用户在线率突然降低,LVS Master同时收到CPU High Load告警,检查发现该节点出现网卡大量断开重连和丢包情况,应急切换到LVS Slave也出现上述问题,在排除掉流量异常和外部攻击后选择切换DNS到背后的Nginx Real Servers后服务逐步恢复。 复盘核心原因在于系统初始化时rps优化脚本没有成功执行,这个脚本起初是因为早期DBA团队遇到过CPU负载较高导致网卡异常,这个优化脚本也一直传承至今,却已经没有人知道为什么添加。现在大多数服务器没有执行成功而被大家一直所忽视显然也是post check没有做到位。在早期大家都停留在Bash Shell运维的阶段,没有专职的团队来管理确实容易失控,好在现在可以基于Ansible来做初始化和检查,运维的压力也减轻了一部分。 通过Google搜索相关知识的过程中,我们也发现在不少人都会遇到这样类似的问题。比如这篇文章提到lvs/irq lvs 的性能问题,软中断耗尽 CPU 单核后到达处理极限 瓶颈现象:当压力较大时,Lvs 服务器 CPU 的其中一个核使用率达到 100%(处理软中断)。 当 Lvs 服务器处理软中断的那个核使用率达到 100%,就到达系统处理上限。占用 CPU 的是进程 “ksoftirqd”,它未能使用到多核。做双网卡绑定,然后调试内核 SMP,中断主要是来自网卡的,不是 LVS 本身。需要把 2 个网卡来的 IRQ 均衡在双核 CPU 上面。和华为的工程师们在交换经验的时候对方分享了一个关于RSS和RPS关系图,之后的内容还会引用美团技术团队的分析 我们遇到的情况是缺少可用服务器资源选择把用户外部请求流量和Codis Cache Cluster内部流量临时混在了同一个LVS上,虽然看上去CPU和traffic的整体压力都不算高,但是CPU的处理压力可能恰好集中在了和外网Bond1网卡相同的Core上最后引起了ksoftirqd软中断,而内网Bond0网卡就没有监控到任何丢包。虽然我们也有正常开启irqbalance,但不清楚是不是因为受到cpupower performance和NUMA的影响最后也没能阻止事故的发生,最终的优化方案主要是手动开启RPS和RFS,大致步骤如下: set cpupower cpupower frequency-set -g performance,CPU 优化建议使用 cpupower 设置 CPU Performance 模式activate rps/rfs by script: rps.shdouble ring buffer size: ethtool -G p1p1 [rx|tx] 4096, check ethtool -g p1p1double NAPI poll budget: sysctl -w net.core.netdev_budget=600add zabbix monitor on net.if.in[eth0,errors,dropped,overruns]#!/bin/bash# chkconfig: 2345 90 60### BEGIN INIT INFO# Provides: rps# Required-Start: $local_fs $remote_fs $network $syslog# Required-Stop: $local_fs $remote_fs $network $syslog# Default-Start: 2 3 4 5# Default-Stop: 0 1 6# Short-Description: enable rps config for ubuntu# Description: enabele rps which is a kernel tweak for network performance### END INIT INFONAME=rpsDESC=rps# cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor# cpupower frequency-set -g performance# activate rps/rfs by script: https://gist.github.com/wsgzao/18828f69147635f3e38a14690a633daf# double ring buffer size: ethtool -G p1p1 [rx|tx] 4096, ethtool -g p1p1# double NAPI poll budget: sysctl -w net.core.netdev_budget=600rps() { net_interface=`ip link show | grep "state UP" | awk '{print $2}' | egrep -v '^docker|^veth' | tr ":\n" " "` for em in ${net_interface[@]} do rq_count=`ls /sys/class/net/$em/queues/rx-* -d | wc -l` rps_flow_cnt_value=`expr 32768 / $rq_count` for ((i=0; i< $rq_count; i++)) do echo $rps_flow_cnt_value > /sys/class/net/$em/queues/rx-$i/rps_flow_cnt done flag=0 while [ -f /sys/class/net/$em/queues/rx-$flag/rps_cpus ] do echo `cat /sys/class/net/$em/queues/rx-$flag/rps_cpus | sed 's/0/f/g' ` > /sys/class/net/$em/queues/rx-$flag/rps_cpus flag=$(($flag+1)) done done echo 32768 > /proc/sys/net/core/rps_sock_flow_entries sysctl -p}check_rps() { ni_list=`ip link show | grep "state UP" | awk '{print $2}' | egrep -v "^docker|^veth" | tr ":\n" " "` for n in $ni_list do rx_queues=`ls /sys/class/net/$n/queues/ | grep "rx-[0-9]"` for q in $rx_queues do rps_cpus=`cat /sys/class/net/$n/queues/$q/rps_cpus` rps_flow_cnt=`cat /sys/class/net/$n/queues/$q/rps_flow_cnt` echo "[$n]" $q "--> rps_cpus =" $rps_cpus ", rps_flow_cnt =" $rps_flow_cnt done done rps_sock_flow_entries=`cat /proc/sys/net/core/rps_sock_flow_entries` echo "rps_sock_flow_entries =" $rps_sock_flow_entries}case "$1" in start) echo -n "Starting $DESC: " rps check_rps ;; stop) echo -n "Stop is not supported. " ;; restart|reload|force-reload) echo -n "Restart is not supported. " ;; status) check_rps ;; *) echo "Usage: $0 [start|status]" ;;esacexit 0Scaling in the Linux Networking Stack了解RSS、RPS、RFS等基础知识很重要This document describes a set of complementary techniques in the Linuxnetworking stack to increase parallelism and improve performance formulti-processor systems. ...

July 5, 2019 · 29 min · jiezi

CPU-是怎样工作的

作者:Milap Neupane翻译:疯狂的技术宅 原文:https://www.freecodecamp.org/... 未经允许严禁转载 CPU,也被称为微处理器,是计算机的大脑。让我们通过深入了解计算机的核心,来帮助自己有效地编写计算机程序。 “工具通常比机器更简单,它一般用手就可以操作,而机器需要被动物或蒸汽动力启动。”—— Charles Babbage 计算机是由电力驱动的机器,但其灵活性和可编程性有助于实现工具的简单性。 CPU 是计算机的大脑。它执行提供给它的指令。它的主要工作是执行算术和逻辑运算并一起编排指令。在深入了解主要部分之前,我们先来看看CPU的主要组件以及它们的作用分别是什么: 处理器的两个主要组件控制单元 — CU(Control unit )算术和逻辑单元 — ALU(Arithmetic and logical unit)控制单元 — CU控制单元 CU 是 CPU 的一部分,用来帮助协调指令的执行,它告诉 CPU 该做什么。根据该指令,激活将 CPU 连接到计算机的其他不同部分的电路,其中包括 ALU。控制单元是 CPU 接收处理指令的第一个组件。 控制单元有两种类型: 硬连线控制单元。可微编程(微编程)控制单元。硬连线控制单元是硬件,需要更改硬件来修改它的工作方式;可编程控制单元可以通过编程来改变其行为。硬连线 CU 在处理指令时更快,而可编程 CU 则更灵活。 算术和逻辑单元 — ALU算术和逻辑单元 ALU 进行所有的算术和逻辑计算。 ALU 执行加法、减法等操作。 ALU 由执行这些操作的逻辑电路或逻辑门组成。 大多数逻辑门有两个输入端和一个输出端。 下图是半加器电路的一个例子,它接收两个输入并输出结果。A 和 B 是输入,S 是输出,C 是进位。 存储 — 寄存器和内存CPU 的主要工作是执行提供给它的指令。通常要处理这些指令,它需要数据。一些数据是中间数据,其中一些是输入,另一些是输出。这些数据以及指令存储在以下存储中: 寄存器寄存器是一小组可以存储数据的地方。寄存器是锁存器的组合。 锁存器也称为触发器,是逻辑门的组合,它能够存储 1 bit 信息。 ...

June 24, 2019 · 2 min · jiezi

CPU-的工作原理

本文资料来源网络收集的CPU 的工作原理 国外资料资料【当然许多码农会说软件工程师不必了解硬件的原理-不过有的伙伴肯定会有兴趣的^_^】有兴趣的可以点个赞表示支持!,软件层的服务依赖于硬件低层提供,越了解对自己越好!下列为本人整理的资料【原文字幕是英文--以下是截图】 由于Markdown不支持视频!所以需要看视频的同学可以点击过去观看 CPU工作原理 下面是CPU模型在线调试【通过编写汇编指令来模拟CPU的原理过程】 CPU模拟图【我缩小截图的,老写用JS做的CPU模型】 CPU模型在线测试 上面是的在线调试的,下面是EXCEL做的CPU模型【老外牛^_^】 可以看视频CPU模型EXCEL版本 CPU工作原理英文版本资料 中文版本【CPU工作原理PDF中文版本】可以联系本人获取【翻译的可能不是很完整,但能看^_^】 中文版本PDF截图 英文截图 CPU芯片设计流程视频 视频是高清视频【存于本人的服务器,宽带小大家可自己下载自己看,别线上看,我服务器会挂的^_^】视频是英文的,本人处理后翻译为了中文字幕^_^

June 23, 2019 · 1 min · jiezi

时延敏感业务低概率超时问题分析

前言作为阿里云底层提供的基础设施,内部的物理网络和许多网络产品在数据平面给客户的可操作性并不高,从一定程度上来说是个黑盒。当然,在传统的IDC环境,业务和物理网络之间也存在同样的隔阂。所以在遇到业务卡顿、延迟、不通等问题的时候,很容易怀疑到网络。因此如何抽丝拨茧,找到正确的方向对症下药才能够真正的解决问题。毕竟“真相只有一个”。 在进行问题排查和处理的时候,难度最高的场景就是极度偶发,复现频率极低的问题。尤其在网络排查的领域,通常为了性能和控制资源消耗,不会将每一个数据包的情况都一一记录下来,对于一次偶发的应用层记录的超时,网络层通常没有明确的对应此次应用层调用的包交互记录,因此排查起来非常困难。 在这次的案例中,我们通过一个客户端查询redis集群偶发超时的小案例,来说明一些诊断思路、排查手段,进而引出一些在网络方面提高业务稳定性的最佳实践。 问题环境这次的问题是一个交互性web应用中的一个子模块,主要进行redis查询,可以简单将其理解为视频弹幕网站中“查询弹幕”的小模块。这个模块的拓扑非常简单: 在上面的拓扑中,客户使用ECS构建了一个Redis集群,前面用Codis实现了一层Redis Proxy (为了通用性,后面均用Redis proxy来描述),并将这组Redis proxy挂载在一个SLB后,通过SLB的单一入口提供服务。 问题现象客户的主要问题是访问其自建Redis系统的客户端会不定期出现超时报错,尽管总体概率不高,但是报错概率高于其业务运行在原有机房的水平。超时报错主要有两种情况: 一般情况下超时数量与业务量呈正相关,非业务高峰期但是SLB、ECS的资源使用率均较低。会存在突发性的大量超时。诊断思路作为问题排查的第一步,首先要了解到问题本身所处的上下文和环境。在平时诊断问题收集信息的时候,为了有条理的、全面的收集信息,笔者将需要收集的信息分为两种类型:资源因素和环境因素。 资源因素:即发生问题的系统的拓扑。比如涉及的各种应用程序、主机、转发设备、链路资源等,并且要充分理解这些资源组建在拓扑中起到的作用。环境因素:即描述这个问题所需要的信息,比如报错日志,问题发生时间、频率,应用层设置的超时时间等等。了解资源因素和环境因素后,可以将问题的定义明确为:在哪些资源上发生了什么样的问题,然后根据该定义收集与问题相关的信息,并在解读和分析的时候通过数据排除所有的不可能,这样才能进行高效和准确的问题排查。 在本案例中,资源因素已经在上文的拓扑中阐述,问题所涉及的环境因素包括:客户设置的是50ms超时,在客户端的报错是read timeout(代表排除了tcp的三次握手超时),报错频率为非业务高峰期一个小时10个左右,业务高峰期1小时上百个。但是偶尔(一周内偶尔发生一次到两次)无论业务高峰还是非业务高峰都会有较多的,上百次的read timeout和connect timeout。客户已经排查过redis,其查询时间每次耗时不超过10ms,而redis proxy没有记录转发的日志。 排查方法因为所有可用的日志仅能定位到这个系统的两端(客户端、Redis),需要收集进一步的信息。面对这种超时类的问题,最直接、有效的办法就是进行抓包。而该问题发生的频率不高,整个系统流量也比较大,一直开着抓包很容易将磁盘撑满,因此需要使用循环抓包: tcpdump -i <接口|any> -C <每文件大小> -W <文件个数> -w <保存文件名> 抓包过滤条件该命令的意思是针对某个接口,按照过滤条件进行抓包,保存指定文件名前缀的文件下,最多占用每文件大小*文件个数 的磁盘空间并循环覆盖。开启循环抓包后,等待客户端报错再次出现,即可抓到现场的包交互过程。 抓包的结果文件可以使用wireshark打开,但是使用循环抓包抓到的数据包文件较大、数量较多,可以使用下面的小技巧进行快速的过滤: //在安装了wireshark的电脑上都会有capinfos和tshark两个命令,以笔者使用的macOS为例~$ capinfos -a -e *cap //使用capinfos查看抓包文件的其实时间和结束时间,选取包含报错时间+-超时时间的文件,其他文件就不需要了File name: colasoft_packets.capPacket size limit: inferred: 66 bytes - 1518 bytes (range)First packet time: 2019-06-12 09:00:00.005519934Last packet time: 2019-06-12 09:59:59.998942048File name: colasoft_packets1.capPacket size limit: inferred: 60 bytes - 1518 bytes (range)First packet time: 2019-06-12 09:00:00.003709451Last packet time: 2019-06-12 09:59:59.983532957//如果依然有较多文件,则可以使用tshark命令进行筛选。比如报错中提到Redis查询一个key超时,则可以用以下脚本找到这次查询请求具体在哪个文件中:~$ for f in ./*; do echo $f; tshark -r $f 'tcp.payload contains "keyname"'; done找到对应的请求之后,再用wireshark打开该文件,找到对应数据包,跟踪对应流来找到五元组和整个流的上下文交互。 ...

June 18, 2019 · 1 min · jiezi

使用符合-CKB-虚拟机当前系统架构的真实-CPU-指令集来构建自己的虚拟机

Nervos 底层公链 CKB 的虚拟机(CKB-VM)是基于 RISC-V 打造的区块链虚拟机。在前两期中,我们介绍了 CKB 虚拟机的设计理念,以及基于 RISC-V 指令集打造的选择逻辑。那么再往前推一步,我们为什么会选择基于真实 CPU 指令集来构建 CKB-VM 呢?在本篇文章中,CKB-VM 设计者将和我们继续讨论 CKB-VM 的设计灵感、设计以及基于真实 CPU 指令集来构建 CKB-VM 的额外优势。秘猿科技区块链小课堂第 24 期 灵感与设计 在设计 CKB-VM 之前,我们发现很多区块链项目并不是用真实的 CPU 指令集来构造自己的虚拟机。我们熟知的以太坊下一代虚拟机 EWASM、EOS 以及 Dfinity 等都选择了 WASM(WebAssembly,一种编码格式)来构造自己的虚拟机。我们也完全可以设计出一个具有高级语言特性的 VM,比如可以用于静态验证,或是可以直接支持高级数据结构,或是支持各种加密算法的 VM。 但是我们发现,虽然带有高级语言特性的虚拟机能够提供更多的便利,比如能够支持语法各异的编程语言,但同时也会出现其他一些问题:任何复杂的、带有高级语言功能的 VM,无论多么灵活,都不可避免的会在设计层面引入一些语义约束,出于性能的原因,不同的语言在底层几乎需要共享相同的语义(带有高级语言特性的虚拟机需要绑定密码学原语,若未来现有的原语被攻破,或者需要更换一套密码学原语时,需要通过分叉来实现)。这样一来, VM 自身的灵活性就会受到限制,这和 CKB 作为加密经济底层基础设施的愿景并不相符。 与此同时,一个带有高级语言功能的 VM 通常会包含某些高级的数据结构与算法,这样任何在 VM 中嵌入的高级数据结构与算法都可能只适合于某一类应用的开发,却不适用于其它应用程序的开发。并且,我们无法预设所有可能的使用方式,这些嵌入 VM 本身的数据结构或算法除了兼容性之外没有任何作用,随着时间的推移,甚至会成为负担。 另外我们还发现,所有的区块链项目都要在冯·诺伊曼 CPU 架构(x86,x86_64,ARM 等架构)下才能运行,并且所有高级的 VM 特性都必须映射到现代体系架构的 CPU 汇编指令。 举个例子,虽然 V8 引擎(由 Google 开发的开源 JavaScript 引擎,用于 Google Chrome 及 Chromium 中)看上去可以有无限量的内存,但是其内部实现依然需要依靠一个十分复杂的垃圾回收算法,才能在有限的内存空间中模拟出无限的内存空间。 ...

June 5, 2019 · 2 min · jiezi

何为真正的-FaaS-阿里舜天平台做了四大创新

阿里妹导读:数据中心和云计算的超高增速,AI、视频、基因测序等应用对于算力的无尽渴求和摩尔定律发展事实上已经停滞的现实,均给异构加速带来了巨大的应用潜力和商机。但 Faas 解决方案仍有较高的门槛,今天,我们一起了解 Faas 的难度在哪里?以及在阿里,我们如何做到真正的 Faas?一、 前言近几年,DC 和云计算领域风起云涌,发展地如火如荼。中外厂家纷纷发布 “DC First”、“All in Cloud”、“Cloud or Dead” 等战略,不管此前公司的主业为何,杀入 DC 和云计算领域的厂家如过江之鲫。但是,和任何 ICT 领域一样,在经过充分的搏杀和竞争之后,整个市场将基本稳定下来,由 TOP2-3的厂家把持,其他 players 加到一起分点残羹冷炙。Gartner 于4月24日发布报告,阿里云以19.6%的市场份额,雄踞整个亚太第一,AWS 和微软分居第二和第三。全球范围内,仍然维持了AWS、微软和阿里云分别为冠、亚、季军的格局。 阿里云 FPGA as a Service(以下简称FaaS)舜天平台正是 FPGA 异构加速领域的领导者和开拓者,也是 FPGA 异构加速领域良好生态的倡导者和建设者。依托阿里云百万企业付费客户以及阿里云强大的飞天操作系统,FaaS 舜天平台对内而言,已经成为阿里集团 FPGA 加速业务的基础设施;对外而言,则将大幅降低 FPGA 的开发和使用门槛,致力于为客户提供最高性价比的算力和打造健康的 FPGA 加速生态。 二、 传统的 FPGA 应用与 FaaS 的区别FPGA 由于其强大的灵活性,自诞生以来,在数以千计的垂直市场中都获得了大量的应用。但是,这种应用都谈不上“云”,也谈不上“service”。我们知道,传统 IT 基础设施是没有弹性的,因此很容易出现要么业务高峰时无法支撑乃至整个 IT 系统崩溃;要么陷入业务低谷时,大量 IT 资源闲置,造成成本高企。因此,“云”和“非云”的一个最大区别就在于是否支持资源的“弹性”伸缩:需要时按需获取,不需要时随时释放。而要做到“弹性”,那么一定要通过虚拟化来支持。如果做不到“弹性”和“虚拟化”,就称不上 FaaS,究其本质和传统上对 FPGA 的使用没有任何区别。 如果单纯从 FPGA 的设计和使用角度看,即使有门槛的存在,设计一颗 glue logic的 FPGA 或者跑一点简单算法,实现一点简单控制,难度是相当有限的。但是,不能说具备了这些能力之后,就可以对外宣称可以提供 FaaS 的能力了。 首先,使用 FPGA 实现复杂的算法的门槛是非常高的(比如用 FPGA 实现 H.265 编码);其次,高效使用 FPGA 实现复杂的算法的门槛是非常非常高的(还拿 H.265编码举例,设计得不好的话,很大容量的一颗 FPGA 也许只能支持一路 1080p/30 帧的 H.265 视频,好的设计也许就能支持4路);最后,把 FPGA 的加速能力通过“云”来输出给客户的门槛是非常非常非常高的。所以,FaaS 的核心之一是让 FPGA 的算力“x86化”,即云上购买和使用 FPGA 的算力和云上购买和使用 CPU 的算力一样简单;核心之二,是让 FPGA 的算力“服务化”,即不需要客户做二次开发和适配,通过简单的类 URL 调用即可使用。 ...

June 3, 2019 · 3 min · jiezi

开源性能可视化工具FlameScope模式识别

文章翻译原文链接 FlameScope是一个新的开源性能可视化工具,它使用次秒级偏移热图和火焰图来分析周期活动、方差、扰动。我们在Netflix TechBlog上面,发表了技术文章Netflix FlameScope,以及工具的源代码。火焰图很好理解,次秒级偏移热图理解起来要困难些(我最近发明的它)。FlameScope可以该帮助你理解后者。 总而言之,次秒级偏移热图是这样的:x轴是一整秒,y轴是这一秒里的几分之一秒。这每个几分之一秒都被称作一个桶(或者说盒),表示这几分之一秒里,事件数量的聚合。盒子颜色深度表示发生的次数,颜色越深表示次数越多。 下图一个真实的CPU上的次秒级偏移热图样本: 这张图中能分析出什么信息来呢?为了能把各种不同模式区分开来展示,我在这篇文章里先画了一些人工合成的样本。实际使用FlameScope工具时,可以选择你的各个模式,还能生成火焰图,显示对应的代码路径(这里我不展示火焰图)。 周期活动1 . 一个线程,每秒一次 线程在每秒钟内的同样的偏移里醒来,做几毫秒的工作,然后回到睡眠。 2 . 一个线程,两次每秒 每500ms唤醒一次。既可能是两个线程,也可能是一个线程500ms 唤醒一次。 3 . 两个线程 看起来像两个线程均1s唤醒一次 4 . 一个忙等待线程,每秒一次 这个线程做约20ms的工作,然后睡1s。这是一个常见的模式,导致每秒钟唤醒抵消匍匐前进。 5 . 一个忙等待线程,两次每秒 每500ms唤醒一次。有可能是单线程程序,每秒唤醒两次。 6 . 一个计算较密集的忙等线程 斜率高,每秒做更多的工作,大约是80毫秒。 7 . 一个计算较不密集的忙等线程 斜率低,每秒做的工作较少,可能只有几毫秒。 8 . 一个忙等待线程,每5秒钟唤醒一次 现在5秒唤醒一次。 我们可以根据夹角和唤醒的时间间隔,计算每个唤醒的CPU繁忙时间:busy_time = (1000 ms / (热图行数 时间长度) tan(夹角)例如45°夹角的线:busy_time = (1000 ms / (50 1)) tan(45) = 20ms 方差9 . cpu利用率100% ...

May 29, 2019 · 1 min · jiezi

5分钟了解阿里时序时空数据库

简介时序时空数据库(Time Series & Spatial Temporal Database,简称 TSDB)是一种高性能、低成本、稳定可靠的在线时序时空数据库服务,提供高效读写、高压缩比存储、时序数据插值及聚合计算等服务,广泛应用于物联网(IoT)设备监控系统、企业能源管理系统(EMS)、生产安全监控系统和电力检测系统等行业场景;除此以外,还提供时空场景的查询和分析的能力。 三个数据库时序时空数据库文档最近经过几次大的变动,有点乱,看的时候注意一下。 时序数据库TSDB版 经过阿里集团大规模验证的时序数据库,支持分布式集群架构水平扩展,支持千万物联网设备接入,基于自研压缩算法,具备高效压缩比。 - 针对时序数据优化,包括存储模型,多值数据模型,时序数据压缩、聚合、采样,高效压缩算法,列存,边缘一体化;- 具备高性能,内存优先数据处理,分布式MPP SQL并行计算,动态schema,实时流式数据计算引擎,海量时间线自适应索引;- 高可扩展,数据动态分区,水平扩展,动态弹性扩容,动态升降配规格;高可靠性,自动集群控制,线程级读写分离,多层数据备份,分级存储;- 瞄准的是大规模指标数据,事件数据场景协议兼容OpenTSDB,但后面内核实现是阿里自研的。但还是完全可以把它当作OpenTSDB的阿里云版,参见 相比OpenTSDB优势 InfluxDB® 不仅仅是一个数据库,更是一个监控系统,围绕采集,可视化,分析服务,事件和指标存储和计算系统;走的是tick生态,瞄准指标,事件,trace,日志,实时分析场景。 InfluxDB®刚上线不久,现在还处在公测阶段。写入速度经测试,每次500条数据,每秒可以执行26次左右,平均速度达到1万/s,增加每次写入数据条数应该还能提高速度。另外,请求地址是外网,如果使用vpc网络速度应该还会加快不少。 注意:InfluxDB在阿里云上有时间线限制(数据库级别最高1万),时间线的定义参见后面简介。 时空数据库 时空数据库能够存储、管理包括时间序列以及空间地理位置相关的数据。时空数据是一种高维数据,具有时空数据模型、时空索引和时空算子,完全兼容SQL及SQL/MM标准,支持时空数据同业务数据一体化存储、无缝衔接,易于集成使用。 时空数据库主要是空间相关的场景,比如热力图,店铺选址等等。 时序数据库简介(主要是InfluxDB)时序数据库英文全称为 Time Series Database,提供高效存取时序数据和统计分析功能的数据管理系统。主要的时序数据库包括OpenTSDB、Druid、InfluxDB以及Beringei这四个。本人主要了解一点OpenTSDB和InfluxDB,不过时序数据库有很多共性。 基本名词 measurement: tag,field和time列的容器对InfluxDB: measurement在概念上类似于传统DB的table(表格) 从原理上讲更像SQL中表的概念,这和其他很多时序数据库有些不同对其他时序DB: Measurement与Metric等同field(数值列): TSDB For InfluxDB®中不能没有field。注意:field是没有索引的在某种程度上,可以把field理解为k/v表的valuetag(维度列): tag不是必须要有的字段tag是被索引的,这意味着以tag作为过滤条件的查询会更快在某种程度上,可以把field理解为k/v表的keytimestamp(时间戳): 默认使用服务器的本地时间戳时间戳是UNIX时间戳,单位:纳秒最小的有效时间戳是-9223372036854775806或1677-09-21T00:12:43.145224194Z最大的有效时间戳是9223372036854775806或2262-04-11T23:47:16.854775806Zpoint(数据点): 由时间线(series)中包含的field组成。每个数据点由它的时间线和时间戳(timestamp)唯一标识您不能在同一时间线存储多个有相同时间戳的数据点Series(时间线) Series是InfluxDB中最重要的概念,时序数据的时间线就是:一个数据源采集的一个指标随着时间的流逝而源源不断地吐出数据这样形成的一条数据线称之为时间线。 下图中有两个数据源,每个数据源会采集两种指标: Series由Measurement和Tags组合而成,Tags组合用来唯一标识Measurement就是说:1\. Measurement不同,就是不同的时间线2\. Measurement相同,Tags不同也是不同的时间线retention policy(保留策略,简称RP) 一个保留策略描述了: 1.InfluxDB保存数据的时间(DURATION) 2.以及存储在集群中数据的副本数量(REPLICATION) 3.指定ShardGroup Duration注:复本系数(replication factors)不适用于单节点实例。autogen:无限的存储时间并且复制系数设为1RP创建语句如下: CREATE RETENTION POLICY ON <retention_policy_name> ON <database_name>DURATION <duration> REPLICATION <n> [SHARD DURATION <duration> ] [DEFAULT]实例:CREATE RETENTION POLICY "one_day_only" ON "water_database"DURATION 1d REPLICATION 1 SHARD DURATION 1h DEFAULT写入时指定rp进行写入: ...

May 27, 2019 · 2 min · jiezi

Linux-硬件信息获取

在 linux 上可以通过 dmidecode 或是 lshw 来获取硬件信息,能够方便的查看系统配置。但它们的输出信息过多,解析起来有些麻烦,另外 lshw 对 usb 接口的网卡支持不好,显示的信息不够,所以在此整理下通过读文件或是一些简单命令来获取硬件信息的方法。 DMI一般情况下内核默认加载了 dmi sysfs ,路径是 /sys/class/dmi 。里面包含了 bios , board , product 等信息。 Bios 通过命令 ls -l /sys/class/dmi/id/bios_* 可以看到支持的 bios 字段,如下: $ ls -l /sys/class/dmi/id/bios_*-r--r--r-- 1 root root 4.0K 5月 8 17:18 /sys/class/dmi/id/bios_date-r--r--r-- 1 root root 4.0K 5月 8 17:18 /sys/class/dmi/id/bios_vendor-r--r--r-- 1 root root 4.0K 5月 8 17:18 /sys/class/dmi/id/bios_version直接读文件即可获取对应值。 Board 通过命令 ls -l /sys/class/dmi/id/board_* 可以看到支持的 board 字段,如下: ...

May 9, 2019 · 6 min · jiezi

深入浅出计算机组成原理05计算机指令让我们试试用纸带编程分类计算机指令示例

May 5, 2019 · 0 min · jiezi

Node.js 应用故障排查手册 —— 类死循环导致进程阻塞

楔子在实践篇一中我们看到了两个表象都是和 CPU 相关的生产问题,它们基本也是我们在线上可能遇到的这一类问题的典型案例,而实际上这两个案例也存在一个共同点:我们可以通过 Node.js 性能平台 导出进程对应的 CPU Profile 信息来进行分析定位问题,但是实际在线上的一些极端情况下,我们遇到的故障是没有办法通过轻量的 V8 引擎暴露的 CPU Profile 接口(仅部分定制的 AliNode runtime 版本支持,详见下文)来获取足够的进程状态信息进行分析的,此时我们又回到了束手无策的状态。本章节将从一个生产环境下 Node.js 应用出现进程级别阻塞导致的不再提供服务的问题场景来给大家展示下如何处理这类相对极端的应用故障。本书首发在 Github,仓库地址:https://github.com/aliyun-node/Node.js-Troubleshooting-Guide,云栖社区会同步更新。最小化复现代码这个例子稍微有些特殊,我们首先给出生产案例的最小化复现代码,有兴趣的同学可以亲自运行一番,这样结合下文的此类问题的排查过程,能更加清晰的看到我们面对这样的问题时的排查思路,问题最小代码如下,基于 Egg.js :‘use strict’;const Controller = require(’egg’).Controller;class RegexpController extends Controller { async long() { const { ctx } = this; // str 模拟用户输入的问题字符串 let str = ‘<br/> ’ + ’ 早餐后自由活动,于指定时间集合自行办理退房手续。’; str += ‘<br/> <br/>’ + ’ <br/> ’ + ’ <br/>’; str += ’ <br/>’ + ’ ’ + ’ ’ + ’ <br/>’; str += ’ <br/> <br/>’; str += ’ ’ + ’ ’ + ’ 根据船班时间,自行前往暹粒机场,返回中国。<br/>’; str += ‘如需送机服务,需增加280/每单。<br/>’; const r = str.replace(/(^(\s*?<br[\s/]?>*?)+|(\s?<br[\s/]?>\s?)+?$)/igm, ‘’); ctx.body = r; }}module.exports = RegexpController;问题应用状态其实这个例子对应的问题场景可能很多 Node.js 开发者都遇到过,它非常有意思,我们首先来看下出现这类故障时我们的 Node.js 应用的状态。当我们收到在平台配置的 CPU 告警信息后,登录性能平台进入对应的告警应用找到出问题的 CPU 非常高的进程:然后点击 数据趋势 按钮查看此进程当前的状态信息:可以看到进程的 CPU 使用率曲线一直处于近乎 100% 的状态,此时进程不再响应其余的请求,而且我们通过跳板机进入生产环境又可以看到进程其实是存活的,并没有挂掉,此时基本上可以判断:此 Node.js 进程因为在执行某个同步函数处于阻塞状态,且一直卡在此同步函数的执行上。Node.js 的设计运行模式就是单主线程,并发靠的是底层实现的一整套异步 I/O 和事件循环的调度。简单的说,具体到事件循环中的某一次,如果我们在执行需要很长时间的同步函数(比如需要循环执行很久才能跳出的 while 循环),那么整个事件循环都会阻塞在这里等待其结束后才能进入下一次,这就是不推荐大家在非初始化的逻辑中使用诸如 fs.readFileSync 等同步方法的原因。排查方法这样的问题其实非常难以排查,原因在于我们没办法知道什么样的用户输入造成了这样的阻塞,所以本地几乎无法复现问题。幸运的是,性能平台目前有不止一种解决办法处理这种类死循环的问题,我们来详细看下。I. CPU Profile这个分析方法可以说是我们的老朋友了,因为类死循环的问题本质上也是 CPU 高的问题,因此我们只要对问题进程抓取 CPU Profile,就能看到当前卡在哪个函数了。需要注意的是,进程假死状态下是无法直接使用 V8 引擎提供的抓取 CPU Profile 文件的接口,因此工具篇章节的 正确打开 Chrome devtools 一节中提到的 v8-profiler 这样的第三方模块是无法正常工作的。不过定制过的 AliNode runtime 采用了一定的方法规避了这个问题,然而遗憾的是依旧并不是所有的 AliNode runtime 版本都支持在类死循环状态下抓取 CPU Profile,这里实际上对大家使用的 Runtime 版本有要求:AliNode V3 版本需要 >= v3.11.4AliNode V4 版本需要 >= v4.2.1AliNode V1 和 V2 版本不支持如果你的线上 AliNode runtime 版本恰好符合需求,那么可以按照前面 Node.js 性能平台使用指南 提到的那样,对问题进程抓取 3 分钟的 CPU Profile,并且使用 AliNode 定制的火焰图分析:这里可以看到,抓取到的问题进程 3 分钟的 CPU 全部耗费在 long 函数里面的 replace 方法上,这和我们提供的最小化复现代码一致,因此可以判断 long 函数内的正则存在问题进行修复。II. 诊断报告诊断报告也是 AliNode 定制的一项导出更多更详细的 Node.js 进程当前状态的能力,导出的信息也包含当前的 JavaScript 代码执行栈以及一些其它进程与系统信息。它与 CPU Profile 的区别主要在两个地方:诊断报告主要针对此刻进程状态的导出,CPU Profile 则是一段时间内的 JavaScript 代码执行状态诊断报告除了此刻 JavaScript 调用栈信息,还包含了 Native C/C++ 栈信息、Libuv 句柄和部分操作系统信息当我们的进程处于假死状态时,显然不管是一段时间内还是此时此刻的 JavaScript 执行状况,必然都是卡在我们代码中的某个函数上,因此我们可以使用诊断报告来处理这样的问题,当然诊断报告功能同样也对 AliNode runtime 版本有所要求:AliNode V2 版本需要 >= v2.5.2AliNode V3 版本需要 >= v3.11.8AliNode V4 版本需要 >= v4.3.0AliNode V1 版本不支持且要求:Agenthub/Egg-alinode 依赖的 Commandx 版本 >= v1.5.3如果你使用的 AliNode runtime 版本符合要求,即可进入平台应用对应的实例信息页面,选中问题进程:然后点击 诊断报告 即可生成此刻问题进程的状态信息报告:诊断报告虽然包含了很多的进程和系统信息,但是其本身是一个相对轻量的操作,故而很快就会结束,此时继续点击 转储 按钮将生成的诊断报告上传至云端以供在线分析展示:继续点击 分析 按钮查看 AliNode 定制的分析功能,展示结果如下:结果页面上面的概览信息比较简单,我们来看下 JavaScript 栈 页面的内容,这里显然也告诉我们当前的 JS 函数卡在 long 方法里面,并且比 CPU Profile 更加详细的是还带上了具体阻塞在 long 方法的哪一行,对比我们提供给大家的最小复现代码其实就是执行 str.replace 这一行,也就是问题的正则匹配操作所在的地方。III. 核心转储分析其实很多朋友看到这里会有疑惑:既然 CPU Profile 分析和诊断报告已经能够找到问题所在了,为什么我们还要继续介绍相对比较重的核心转储分析功能呢?其实道理也很简单,不管是类死循环状态下的 CPU Profile 抓取还是诊断报告功能的使用,都对问题进程的 AliNode runtime 版本有所要求,而且更重要的是,这两种方法我们都只能获取到问题正则的代码位置,但是我们无法知道什么样的用户输入在执行这样的正则时会触发进程阻塞的问题,这会给我们分析和给出针对性的处理造成困扰。因此,这里最后给大家介绍对 AliNode runtime 版本没有任何要求,且能拿到更精准信息的核心转储分析功能。首先按照预备章节的核心转储一节中提到的 手动生成 Core dump 文件的方法,我们对问题进程进行 sudo gcore <pid> 的方式获取到核心转储文件,然后在平台的详情页面,将鼠标移动到左边 Tab 栏目的 文件 按钮上,可以看到 Coredump 文件 的按钮:点击后可以进入 Core dump 文件列表页,然后点击上方的 上传 按钮进行核心转储文件的上传操作:这里需要注意的是,请将 Core dump 文件以 .core 结尾重命名,而对应的 Node 可执行文件以 .node 结尾重命名,推荐的命名方式为 <os info>-<alinode/node>-<version>.node,方便以后回顾,比如 centos7-alinode-v4.7.2.node 这种。最后 Core dump 文件和 Node 可执行文件之间必须是 一一对应 的关系。这里一一对应指的是:这份 Core dump 文件必须是由这个 Node 可执行文件启动的进程生成的,如果这两者没有一一对应,分析结果往往是无效信息。因为 Core dump 文件一般来说都比较大,所以上传会比较慢,耐心等待至上传完毕后,我们就可以使用 AliNode 定制的核心转储文件分析功能进行分析了,点击 分析 按钮即可:此时我们在新打开的分析结果页面可以看到如下的分析结果展示信息:这个页面的各项含义在工具篇的 Node.js 性能平台使用指南的 最佳实践——核心转储分析 一节已经解释过,这里不再赘述,这里直接展开 JavaScript 栈信息:这里可以看到得到的结论和前面的 CPU Profile 分析以及诊断报告分析一致,都能定位到提供的最小复现代码中的 long 方法中的异常正则匹配,但是核心转储文件分析比前面两者多了导致当前 Node.js 进程产生问题的异常字符串: "<br/> 早餐后自由活动,于指定时间集合自行办理退房手续。<br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> 根据船班时间,自行前往暹粒机场,返回中国。<br/>如需送机服务,需增加280/每单。<br/>" ,有了这个触发正则执行异常的问题字符串,我们无论是构造本地复现样例还是进一步分析都有了重要的信息依靠。分析问题上一节中我们采用了 Node.js 性能平台提供的三种不同的方式分析定位到了线上应用处于假死状态的原因,这里来简单的解释下为什么字符串的正则匹配会造成类死循环的状态,它实际上异常的用户输入触发了 正则表达式灾难性的回溯,会导致执行时间要耗费几年甚至几十年,显然不管是那种情况,单主工作线程的模型会导致我们的 Node.js 应用处于假死状态,即进程依旧存活,但是却不再处理新的请求。关于正则回溯的原因有兴趣的同学可以参见 小心别落入正则回溯陷阱 一文。结尾其实这类正则回溯引发的进程级别阻塞问题,本质上都是由于不可控的用户输入引发的,而 Node.js 应用又往往作为 Web 应用直接面向一线客户,无时不刻地处理千奇百怪的用户请求,因此更容易触发这样的问题。相似的问题其实还有一些代码逻辑中诸如 while 循环的跳出条件在一些情况下失效,导致 Node.js 应用阻塞在循环中。之前我们就算知道是进程阻塞也难以方便的定位到具体的问题代码以及产生问题的输入,现在借助于 Node.js 性能平台 提供的核心转储分析能力,相信大家可以比较容易地来解决这样的问题。本文作者:奕钧阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 15, 2019 · 2 min · jiezi

不可错过的CMS学习笔记

引子带着问题去学习一个东西,才会有目标感,我先把一直以来自己对CMS的一些疑惑罗列了下,希望这篇学习笔记能解决掉这些疑惑,希望也能对你有所帮助。CMS出现的初衷、背景和目的?CMS的适用场景?CMS的trade-off是什么?优势、劣势和代价CMS会回收哪个区域的对象?CMS的GC Roots包括那些对象?CMS的过程?CMS和Full gc是不是一回事?CMS何时触发?CMS的日志如何分析?CMS的调优如何做?CMS扫描那些对象?CMS和CMS collector的区别?CMS的推荐参数设置?为什么ParNew可以和CMS配合使用,而Parallel Scanvenge不可以?一、基础知识CMS收集器:Mostly-Concurrent收集器,也称并发标记清除收集器(Concurrent Mark-Sweep GC,CMS收集器),它管理新生代的方式与Parallel收集器和Serial收集器相同,而在老年代则是尽可能得并发执行,每个垃圾收集器周期只有2次短停顿。我之前对CMS的理解,以为它是针对老年代的收集器。今天查阅了《Java性能优化权威指南》和《Java性能权威指南》两本书,确认之前的理解是错误的。CMS的初衷和目的:为了消除Throught收集器和Serial收集器在Full GC周期中的长时间停顿。CMS的适用场景:如果你的应用需要更快的响应,不希望有长时间的停顿,同时你的CPU资源也比较丰富,就适合适用CMS收集器。二、CMS的过程CMS的正常过程这里我们首先看下CMS并发收集周期正常完成的几个状态。(STW)初始标记:这个阶段是标记从GcRoots直接可达的老年代对象、新生代引用的老年代对象,就是下图中灰色的点。这个过程是单线程的(JDK7之前单线程,JDK8之后并行,可以通过参数CMSParallelInitialMarkEnabled调整)。并发标记:由上一个阶段标记过的对象,开始tracing过程,标记所有可达的对象,这个阶段垃圾回收线程和应用线程同时运行,如上图中的灰色的点。在并发标记过程中,应用线程还在跑,因此会导致有些对象会从新生代晋升到老年代、有些老年代的对象引用会被改变、有些对象会直接分配到老年代,这些受到影响的老年代对象所在的card会被标记为dirty,用于重新标记阶段扫描。这个阶段过程中,老年代对象的card被标记为dirty的可能原因,就是下图中绿色的线:预清理:预清理,也是用于标记老年代存活的对象,目的是为了让重新标记阶段的STW尽可能短。这个阶段的目标是在并发标记阶段被应用线程影响到的老年代对象,包括:(1)老年代中card为dirty的对象;(2)幸存区(from和to)中引用的老年代对象。因此,这个阶段也需要扫描新生代+老年代。【PS:会不会扫描Eden区的对象,我看源代码猜测是没有,还需要继续求证】可中断的预清理:这个阶段的目标跟“预清理”阶段相同,也是为了减轻重新标记阶段的工作量。可中断预清理的价值:在进入重新标记阶段之前尽量等到一个Minor GC,尽量缩短重新标记阶段的停顿时间。另外可中断预清理会在Eden达到50%的时候开始,这时候离下一次minor gc还有半程的时间,这个还有另一个意义,即避免短时间内连着的两个停顿,如下图资料所示:在预清理步骤后,如果满足下面两个条件,就不会开启可中断的预清理,直接进入重新标记阶段:Eden的使用空间大于“CMSScheduleRemarkEdenSizeThreshold”,这个参数的默认值是2M;Eden的使用率大于等于“CMSScheduleRemarkEdenPenetration”,这个参数的默认值是50%。如果不满足上面两个条件,则进入可中断的预清理,可中断预清理可能会执行多次,那么退出这个阶段的出口有两个(源码参见下图):* 设置了CMSMaxAbortablePrecleanLoops,并且执行的次数超过了这个值,这个参数的默认值是0;* CMSMaxAbortablePrecleanTime,执行可中断预清理的时间超过了这个值,这个参数的默认值是5000毫秒。 如果是因为这个原因退出,gc日志打印如下:有可能可中断预清理过程中一直没等到Minor gc,这时候进入重新标记阶段的话,新生代还有很多活着的对象,就回导致STW变长,因此CMS还提供了CMSScavengeBeforeRemark参数,可以在进入重新标记之前强制进行依次Minor gc。(STW)重新标记:重新扫描堆中的对象,进行可达性分析,标记活着的对象。这个阶段扫描的目标是:新生代的对象 + Gc Roots + 前面被标记为dirty的card对应的老年代对象。如果预清理的工作没做好,这一步扫描新生代的时候就会花很多时间,导致这个阶段的停顿时间过长。这个过程是多线程的。并发清除:用户线程被重新激活,同时将那些未被标记为存活的对象标记为不可达;并发重置:CMS内部重置回收器状态,准备进入下一个并发回收周期。CMS的异常情况上面描述的是CMS的并发周期正常完成的情况,但是还有几种CMS并发周期失败的情况:并发模式失败(Concurrent mode failure):CMS的目标就是在回收老年代对象的时候不要停止全部应用线程,在并发周期执行期间,用户的线程依然在运行,如果这时候如果应用线程向老年代请求分配的空间超过预留的空间(担保失败),就回触发concurrent mode failure,然后CMS的并发周期就会被一次Full GC代替——停止全部应用进行垃圾收集,并进行空间压缩。如果我们设置了UseCMSInitiatingOccupancyOnly和CMSInitiatingOccupancyFraction参数,其中CMSInitiatingOccupancyFraction的值是70,那预留空间就是老年代的30%。晋升失败:新生代做minor gc的时候,需要CMS的担保机制确认老年代是否有足够的空间容纳要晋升的对象,担保机制发现不够,则报concurrent mode failure,如果担保机制判断是够的,但是实际上由于碎片问题导致无法分配,就会报晋升失败。永久代空间(或Java8的元空间)耗尽,默认情况下,CMS不会对永久代进行收集,一旦永久代空间耗尽,就回触发Full GC。三、CMS的调优针对停顿时间过长的调优首先需要判断是哪个阶段的停顿导致的,然后再针对具体的原因进行调优。使用CMS收集器的JVM可能引发停顿的情况有:(1)Minor gc的停顿;(2)并发周期里初始标记的停顿;(3)并发周期里重新标记的停顿;(4)Serial-Old收集老年代的停顿;(5)Full GC的停顿。其中并发模式失败会导致第(4)种情况,晋升失败和永久代空间耗尽会导致第(5)种情况。针对并发模式失败的调优想办法增大老年代的空间,增加整个堆的大小,或者减少年轻代的大小以更高的频率执行后台的回收线程,即提高CMS并发周期发生的频率。设置UseCMSInitiatingOccupancyOnly和CMSInitiatingOccupancyFraction参数,调低CMSInitiatingOccupancyFraction的值,但是也不能调得太低,太低了会导致过多的无效的并发周期,会导致消耗CPU时间和更多的无效的停顿。通常来讲,这个过程需要几个迭代,但是还是有一定的套路,参见《Java性能权威指南》中给出的建议,摘抄如下:> 对特定的应用程序,该标志的更优值可以根据 GC 日志中 CMS 周期首次启动失败时的值得到。具体方法是,在垃圾回收日志中寻找并发模式失效,找到后再反向查找 CMS 周期最近的启动记录,然后根据日志来计算这时候的老年代空间占用值,然后设置一个比该值更小的值。增多回收线程的个数CMS默认的垃圾收集线程数是*(CPU个数 + 3)/4*,这个公式的含义是:当CPU个数大于4个的时候,垃圾回收后台线程至少占用25%的CPU资源。举个例子:如果CPU核数是1-4个,那么会有1个CPU用于垃圾收集,如果CPU核数是5-8个,那么久会有2个CPU用于垃圾收集。针对永久代的调优如果永久代需要垃圾回收(或元空间扩容),就会触发Full GC。默认情况下,CMS不会处理永久代中的垃圾,可以通过开启CMSPermGenSweepingEnabled配置来开启永久代中的垃圾回收,开启后会有一组后台线程针对永久代做收集,需要注意的是,触发永久代进行垃圾收集的指标跟触发老年代进行垃圾收集的指标是独立的,老年代的阈值可以通过CMSInitiatingPermOccupancyFraction参数设置,这个参数的默认值是80%。开启对永久代的垃圾收集只是其中的一步,还需要开启另一个参数——CMSClassUnloadingEnabled,使得在垃圾收集的时候可以卸载不用的类。四、CMS的trade-off是什么?优势低延迟的收集器:几乎没有长时间的停顿,应用程序只在Minor gc以及后台线程扫描老年代的时候发生极其短暂的停顿。劣势更高的CPU使用:必须有足够的CPU资源用于运行后台的垃圾收集线程,在应用程序线程运行的同时扫描堆的使用情况。【PS:现在服务器的CPU资源基本不是问题,这个点可以忽略】CMS收集器对老年代收集的时候,不再进行任何压缩和整理的工作,意味着老年代随着应用的运行会变得碎片化;碎片过多会影响大对象的分配,虽然老年代还有很大的剩余空间,但是没有连续的空间来分配大对象,这时候就会触发Full GC。CMS提供了两个参数来解决这个问题:(1)UseCMSCompactAtFullCollection,在要进行Full GC的时候进行内存碎片整理;(2)CMSFullGCsBeforeCompaction,每隔多少次不压缩的Full GC后,执行一次带压缩的Full GC。会出现浮动垃圾;在并发清理阶段,用户线程仍然在运行,必须预留出空间给用户线程使用,因此CMS比其他回收器需要更大的堆空间。五、几个问题的解答为什么ParNew可以和CMS配合使用,而Parallel Scanvenge不可以?答:这个跟Hotspot VM的历史有关,Parallel Scanvenge是不在“分代框架”下开发的,而ParNew、CMS都是在分代框架下开发的。CMS中minor gc和major gc是顺序发生的吗?答:不是的,可以交叉发生,即在并发周期执行过程中,是可以发生Minor gc的,这个找个gc日志就可以观察到。CMS的并发收集周期合适触发?由下图可以看出,CMS 并发周期触发的条件有两个:阈值检查机制:老年代的使用空间达到某个阈值,JVM的默认值是92%(jdk1.5之前是68%,jdk1.6之后是92%),或者可以通过CMSInitiatingOccupancyFraction和UseCMSInitiatingOccupancyOnly两个参数来设置;这个参数的设置需要看应用场景,设置得太小,会导致CMS频繁发生,设置得太大,会导致过多的并发模式失败。例如动态检查机制:JVM会根据最近的回收历史,估算下一次老年代被耗尽的时间,快到这个时间的时候就启动一个并发周期。设置UseCMSInitiatingOccupancyOnly这个参数可以将这个特性关闭。CMS的并发收集周期会扫描哪些对象?会回收哪些对象?答:CMS的并发周期只会回收老年代的对象,但是在标记老年代的存活对象时,可能有些对象会被年轻代的对象引用,因此需要扫描整个堆的对象。CMS的gc roots包括哪些对象?答:首先,在JVM垃圾收集中Gc Roots的概念如何理解(参见R大对GC roots的概念的解释);第二,CMS的并发收集周期中,如何判断老年代的对象是活着?我们前面提到了,在CMS的并发周期中,仅仅扫描Gc Roots直达的对象会有遗漏,还需要扫描新生代的对象。如下图中的蓝色字体所示,CMS中的年轻代和老年代是分别收集的,因此在判断年轻代的对象存活的时候,需要把老年代当作自己的GcRoots,这时候并不需要扫描老年代的全部对象,而是使用了card table数据结构,如果一个老年代对象引用了年轻代的对象,则card中的值会被设置为特殊的数值;反过来判断老年代对象存活的时候,也需要把年轻代当作自己的Gc Roots,这个过程我们在第三节已经论述过了。如果我的应用决定使用CMS收集器,推荐的JVM参数是什么?我自己的应用使用的参数如下,是根据PerfMa的xxfox生成的,大家也可以使用这个产品调优自己的JVM参数:-Xmx4096M -Xms4096M -Xmn1536M -XX:MaxMetaspaceSize=512M -XX:MetaspaceSize=512M -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses -XX:+CMSClassUnloadingEnabled -XX:+ParallelRefProcEnabled -XX:+CMSScavengeBeforeRemark -XX:ErrorFile=/home/admin/logs/xelephant/hs_err_pid%p.log -Xloggc:/home/admin/logs/xelephant/gc.log -XX:HeapDumpPath=/home/admin/logs/xelephant -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryErrorCMS相关的参数总结(需要注意的是,这里我没有考虑太多JDK版本的问题,JDK1.7和JDK1.8这些参数的配置,有些默认值可能不一样,具体使用的时候还需要根据具体的版本来确认怎么设置)| 编号 | 参数名称 | 解释 || — | — | — || 1 | UseConcMarkSweepGC | 启用CMS收集器 || 2 | UseCMSInitiatingOccupancyOnly | 关闭CMS的动态检查机制,只通过预设的阈值来判断是否启动并发收集周期 || 3 | CMSInitiatingOccupancyFraction | 老年代空间占用到多少的时候启动并发收集周期,跟UseCMSInitiatingOccupancyOnly一起使用 || 4 | ExplicitGCInvokesConcurrentAndUnloadsClasses | 将System.gc()触发的Full GC转换为一次CMS并发收集,并且在这个收集周期中卸载 Perm(Metaspace)区域中不需要的类 || 5 | CMSClassUnloadingEnabled | 在CMS收集周期中,是否卸载类 || 6 | ParallelRefProcEnabled | 是否开启并发引用处理 || 7 | CMSScavengeBeforeRemark | 如果开启这个参数,会在进入重新标记阶段之前强制触发一次minor gc |参考资料从实际案例聊聊Java应用的GC优化理解CMS垃圾回收日志图解CMS垃圾回收机制,你值得拥有为什么CMS虽然是老年代的gc,但仍要扫描新生代的?R大对GC roots的概念的解释Introduce to CMS Collector《深入理解Java虚拟机》《Java性能权威指南》Oracle的GC调优手册what-is-the-threshold-for-cms-old-gc-to-be-triggeredFrequently Asked Questions about Garbage Collection in the Hotspot Java VirtualMachineJava SE HotSpot at a Glancexxfox:PerfMa的参数调优神器详解CMS垃圾回收机制ParNew和PSYoungGen和DefNew是一个东西么?Java SE的内存管理白皮书Garbage Collection in Elasticsearch and the G1GCA Heap of Trouble毕玄的文章:为什么不建议JVM源码分析之SystemGC完全解读读者讨论关于CMS收集器的回收范围,下面这张图是有误导的,从官方文档上看来,CMS收集器包括年轻代和老年代的收集,只不过对年轻代的收集的策略和ParNew相同,这个可以从参考资料16的第11页看到。concurrent mode failure和promotion failed触发的Full GC有啥不同?(这个问题是我、阿飞、蒋晓峰一起讨论的结果)答:concurrent mode failure触发的"Full GC"不是我们常说的Full GC——正常的Full GC其实是整个gc过程包括ygc和cms gc。也就是说,这个问题本身是有问题的,concurrent mode failure的时候触发的并不是我们常说的Full GC。然后再去讨论一个遗漏的知识点:CMS gc的并发周期有两种模式:foreground和background。concurrent mode failure触发的是foreground模式,会暂停整个应用,会将一些并行的阶段省掉做一次老年代收集,行为跟Serial-Old的一样,至于在这个过程中是否需要压缩,则需要看三个条件:(1)我们设置了UseCMSCompactAtFullCollection和CMSFullGCsBeforeCompaction,前者设置为true,后者默认是0,前者表示是在Full GC的时候执行压缩,后者表示是每隔多少个进行压缩,默认是0的话就是每次Full GC都压缩;(2)用户调用了System.gc(),而且DisableExplicitGC没有开启;(3)young gen报告接下来如果做增量收集会失败。promotion failed触发的是我们常说的的Full GC,对年轻代和老年代都会回收,并进行整理。promotion failed和concurrent mode failure的触发原因有啥不同?promotion failed是说,担保机制确定老年代是否有足够的空间容纳新来的对象,如果担保机制说有,但是真正分配的时候发现由于碎片导致找不到连续的空间而失败;concurrent mode failure是指并发周期还没执行完,用户线程就来请求比预留空间更大的空间了,即后台线程的收集没有赶上应用线程的分配速度。什么情况下才选择使用CMS收集器呢?我之前的观念是:小于8G的都用CMS,大于8G的选择G1。蒋晓峰跟我讨论了下这个观念,提出了一些别的想法,我觉得也有道理,记录在这里:除了看吞吐量和延时,还需要看具体的应用,比方说ES,Lucene和G1是不兼容的,因此默认的收集器就是CMS,具体见可参考资料17和18。小于3G的堆,如果不是对延迟有特别高的需求,不建议使用CMS,主要是由于CMS的几个缺点导致的:(1)并发周期的触发比例不好设置;(2)抢占CPU时间;(3)担保判断导致YGC变慢;(4)碎片问题,更详细的讨论参见资料19。本文作者:杜琪阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 10, 2019 · 1 min · jiezi

Node.js 应用故障排查手册 —— 综合性 GC 问题和优化

楔子本章前面两节生产案例分别侧重于单一的 CPU 高和单一的内存问题,我们也给大家详细展示了问题的定位排查过程,那么实际上还有一类相对更复杂的场景——它本质上是 V8 引擎的 GC 引发的问题。简单的给大家介绍下什么是 GC,GC 实际上是语言引擎实现的一种自动垃圾回收机制,它会在设定的条件触发时(比如堆内存达到一定值)时查看当前堆上哪些对象已经不再使用,并且将这些没有再使用到的对象所占据的空间释放出来。许多的现代高级语言都实现了这一机制,来减轻程序员的心智负担。本书首发在 Github,仓库地址:https://github.com/aliyun-node/Node.js-Troubleshooting-Guide,云栖社区会同步更新。GC 带来的问题虽然上面介绍中现代语言的 GC 机制解放了程序员间接提升了开发效率,但是万事万物都存在利弊,底层的引擎引入 GC 后程序员无需再关注对象何时释放的问题,那么相对来说程序员也就没办法实现对自己编写的程序的精准控制,它带来两大问题:代码编写问题引发的内存泄漏程序执行的性能降低内存泄漏问题我们已经在上一节的生产案例中体验了一下,那么后者是怎么回事呢?其实理解起来也很简单:原本一个程序全部的工作都是执行业务逻辑,但是存在了 GC 机制后,程序总会在一定的条件下耗费时间在扫描堆空间找出不再使用的对象上,这样就变相降低了程序执行业务逻辑的时间,从而造成了性能的下降,而且降低的性能和耗费在 GC 上的时间,换言之即 GC 的次数 * 每次 GC 耗费的时间成正比。问题现象与原始分析现在大家应该对 GC 有了一个比较整体的了解,这里我们可以看下 GC 引发的问题在生产中的表现是什么样的。在这个案例中,表象首先是 Node.js 性能平台 上监控到进程的 CPU 达到 100%,但是此时服务器负载其实并不大,QPS 只有 100 上下,我们按照前面提到的处理 CPU 问题的步骤抓取 CPU Profile 进行分析可以看到:这次的问题显然是 Garbage Collector 耗费的 CPU 太多了,也就是 GC 的问题。实际上绝大部分的 GC 机制引发的问题往往表象都是反映在 Node.js 进程的 CPU 上,而本质上这类问题可以认为是引擎的 GC 引起的问题,也可以理解为内存问题,我们看下这类问题的产生流程:堆内存不断达到触发 GC 动作的预设条件进程不断触发 GC 操作进程 CPU 飙高而且 GC 问题不像之前的 ejs 模板渲染引发的问题,就算我们在 CPU Profile 中可以看到这部分的耗费,但是想要优化解决这个问题基本是无从下手的,幸运的是 Node.js 提供了(其实是 V8 引擎提供的)一系列的启动 Flag 能够输出进程触发 GC 动作时的相关日志以供开发者进行分析:–trace_gc:一行日志简要描述每次 GC 时的时间、类型、堆大小变化和产生原因–trace_gc_verbose: 结合 –trace_gc 一起开启的话会展示每次 GC 后每个 V8 堆空间的详细状况–trace_gc_nvp: 每一次 GC 的一些详细键值对信息,包含 GC 类型,暂停时间,内存变化等信息加粗的 Flag 意味着我们需要在启动应用前加上才能在运行时生效,这部分的日志实际上是一个文本格式,可惜的是 Chrome devtools 原生并不支持 GC 日志的解析和结果展示,因此需要大家获取到以后进行对应的按行解析处理,当然我们也可以使用社区提供 v8-gc-log-parser 这个模块直接进行解析处理,对这一块有兴趣的同学可以看 @joyeeCheung 在 JS Interactive 的分享: Are Your V8 Garbage Collection Logs Speaking To You?,这里不详细展开。更好的 GC 日志展示虽然 Chrome devtools 并不能直接帮助我们解析展示 GC 日志的结果,但是 Node.js 性能平台 其实给大家提供了更方便的动态获取线上运行进程的 GC 状态信息以及对应的结果展示,换言之,开发者无需在运行你的 Node.js 应用前开启上面提到的那些 Flag 而仍然可以在想要获取到 GC 信息时通过控制台拿到 3 分钟内的 GC 数据。对应在这个案例中,我们可以进入平台的应用实例详情页面,找到 GC 耗费特别大的进程,然后点击 GC Trace 抓取 GC 数据:这里默认会抓取 3 分钟的对应进程的 GC 日志信息,等到结束后生成的文件会显示在 文件 页面:此时点击 转储 即可上传到云端以供在线分析展示了,如下图所示:最后点击这里的 分析 按钮,即可看到 AliNode 定制后的 GC 信息分析结果的展现:结果展示中,可以比较方便的看到问题进程的 GC 具体次数,GC 类型以及每次 GC 的耗费时间等信息,方便我们进一步的分析定位。比如这次问题的 GC Trace 结果分析图中,我们可以看到红圈起来的几个重要信息:GC 总暂停时间高达 47.8s,大头是 Scavenge3min 的 GC 追踪日志里面,总共进行了 988 次的 Scavenge 回收每次 Scavenge 耗时均值在 50 ~ 60ms 之间从这些解困中我们可以看到此次 GC 案例的问题点集中在 Scavenge 回收阶段,即新生代的内存回收。那么通过翻阅 V8 的 Scavenge 回收逻辑可以知道,这个阶段触发回收的条件是:Semi space allocation failed。这样就可以推测,我们的应用在压测期间应该是在新生代频繁生成了大量的小对象,导致默认的 Semi space 总是处于很快被填满从而触发 Flip 的状态,这才会出现在 GC 追踪期间这么多的 Scavenge 回收和对应的 CPU 耗费,这样这个问题就变为如何去优化新生代的 GC 来提升应用性能。优化新生代 GC通过平台提供的 GC 数据抓取和结果分析,我们知道可以去尝试优化新生代的 GC 来达到提升应用性能的目的,而新生代的空间触发 GC 的条件又是其空间被占满,那么新生代的空间大小由 Flag –max-semi-space-size 控制,默认为 16MB,因此我们自然可以想到要可以通过调整默认的 Semi space 的值来进行优化。这里需要注意的是,控制新生代空间的 Flag 在不同的 Node.js 版本下并不是一样的,大家具体可以查看当前的版本来进行确认使用。在这个案例中,显然是默认的 16M 相对当前的应用来说显得比较小,导致 Scavenge 过于频繁,我们首先尝试通过启动时增加 –max-semi-space-size=64 这个 Flag 来将默认的新生代使用到的空间大小从 16M 的值增大为 64M,并且在流量比较大而且进程 CPU 很高时抓取 CPU Profile 观察效果:调整后可以看到 Garbage collector 阶段 CPU 耗费占比下降到 7% 左右,再抓取 GC Trace 并观察其展示结果确认是不是 Scavenge 阶段的耗费下降了:显然,Semi space 调大为 64M 后,Scavenge 次数从近 1000 次降低到 294 次,但是这种状况下每次的 Scavenge 回收耗时没有明显增加,还是在 50 ~ 60ms 之间波动,因此 3 分钟的 GC 追踪总的停顿时间从 48s 下降到 12s,相对应的,业务的 QPS 提升了约 10% 左右。那么如果我们通过 –max-semi-space-size 这个 Flag 来继续调大新生代使用到的空间,是不是可以进一步优化这个应用的性能呢?此时尝试 –max-semi-space-size=128 来从 64M 调大到 128M,在进程 CPU 很高时继续抓取 CPU Profile 来查看效果:此时 Garbage collector 耗费下降相比上面的设置为 64M 并不是很明显,GC 耗费下降占比不到 1%,同样我们来抓取并观察下 GC Trace 的结果来查看具体的原因:很明显,造成相比设置为 64M 时 GC 占比提升不大的原因是:虽然此时进一步调大了 Semi space 至 128M,并且 Scavenge 回收的次数确实从 294 次下降到 145 次,但是每次算法回收耗时近乎翻倍了,因此总收益并不明显。按照这个思路,我们再使用 –max-semi-space-size=256 来将新生代使用的空间进一步增大到 256M 再来进行最后一次的观察:这里和调整为 128M 时是类似的情况: 3 分钟内 Scavenge 次数从 294 次下降到 72 次,但是相对的每次算法回收耗时波动到了 150ms 左右,因此整体性能并没有显著提升。借助于性能平台的 GC 数据抓取和结果展示,通过以上的几次尝试改进 Semi space 的值后,我们可以看到从默认的 16M 设置到 64M 时,Node 应用的整体 GC 性能是有显著提升的,并且反映到压测 QPS 上大约提升了 10%;但是进一步将 Semi space 增大到 128M 和 256M 时,收益确并不明显,而且 Semi space 本身也是作用于新生代对象快速内存分配,本身不宜设置的过大,因此这次优化最终选取对此项目 最优的运行时 Semi space 的值为 64M。结尾在本生产案例中,我们首先可以看到,项目使用的三方库其实也并不总是在所有场景下都不会有 Bug 的(实际上这是不可能的),因此在遇到三方库的问题时我们要敢于去从源码的层面来对问题进行深入的分析。最后实际上在生产环境下通过 GC 方面的运行时调优来提升我们的项目性能是一种大家不那么常用的方式,这也有很大一部分原因是应用运行时 GC 状态本身不直接暴露给开发者。通过上面这个客户案例,我们可以看到借助于 Node.js 性能平台,实时感知 Node 应用 GC 状态以及进行对应的优化,使得不改一行代码提升项目性能变成了一件非常容易的事情。本文作者:奕钧阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 9, 2019 · 2 min · jiezi

Node.js 应用故障排查手册 —— 正确打开 Chrome devtools

楔子前面的预备章节中我们大致了解了如何在服务器上的 Node.js 应用出现问题时,从常规的错误日志、系统/进程指标以及兜底的核心转储这些角度来排查问题。这样就引出了下一个问题:我们知道进程的 CPU/Memory 高,或者拿到了进程 Crash 后的核心转储,要如何去进行分析定位到具体的 JavaScript 代码段。其实 Chrome 自带的 Devtools,对于 JavaScript 代码的上述 CPU/Memory 问题有着很好的原生解析展示,本节会给大家做一些实用功能和指标的介绍(基于 Chrome v72,不同的版本间使用方式存在差异)。本书首发在 Github,仓库地址:https://github.com/aliyun-node/Node.js-Troubleshooting-Guide,云栖社区会同步更新。CPU 飙高问题I. 导出 JS 代码运行状态当我们通过第一节中提到的系统/进程指标排查发现当前的 Node.js 应用的 CPU 特别高时,首先我们需要去通过一些方式将当前 Node.js 应用一段时间内的 JavaScript 代码运行状况 Dump 出来,这样子才能分析知道 CPU 高的原因。幸运的是,V8 引擎内部实现了一个 CPU Profiler 能够帮助我们完成一段时间内 JS 代码运行状态的导出,目前也有不少成熟的模块或者工具来帮我们完成这样的操作。v8-profiler 是一个老牌的 Node.js 应用性能分析工具,它可以很方便地帮助开发者导出 JS 代码地运行状态,我们可以在项目目录执行如下命令安装此模块:npm install v8-profiler –save接着可以在代码中按照如下方式获取到 5s 内地 JS 代码运行状态:‘use strict’;const v8Profiler = require(‘v8-profiler’);const title = ’test’;v8Profiler.startProfiling(title, true);setTimeout(() => { const profiler = v8Profiler.stopProfiling(title); profiler.delete(); console.log(profiler);}, 5000);那么我们可以看到,v8-profiler 模块帮我导出的代码运行状态实际上是一个很大的 JSON 对象,我们可以将这个 JSON 对象序列化为字符串后存储到文件:test.cpuprofile 。注意这里的文件名后缀必须为 .cpuprofile ,否则 Chrome devtools 是不识别的。注意:v8-profiler 目前也处于年久失修的状态了,在 Node.js 8 和 Node.js 10 上已经无法正确编译安装了,如果你在 8 或者 10 的项目中想进行使用,可以试试看 v8-profiler-next。II. 分析 CPU Profile 文件借助于 v8-profiler 拿到我们的 Node.js 应用一段时间内的 JS 代码运行状态后,我们可以将其导入 Chrome devtools 中进行分析展示。在 Chrome 72 中,分析我们 Dump 出来的 CPU Profile 的方法已经和之前有所不同了,默认工具栏中也不会展示 CPU Profile 的分析页面,我们需要通过点击工具栏右侧的 更多 按钮,然后选择 More tools -> JavaScript Profiler 来进入到 CPU 的分析页面,如下图所示:选中 JavaScript Profiler 后,在出现的页面上点击 Load 按钮,然后将刚才保存得到的 test.cpuprofile 文件加载进来,就可以看到 Chrome devtools 的解析结果了:这里默认的视图是 Heavy 视图,在这个视图下,Devtools 会按照对你的应用的影响程度从高到低,将这些函数列举出来,点击展开能够看到这些列举出来的函数的全路径,方便你去代码中对应的位置进行排查。这里解释两个比较重要的指标,以便让大家能更有针对性地进行排查:Self Time: 此函数本身代码段执行地时间(不包含任何调用)Total Time: 此函数包含了其调用地其它函数总共的执行时间像在上述地截图例子中,ejs 模块在线上都应该开启了缓存,所以 ejs 模块的 compile 方法不应该出现在列表中,这显然是一个非常可疑的性能损耗点,需要我们去展开找到原因。除了 Heavy 视图,Devtools 实际上还给我们提供了火焰图来进行更多维度的展示,点击左上角可以切换:火焰图按照我们的 CPU 采样时间轴进行展示,那么在这里我们更容易看到我们的 Node.js 应用在采样期间 JS 代码的执行行为,新增的两个指标这边也给大家解释一下其含义:Aggregated self time: 在 CPU 采样期间聚合后的此函数本身代码段的执行总时间(不包含其他调用)Aggregated total time: 在 CPU 采样期间聚合后的此函数包含了其调用地其它函数总共的执行总时间综上,借助于 Chrome devtools 和能够导出当前 Node.js 应用 Javascript 代码运行状态的模块,我们已经可以比较完备地对应用服务异常时,排查定位到相应的 Node.js 进程 CPU 很高的情况进行排查和定位分析了。在生产实践中,这部分的 JS 代码的性能的分析往往也会用到新项目上线前的性能压测中,有兴趣的同学可以更深入地研究下。内存泄漏问题I. 判断是否内存泄漏在笔者的经历中,内存泄漏问题是 Node.js 在线上运行时出现的问题种类中的重灾区。尤其是三方库自身的 Bug 或者开发者使用不当引起的内存泄漏,会让很多的 Node.js 开发者感到束手无策。本节首先向读者介绍下,什么情况下我们的应用算是有很大的可能在发生内存泄漏呢?实际上判断我们的线上 Node.js 应用是否有内存泄漏也非常简单:借助于大家各自公司的一些系统和进程监控工具,如果我们发现 Node.js 应用的总内存占用曲线 处于长时间的只增不降 ,并且堆内存按照趋势突破了 堆限制的 70% 了,那么基本上应用 很大可能 产生了泄漏。当然事无绝对,如果确实应用的访问量(QPS)也在一直增长中,那么内存曲线只增不减也属于正常情况,如果确实因为 QPS 的不断增长导致堆内存超过堆限制的 70% 甚至 90%,此时我们需要考虑的扩容服务器来缓解内存问题。II. 导出 JS 堆内存快照如果确认了 Node.js 应用出现了内存泄漏的问题,那么和上面 CPU 的问题一样,我们需要通过一些办法导出 JS 内存快照(堆快照)来进行分析。V8 引擎同样在内部提供了接口可以直接将分配在 V8 堆上的 JS 对象导出来供开发者进行分析,这里我们采用 heapdump 这个模块,首先依旧是执行如下命令进行安装:npm install heapdump –save接着可以在代码中按照如下方法使用此模块:‘use sytrict’;const heapdump = require(‘heapdump’);heapdump.writeSnapshot(’./test’ + ‘.heapsnapshot’);这样我们就在当前目录下得到了一个堆快照文件:test.heapsnapshot ,用文本编辑工具打开这个文件,可以看到其依旧是一个很大的 JSON 结构,同样这里的堆快照文件后缀必须为 .heapsnapshot ,否则 Chrome devtools 是不识别的。III. 分析堆快照在 Chrome devtools 的工具栏中选择 Memory 即可进入到分析页面,如下图所示:然后点击页面上的 Load 按钮,选择我们刚才生成 test.heapsnapshot 文件,就可以看到分析结果,如下图所示:默认的视图其实是一个 Summary 视图,这里的 Constructor 和我们编写 JS 代码时的构造函数并无不同,都是指代此构造函数创建的对象,新版本的 Chrome devtools 中还在构造函数后面增加 * number 的信息,它代表这个构造函数创建的实例的个数。实际上在堆快照的分析视图中,有两个非常重要的概念需要大家去理解,否则很可能拿到堆快照看着分析结果也无所适从,它们是 Shallow Size 和 Retained Size ,要更好地去理解这两个概念,我们需要先了解 支配树。首先我们看如下简化后的堆快照描述的内存关系图:这里的 1 为根节点,即 GC 根,那么对于对象 5 来说,如果我们想要让对象 5 回收(即从 GC 根不可达),仅仅去掉对象 4 或者对象 3 对于对象 5 的引用是不够的,因为显然从根节点 1 可以分别从对象 3 或者对象 4 遍历到对象 5。因此我们只有去掉对象 2 才能将对象 5 回收,所以在上面这个图中,对象 5 的直接支配者是对象 2。照着这个思路,我们可以通过一定的算法将上述简化后的堆内存关系图转化为支配树:对象 1 到对象 8 间的支配关系描述如下:对象 1 支配对象 2对象 2 支配对象 3 、4 和 5对象 4 支配对象 6对象 5 支配对象 7对象 6 支配对象 8好了,到这里我们可以开始解释什么是 Shallow Size 和 Retained Size 了,实际上对象的 Shallow Size 就是对象自身被创建时,在 V8 堆上分配的大小,结合上面的例子,即对象 1 到 8 自身的大小。对象的 Retained Size 则是把此对象从堆上拿掉,则 Full GC 后 V8 堆能够释放出的空间大小。同样结合上面的例子,支配树的叶子节点对象 3、对象 7 和对象 8 因为没有任何直接支配对象,因此其 Retained Size 等于其 Shallow Size。将剩下的非叶子节点可以一一展开,为了篇幅描述方便,SZ_5表示对象 5 的 Shallow Size,RZ_5 表示对象 5 的 Retained Size,那么可以得到如下结果:对象 3 的 Retained Size:RZ_3 = SZ_3对象 7 的 Retained Size:RZ_7 = SZ_7对象 8 的 Retained Size:RZ_8 = SZ_8对象 6 的 Retained Size:RZ_6 = SZ_6 + RZ_8 = SZ_6 + SZ_8对象 5 的 Retained Size:RZ_5 = SZ_5 + RZ_7 = SZ_5 + SZ_7对象 4 的 Retained Size:RZ_4 = SZ_4 + RZ_6 = SZ_4 + SZ_6 + SZ_8对象 2 的 Retained Size:RZ_2 = SZ_2 + RZ_3 + RZ_4 + RZ_5 = SZ_2 + SZ_3 + SZ_4 + SZ_5 + SZ_6 + SZ_7 + SZ_8GC 根 1 的 Retained Size:RZ_1 = SZ_1 + RZ_2 = SZ_1 + SZ_2 + RZ_3 + RZ_4 + RZ_5 = SZ_2 + SZ_3 + SZ_4 + SZ_5 + SZ_6 + SZ_7 + SZ_8这里可以发现,GC 根的 Retained Size 等于堆上所有从此根出发可达对象的 Shallow Size 之和,这和我们的理解预期是相符合的,毕竟将 GC 根从堆上拿掉的话,原本就应当将从此根出发的所有对象都清理掉。理解了这一点,回到我们最开始看到的默认总览视图中,正常来说,可能的泄漏对象往往其 Retained Size 特别大,我们可以在窗口中依据 Retained Size 进行排序来对那些占据了堆空间绝大部分的对象进行排查:假如确认了可疑对象,Chrome devtools 中也会给你自动展开方便你去定位到代码段,下面以 NativeModule 这个构造器生成的对象 vm 为例:这里上半部分是顺序的引用关系,比如 NativeModule 实例 @45655 的 exports 属性指向了对象 @45589,filename 属性则指向一个字符串 “vm.js”;下半部分则是反向的引用关系:NativeModule 实例 @13021 的 _cache 属性指向了 Object 实例 @41103,而 Object 实例 @41103 的 vm 属性指向了 NativeModule 实例 @45655。如果对这部分展示图表比较晕的可以仔细看下上面的例子,因为找到可疑的泄漏对象,结合上图能看到此对象下的属性和值及其父引用关系链,绝大部分情况下我们就可以定位到生成可疑对象的 JS 代码段了。实际上除了默认的 Summary 视图,Chrome devtools 还提供了 Containment 和 Statistics 视图,这里再看下 Containment 视图,选择堆快照解析页面的左上角可以进行切换,如下图所示:这个视图实际上是堆快照解析出来的内存关系图的直接展示,因此相比 Summary 视图,从这个视图中直接查找可疑的泄漏对象相对比较困难。结尾Chrome devtools 实际上是非常强大的一个工具,本节也只是仅仅介绍了对 CPU Profile 和堆快照解析能力的介绍和常用视图的使用指南,如果你仔细阅读了本节内容,面对服务器上定位到的 Node.js 应用 CPU 飙高或者内存泄漏这样的问题,想必就可以做到心中有数不慌乱了。本文作者:奕钧阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 4, 2019 · 3 min · jiezi

CPU & Memory, Part 3: Virtual Memory

博文:https://chanjarster.github.io…原文:What every programmer should know about memory, Part 3: Virtual Memory4 Virtual Memory虚拟内存(virtual memory)是处理器的一个子系统,它给每个进程提供虚拟地址空间(virtual address space)。这让每个进程以为自己在系统中是独自一人。wiki词条:虚拟内存的作用在于为进程提供“看上去”连续的地址空间,这么做的好处在于程序不需要处理内存碎片的问题了。虚拟地址空间由CPU的Memory Management Unit(MMU)实现,操作系统必须填写页表数据结构(page table data structures,见wiki词条),大多数CPU自己完成余下的工作。把虚拟地址(virtual address)作为输入交给MMU做翻译。在32位系统中虚拟地址是32位的,在64位系统中是64位的。4.1 Simplest Address TranslationMMU可以逐页(page)的将虚拟地址翻译成物理地址的,和cache line一样,虚拟地址被分割成多个部分,这些部分则被索引到不同的表(table)里,这些表用来构造最终的物理地址。最简单的模型则只拥有一个级别的表(only one level of tables)。Figure 4.1: 1-Level Address Translation虚拟地址的结构:虚拟地址的头部被用来在一个页目录(Page Directory)中选择条目(entry),页目录中存储的是条目(entry),每个条目可由操作系统单独设置。条目决定了物理内存页的地址,即页的物理地址虚拟地址的尾部是页内的偏移量所以页的物理地址+偏移量=物理地址页目录的条目还包含一些辅助信息,比如访问权限页目录的存储:页目录是存在内存里的,操纵系统为其分配一段连续的物理内存空间,并将基地址(base address)存在一个特殊的寄存器里而条目在目录里就是一个数组(记住这是数组,这对于理解下面多级目录,多级索引很重要)先弄个速算表,下面会用得着:29=512210=512 * 2=1024=1K220=1024 * 1024=1MB拿x86系统,4MB页举例:虚拟地址的偏移量部分占用22位(可以覆盖4MB的空间)目录部分则还剩10位,即可以存放1024个条目每个条目存了10位的物理页内存的基地址10位+22位=32位,形成了完整的物理内存地址4.2 Multi-Level Page Tables多级页表(page table),注意原文写到这里用页表(page table)而不是页目录(page directory),这两个实际上是一个东西。上面的例子拿4MB页来举例的,不过4MB页表不常见,这是因为操作系统执行的很多操作是按照页来对齐的,意思是每个页的基地址之间都差4MB的倍数,就算你要用1k内存也要申请了一个4MB的页,这造成了大量的浪费。真实世界里32位机器大多用4kB页,同样多见于64位机器。为啥4kB页,单级页表不行:虚拟地址偏移量占12位虚拟地址页目录部分占20位(64位机器就是52位)页表条目数=220,就算每个条目只占4 bytes(32位)那整个页表页要占4MB然后每个进程会拥有自己的页表,那么大量的物理内存就会被用在页表上。实际上不光是物理内存用量太大的问题,因页表就是一个数组,需要连续的内存空间,到时候很难分配。解决办法是使用多级页表。它们能够代表一个稀疏的巨大的页表,可以做到对没有被使用的区域(原文没有讲区域是啥)不需要分配内存。这种形式跟为紧凑,可以为许多进程提供页表,同时又不对性能产生太大影响。Figure 4.2: 4-Level Address Translation上面是一个4级页表:虚拟地址被分割成5个部分,其中4个部分是不同页表的索引第4级页表通过CPU里的一个特殊目的的register来引用第4级-第2级的页表的内容是对下一级页表引用(我觉得应该就是物理内存地址,因为前面讲过页表存在物理内存中的)第1级页表存储的物理地址的一部分(应该就是去掉偏移量的那一部分)和辅助数据,比如访问权限所以整个形成了一个页表树(page table tree),稀疏又紧凑(sparse and compact)得到物理地址的步骤,Page tree walking:先从register中得到第4级页表的地址,拿到第4级页表拿虚拟地址中Level 4 Index取得页表中的条目,这个条目里存的是第3级页表的地址拿到第3级页表拿虚拟地址中Level 3 Index取得页表中的条目,这个条目里存的是第2级页表的地址如此反复直到拿到第1级页表里的条目,这个条目里存的是物理地址的高位部分结合虚拟地址中的偏移量,得到最终的物理地址Page tree walking在x86、x86-64处理器里是发生在硬件层面的Page table tree尺寸对性能的影响:每个进程可能需要自己的page table tree,几个进程共享树的一部分是存在的,但这只是特例。如果页表树所需内存越小,那就越有利于性能和扩展性(performance and scalability)理想情况下,把使用的内存在虚拟地址空间里紧密的放在一起,就能够让page table tree占用的空间小(单独看这句没有办法明白,结合后面的内容看举例,4kB/页,512条目/页表,1页表/每级,那么可以寻址2MB连续的地址空间(512*4kB=2MB)举例,4kB/页,512条目/页表,4-2级只有1个页表,1级有512个页表,那么可以寻址1GB连续的地址空间(512 512 4KB=1G)Page table tree布局:假设所有内存都能够连续的被分配太过简单了比如,出于灵活性的考虑(flexibility),stack和heap分占地址空间的两端,所以极有可能有2个2级页表,每个二级页表有一个1级页表。显示中比上面这个更复杂,处于安全性考虑,不同的可执行部分(code、data、heap、stack、DSOs又称共享库)是被影射到随机地址上的。所以进程所使用的不同内存区域是遍布整个虚拟地址空间的。所以一个进程不可能只有一两个2级3级页表的。个人总结,前面讲的对于多少连续的寻址空间,各级别页表需要多少个是这么计算的:首先得知道前提,对于4-2级页表,在同一页表内,不同页表条目不会指向同一个下一级页表对于1级页表,不同页表条目不会指向相同的物理地址(准确的说是物理地址去掉offset的部分)对于4-2级页表,每个页表条目指向一个下级页表,即上级页表条目数目=下级页表数假设现在是32位系统,每个页表至多保存29=512个页表项下面举连续的2MB寻址空间(页大小为4kB):2MB=210 210 2=221 bytes所以需要:2MB / 4kB = 221 / 212 = 29个1级页表条目所以需要:29 / 29=1个一级页表=1个2级页表条目所以前面说,4kB/页,512条目/页表,1页表/每级,那么可以寻址2MB连续的地址空间下面举例连续的1GB寻址空间(页大小为4kB):1GB=210 210 210=230 bytes所以需要1级页表条目:1GB / 4kB = 230 / 212=218个1级页表条目所以需要:218 / 29=29个1级页表=29个2级页表条目所以需要:29 / 29=1个2级页表所以前面说,4kB/页,512条目/页表,4-2级只有1个页表,1级有512个页表,那么可以寻址1GB连续的地址空间(512 512 4KB=1G)同理如果是连续的2GB寻址空间(页大小为4kB):1GB=210 210 210 * 2=231 bytes所以需要:1GB / 4kB = 231 / 212=219个1级页表条目所以需要:219 / 29=210个1级页表=210个2级页表条目所以需要:210 / 29=2个二级页表=2个3级页表条目4.3 Optimizing Page Table Access所有页表是存在main memory中的,操作系统负责构建和更新页表创建进程或更新页表时CPU会收到通知页表被用来每一次解析虚拟地址到物理地址的工作,采用的方式是page tree walking当解析虚拟地址的时候,每级都至少有一个页表在page tree walking中被使用所以每次解析虚拟地址要访问4次内存,这很慢TLB:现代CPU将虚拟地址的计算结果保存在一个叫做TLB(Tranlsation Look-Aside Buffer)的cache中。TLB是一个很小的cache,而且速度极快现代CPU提供多级TLB,级别越高尺寸越大同时越慢。也分为数据和指令两种,ITLB和DTLB。高层级TLB比如2LTLB通常是统一的。(和前一篇文章讲的cache结构类似)因为虚拟地址的offset不参与page tree walking,所以使用其余部分作为cache的tag通过软件或硬件prefetch code/data会隐式的prefetch TLB条目,如果地址是在另一个page上时4.3.1 Caveats Of Using A TLB讲了几种优化TLB cache flush的手段,不过没有讲现代CPU使用的是哪一种。个人认为这段不用太仔细读,只需要知道存在一种手段可以最少范围的flush TLB cache entry就行了。4.3.2 Influencing TLB Performance使用大页:页尺寸越大,则页表需要存储的条目就越少,则需要做的虚拟地址->物理地址翻译工作就越少,则需要TLB的条目就越少。有些x86/x86-64支持4kB、2MB、4MB的页尺寸。不过大页存在问题,给大页使用的内存区域必须是连续的。如果物理内存的管理基本单位和虚拟内存页一样大的话,浪费的内存就会变多(因为内存申请是以页为单位的,不管你用多少,都会占用1页)。2MB的页对于x86-64系统来说也还是太大了,如果要实现则必须用几个小页组成大页来实现。如果小页是4kB,那么就意味着要在物理内存中连续地分配512个小页。要做到这个比较困难,而且系统运行一段时间物理内存就会变得碎片化。Linux系统在操作系统启动时遇险分配了一块内存区域存放大页(hugetlbs文件系统),固定数量的物理页被保留给虚拟大页使用。所以大页适合在以下场景:性能优先、资源充足、不怕配置繁琐,比如数据库应用。提高虚拟页最小尺寸(前面讲的大页是可选的)也会产生问题:内存影射操作(比如加载应用程序)必须能够适配页尺寸。比页尺寸更小的映射是不允许的。一个可执行程序的各个部分,在大多数架构中,的关系是固定的。如果页尺寸变得太大,以至于超出了可执行程序所适配的大小,那么就无法加载了。看下图:$ eu-readelf -l /bin/lsProgram Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align… LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x0132ac 0x0132ac R E 0x200000 LOAD 0x0132b0 0x00000000006132b0 0x00000000006132b0 0x001a71 0x001a71 RW 0x200000…Figure 4.3: ELF Program Header Indicating Alignment Requirements这是一个x86-64可执行二进制的头,里面规定了内存对齐单位是0x200000 = 2,097,152 = 2MB,如果页尺寸比这个大就不行了。另一个使用大页的影响是减少page table tree的层级,因为offset变大了,那么剩下的留给页表的部分就变少了,那么page tree walking就更快了,那么TLB missing所要产生的工作就变少了。下面这段没有看懂:Beyond using large page sizes, it is possible to reduce the number of TLB entries needed by moving data which is used at the same time to fewer pages. This is similar to some optimizations for cache use we talked about above. Only now the alignment required is large. Given that the number of TLB entries is quite small this can be an important optimization.4.4 Impact Of Virtualization大致意思是现代虚拟化技术能够消解大部分因虚拟化导致的TLB性能损失,但是这个开销不会完全消失。 ...

March 20, 2019 · 2 min · jiezi

(笔记)CPU & Memory, Part 1: RAM

博文:https://chanjarster.github.io…原文:What every programmer should know about memory, Part 1, RAM1 Introduction如今的计算机架构中CPU和main memory的访问速度的差异是很大的,解决这一瓶颈有这么几种形式:RAM硬件设计的改善(速度和并行)Memory controller设计CPU caches给设备用的Direct memory access(DMA)2 Commodity Hardware Today大众架构Figure 2.1: Structure with Northbridge and Southbridge所有CPU通过FSB连接到北桥,北桥包含内存控制器(memory controller),连接到RAM。不同的内存类型如SRAM、DRAM有不同的内存控制器。南桥又称I/O桥,如果要访问其他系统设备,北桥必须和南桥通信。南桥连接着各种不同的bus这个架构要注意:CPU之间的所有数据通信必须经过FSB,而这个FSB也是CPU和北桥通信的bus。所有和RAM的通信都必须经过北桥RAM只有一个端口(port)CPU和挂接到南桥设备的通信则有北桥路由可以发现瓶颈:为设备去访问RAM的瓶颈。解决办法是DMA,让设备直接通过北桥访问RAM,而不需要CPU的介入。如今挂到任一bus的所有高性能设备都能利用DMA。虽然DMA减少了CPU的工作量,但是争用了北桥的带宽北桥到RAM的瓶颈。老的系统里只有一条通往所有RAM芯片的bus。现在的RAM类型要求有两条独立的bus,所以倍增了带宽(DDR2里称为channel)。北桥通过多个channel交替访问内存。多内存控制器比较贵的系统北桥自己不包含内存控制器,而是外接内存控制器:Figure 2.2: Northbridge with External Controllers在这种架构里有多个内存bus,大大增加了带宽。在并发内存访问的时候,可以同时访问不同的memory bank(我理解为就是内存条)。而这个架构的瓶颈则是北桥内部的带宽。NUMA除了使用多个内存控制器,还可以采用下面的架构增加内存带宽。做法就是把内存控制器内置在CPU里。每个CPU访问自己的本地RAM。Figure 2.3: Integrated Memory Controller这个架构同样也有缺点:因为这种系统里的所有CPU还是要能够访问所有的RAM,所以the memory is not uniform anymore (hence the name NUMA - Non-Uniform Memory Architecture - for such an architecture)。访问本地内存速度是正常的,访问别的CPU的内存就不一样了,CPU之间必须interconnect才行。在上图中CPU1访问CPU4的时候就要用到两条interconnect。2.1 RAM Types2.1.1 Static RAM访问SRAM没有延迟,但SRAM贵,容量小。Figure 2.4: 6-T Static RAM电路图就不解释了。2.2.1 Dynamic RAMFigure 2.5: 1-T Dynamic RAM电路图就不解释了。DRAM物理结构:若干RAM chip,RAM chip下有若干RAM cell,每个RAM cell的状态代表1 bit。访问DRAM有延迟(等待电容充放电),但DRAM便宜,容量大。商业机器普遍使用DRAM,DDR之类的就是DRAM。2.1.3 DRAM AccessFigure 2.7: Dynamic RAM Schematic访问DRAM的步骤:RAS(Row address selection)CAS(Column address selection)传输数据RAS和CAS都需要消耗时钟频率,如果每次都需要重新RAS-CAS则性能会低。如果一次性把一行的数据都传输,则速度很快。2.1.4 Conclusions不是所有内存都是SRAM是有原因的(成本原因)memory cell必须被单独选择才能够使用address line的数目直接影响到内存控制器、主板、DRAM module、DRAM chip的成本需要等待一段时间才能得到读、写操作的结果2.2 DRAM Access Technical Details略。2.2.4 Memory Types现代DRAM内置I/O buffer增加每次传输的数据量。Figure 2.14: DDR3 SDRAM Operation2.2.5 Conclusions假如DRAM的时钟频率为200MHz,I/O buffer每次传送4份数据(商业宣传其FSB为800MHz),你的CPU是2GHz,那么两者时钟频率则是1:10,意味着内存延迟1个时钟频率,那么CPU就要等待10个时钟频率。2.3 Other Main Memory Users网络控制器、大存储控制器,使用DMA访问内存。PCI-E卡也能通过南桥-北桥访问内存。USB也用到FSB。高DMA流量会占用FSB,导致CPU访问内存的时候等待时间变长。在NUMA架构中,可以CPU使用的内存不被DMA影响。在Section 6会详细讨论。没有独立显存的系统(会使用内存作为显寸),这种系统对于RAM的访问会很频繁,造成占用FSB带宽,影响系统性能。 ...

March 18, 2019 · 1 min · jiezi

(笔记)CPU & Memory, Part 2: CPU caches

博文:https://chanjarster.github.io…原文:What every programmer should know about memory, Part 2: CPU caches关键词:Cache prefetching、TLB cache missing、MESI protocol、Cache types(L1d、L1i、L2、L3)3.1 CPU Caches in the Big Picture内存很慢,这就是为何CPU cache存在的原因,CPU cache内置在CPU内部,SRAM。CPU cache尺寸不大。CPU cache处于CPU和内存之间,默认情况下CPU所读写的数据都存在cache中。Intel将CPU cache分为data cache和code cache,这样会有性能提升。随着CPU cache和内存的速度差异增大,在两者之间增加了更大但是更慢的CPU cache,为何不扩大原CPU cache的尺寸?答案是不经济。现代CPU core拥有三级缓存。L1d是data cache,L1i是instruction cache(code cache)。上图只是概要,现实中从CPU core到内存的数据流一路上可以通过、也可以不通过各个高层cache,这取决于CPU的设计者,这部分对于程序员是不可见的。每个处理器拥有多个core,每个core几乎拥有所有硬件资源的copy,每个core可以独立运行,除非它们用到了相同的资源。每个core有用多个thread,每个thread共享其所属处理器的所有资源,Intel的thread仅对reigster做分离,甚至这个也是有限制的,有些register依然是共享的。上面这张图:两个处理器,processors,大的灰色矩形每个处理器有一个L3 cache和L2 cache(从上往下看第一个深绿色L3 cache,第二个较浅绿色L2 cache)每个处理器有两个core(小的灰色矩形)每个core有一个L1d cache和L1i cache(两个浅绿色矩形)每个core有两个thread,红色矩形,同一个processor的所有core都共享相同的L2/L3 cache3.2 Cache Operation at High Level插播概念word:Word,数据的自然单位,CPU指令集所能处理的数据单位。在x86-64架构中,word size=64 bits=8 bytes。CPU cache中存储的条目(entry)不是word,而是cache line,如今一条cache line大小为64 bytes。每次从RAM中抓取数据的时候不仅会将目标数据抓过来,还会将其附近的数据一并抓过来,构成64 bytes大小的cache line。当一个cache line被修改了,但是还没有被写到内存(main memory),则这个cache line被标记为dirty。一旦被写到内存,则dirty标记被清除。对于多处理器系统,处理器之间会互相监视写动作,并维持以下规则:A dirty cache line is not present in any other processor’s cache.Clean copies of the same cache line can reside in arbitrarily many caches.Cache eviction类型:exclusive,当要加载新数据的时候,如果L1d已满,则需要将cache line推到L2,L2转而推到L3,最终推到main memory。优点:加载新数据的时候只需要碰L1d。缺点:eviction发生时代价逐级增高。inclusive,L1d中的所有cache line同样存在于L2中。优点:L1d eviction快,因为只需要碰L2。缺点:浪费了一些L2的空间。下表是Intel奔腾M处理访问不同组件所需的CPU周期:To WhereCyclesRegister<= 1L1d3L214Main Memory~240下图是写不同尺寸数据下的性能表现:根据经验可以推测出L1d size=2^12=4K,L2 size=2^20=1M。当数据<=4K时,正好能够放进L1d中,操作的CPU周期<10个。当数据>4K and <=1M时,会利用到L2,操作的CPU周期<75。当数据>1M时,CPU操作周期>400,则说明没有L3,此时是直接访问内存了。非常重要:下面的例子里CPU访问数据是按照以下逻辑:CPU只能从L1d cache访问数据如果L1d没有数据,则得先从L2把数据加载到L1d如果L2没有数据,则得先从main memory(RAM)加载数据也就是说如果这个数据一开始在L1d、L2都不存在,那么就得先从main memory加载到L2,然后从L2加载到L1d,最后CPU才可以访问。3.3 CPU Cache Implementation Details3.3.1 Associativity没看懂。略。3.3.2 Measurements of Cache Effectskeyword:cache prefetching、TLB cache miss测试方法是顺序读一个l的数组:struct l { struct l *n; long int pad[NPAD];};根据NPAD不同,元素的大小也不同:NPAD=0,element size=8 bytes,element间隔 0 bytesNAPD=7,element size=64 bytes,element间隔 56 bytesNPAD=15,element size=128 bytes,element间隔 120 bytesNPAD=31,element size=256 bytes,element间隔 248 bytes被测CPU L1d cache=16K、L2 cache=1M、cache line=64 bytes。Single Threaded Sequential Accesscase 1: element size=8 bytes下面是NPAD=0(element size=8 bytes,element间隔0 bytes),read 单个 element的平均时钟周期:Figure 3.10: Sequential Read Access, NPAD=0上面的Working set size是指数组的总尺寸(bytes)。可以看到就算数据尺寸超过了16K(L1d size),对每个元素的读的CPU周期也没有到达14,甚至当数据尺寸超过1M(L2 size)时也是这样,这就是因为cache prefetching的功劳:当你在读L1d cache line的时候,处理器预先从L2 cache抓取到L1d cache,当你读L1d next cache line的时候这条cache line已经准备好了。而L2也会做prefetching动作Cache prefetching是一项重要的优化手段,在使用连续的内存区域的时候,处理器会将后续数据预先加载到cache line中,也就是说当在访问当前cache line的时候,下一个cache line的数据已经在半路上了。Prefetching发生既会发生在L1中也会发生在L2中。case 2: element size >= cache line, 总尺寸 <= L2各个尺寸element的情况:NPAD=7(element size=64 bytes,element间隔56 bytes)NPAD=15,element size=128 bytes,element间隔 120 bytesNPAD=31,element size=256 bytes,element间隔 248 bytesFigure 3.11: Sequential Read for Several Sizes观察working set size <= L2,看(210~219区段):当working set size <= L1d的时候,时钟周期和NPAD=0持平。当working set size > L1d <= L2的时候,时钟周期和L2本身的周期吻合,在28左右这是为什么呢?此时prefetching没有起到作用了吗?这是因为:prefetching本身需要时钟周期顺序read array实际上就是在顺序read cache line当NPAD=0时,element size=8,就意味着你要read多次才会用光cache line,那么prefetching可以穿插在read之间进行,将next cache line准备好当NPAD=7,15,31时,element size>=cache line,那么就意味着每次read都把一条cache line用完,没有留给prefetching的时钟周期了,下一次read的时候就只能老实从L2加载,所以时钟周期在28左右。case 3: selement size >= cache line, 总尺寸 > L2还是看各个尺寸element的情况:NPAD=7(element size=64 bytes,element间隔56 bytes)NPAD=15,element size=128 bytes,element间隔 120 bytesNPAD=31,element size=256 bytes,element间隔 248 bytesFigure 3.11: Sequential Read for Several Sizes观察working set size > L2,看(219之后的区段):NPAD=7(element size=64),依然有prefetching的迹象NPAD=15(element size=128)、NPAD=31(element size=256)则没有prefetching的迹象这是因为处理器从size of the strides判断NPAD=15和31,小于prefetching window(具体后面会讲),因此没有启用prefetching。而元素大小妨碍prefetching的硬件上的原因是:prefetching无法跨过page boundaries。而NPAD=15与31的差别很大则是因为TLB cache miss。测量TLB效果TLB是用来存放virtual memory address到physical memory address的计算结果的(虚拟内存地址和物理内存地址在后面会讲)。测试NPAD=7(element size=64),每个元素按照下列两种方式排列的性能表现:On cache line,数组中的每个元素连续。也就是每次迭代需要新的cache line,每64个元素需要一个新page。On page,数组中的每个元素在独立的Page中。每次迭代需要新的cache line。Figure 3.12: TLB Influence for Sequential Read蓝色曲线看到,当数据量超过212 bytes(4K),曲线开始飙升。因此可以推测TLB的大小为4K。因为每个元素大小为64 bytes,因此可以推测TLB的entry数目为64.从虚拟内存地址计算物理内存地址,并将结果放到TLB cache中是很耗时的。main memory读取或从L2读取数据到cache line之前,必须先计算物理内存地址。可以看到越大的NPAD就越会降低TLB cache的效率。换句话说,越大的元素尺寸就越会降低TLB cache的效率。所以address translation(地址翻译)的惩罚会叠加到内存访问上,所以Figure 3.11的NPAD=31(element size=256)周期数会比其他更高,而且也比理论上访问RAM的周期高。case 4: Sequential Read and Write, NPAD=1测试NPAD=1,element size=16 bytes,顺序读与写:Follow,和之前一样是顺序读的测试结果,作为baselineInc,每次迭代对pad[0]++Addnext0,每次迭代读取下一个元素的pad[0],把值加到自己的pad[0]上Figure 3.13: Sequential Read and Write, NPAD=1按照常理来说,Addnext0应该比较慢因为它做的工组比较多,然而在某些working set size下反而比Inc要好,这是因为:Addnext0的读取下一个元素的pad[0]这个动作实际上是force prefetch。当程序读取下一个元素的pad[0]的时候,数据已经存在于cache line之中了。所以只要working set size符合能够放到L2中,Addnext0的表现和Follow一样好下面这段没看懂,也许这不重要。The “Addnext0” test runs out of L2 faster than the “Inc” test, though. It needs more data loaded from main memory. This is why the “Addnext0” test reaches the 28 cycles level for a working set size of 221 bytes. The 28 cycles level is twice as high as the 14 cycles level the “Follow” test reaches. This is easy to explain, too. Since the other two tests modify memory an L2 cache eviction to make room for new cache lines cannot simply discard the data. Instead it has to be written to memory. This means the available bandwidth on the FSB is cut in half, hence doubling the time it takes to transfer the data from main memory to L2.case 4: Sequential Read on larger L2/L3测试NPAD=15,element size=128 bytes。Figure 3.14: Advantage of Larger L2/L3 Caches最后一级cache越大,则曲线逗留于L2访问开销对应的低等级的时间越长第二个处理器在220时比第一个处理快一倍,是因为它的L3第三个处理器表现的更好则是因为它的4M L2所以缓存越大越能得到性能上的提升。Single Threaded Random Access Measurements之前已经看到处理器通过prefetching cache line到L2和L1d的方法,可以隐藏main memory的访问开销,甚至L2的访问开销。但是,只有在内存访问可预测的情况下,这才能工作良好。下图是顺序访问和随机访问的对比:Figure 3.15: Sequential vs Random Read, NPAD=0后面的没有看懂。3.3.3 Write behaviorcache应该是coherent的,cache的coherency对于userlevel 代码应该是完全透明的,内核代码除外。如果一个cache line被修改了,那么自此时间点之后的系统的结果和压根没有cache并且main memory被修改的结果是一样。有两个实现策略:Write-through:一旦cache line被写,则马上将cache line写到main memory总是保证main memory和cache保持一致,无论什么时候cache line被替换,所cache的内容可以随时丢弃优点:实现起来最简单缺点:虽然简单但是不快。如果一个程序不停的修改一个本地变量,会占用FSB带宽。Write-back:cache line被写不马上写到main memory,仅标记为dirty。当cache line在之后的某个时间点被drop,dirty标记会指导处理器把内容写到main memory绝大多数系统采用的是这个策略处理器甚至可以在驱散cache line之前,利用FSB的空闲空间存储cache line的内容。这样一来就允许清除dirty标记,当需要新空间的时候,处理器就能够直接丢弃cache line。还有另外两个策略,它们都用于地址空间的特殊区域,这些区域不由实际的RAM支持。Write-combining:Write-combining是一种首先的cache优化策略,更多的用于设备的RAM上,比如显卡。传输数据到设备的开销比访问本地RAM要大得多,所以要尽可能避免传输次数太多。如果仅因为cache line的一个word的修改而要把整个cache line都传输太浪费。因此,write-combining把多个写访问合并在一起,然后再把cache line写出去。这可以加速访问设备RAM的速度。个人插播:这个策略牺牲了一定的latency,但是提高了throughput,类似于批处理。Uncacheable:内存地址压根就不存在RAM里,这些地址一般都是硬编码的。在商业机器上,一般来说这些地址会被翻译成访问card和连接到某个总线的设备(PCIe)。这些内存不应该被缓存。3.3.4 Multi-processor support在多处理器系统和多核处理器中,对于所有不共享的cache,都会存在cache内容不一致的问题。两个处理器之间不会共享L1d、L1i、L2、L3,同一处理器的两个核之间至少不会共享L1d。提供一条能从A处理器直接访问B处理器的cache的通道是不切实际的,因为速度不够快。所以比较实际的做法是将cache内容传输到其他处理器以备不时之需。对于多核处理器也采用这种做法。那什么时候传输呢?当一个处理器需要一条cache line做读/写,但是它在其他处理器里是dirty时。那么一个处理器是如何决定一条cache line在另外一个处理器里是否dirty呢?通常来说内存访问是读,而读不会把一个cache line变成dirty。处理器每次对cache line的写访问之后都把cache line信息广播出去是不切实际的。MESI cache coherency protocol开发了MESI缓存协同协议(MESI cache coherency protocol),规定了一条cache line的状态有四种:Modified、Exclusive、Shared、Invalid。Modified: 本地处理器刚修改cache line,也意味着它是所有cache中的唯一copy。Exclusive: cache line没有被修改,但已知没有被加载到任何其他处理的cache中。Shared: cache line没有被修改,可能存在于其他处理器的cache中。Invalid: cache line无效,比如还未被使用。MESI所解决的问题和分布式缓存中数据同步的问题是一致的,好好看看,这能够带来一些启发使用这四个状态有可能有效率地实现write-back策略,同时支持多处理器并发使用read-only数据。Figure 3.18: MESI Protocol Transitions下面是四种状态变化的解读:Invalid:最开始所有的cache line都是空的,也即Invalid如果数据加载到cache line的目的是为了写,变成Modified如果数据加载到cache line的目的是为了读,若其他处理器是否也加载了相同的cache line,变成Shared如果没有,变成ExclusiveModified如果一条Modified cache line被本地处理器读or写,状态不变。如果B处理器要读A处理器的Modified cache line,则A处理器必须将内容发送给B处理器,然后状态变成Shared。发送给B处理器的数据同样也被memory controller接收和处理,然后存到main memory里。如果这一步没有做,那么状态就不能变成Shared。如果B处理器要写A处理器的Modified cache line,则A处理器要把数据传给B,然后标记为Invalid。这就是臭名昭著的Request For Ownership(RFO)(通过address bus)。在最后一级缓存执行这个操作和I->M一样,代价相对来说是比较高的对于write-through cache,则必须加上在高一级cache写新的cache line,或者写到main memory的时间,进一步增加了代价Shared如果本地处理器读一条Shared cache line,状态不变。如果本地写一条Shared cache line,则变成Modified。所有其他处理器的cache line copy变成Invalid。所以写操作必须通过RFO广播到其他处理器。如果B处理器要读A处理器的Shared cache line,状态不变。如果B处理器要写A处理器的Shared cache line,则变成Invalid,不牵涉到bus operation。ExclusiveExlusive和Shared一样除了一个区别:本地写不需要RFO。所以处理器会尽可能把多的cache line维持在Exclusive状态,而不是Shared状态。当信息不足的时候,Shared状态作为一种fallback——原文没有说明白是什么信息,猜测应该是当无法知道其他处理器是否拥有相同cache line的时候,就把它设为Shared,这样做会比较安全。E->M 比 S->M 快得多所以在多处理器系统中,除了填充cache line之外,我们还得关注RFO消息对性能的影响。只要出现了RFO消息,就会变慢。有两种场景RFO消息是必须的:一个线程从一个处理器迁移到另一个处理器,所有的cache line都必须一次性移动到新处理器同一条cache line是真的被两个处理器需要。在稍小一点的尺度上,多核处理器内部就存在这样的情况,只是代价小一点而已,RFO可能会被发送很多次。影响Coherency protocol速度的因素:RFO出现的频率,应该越低越好一次MESI状态变化必须等所有处理器都应答之后才能成功,所以最长的可能应答时间决定了coherency protocol的速度。FSB是共享资源,大多数系统所有处理器通过同一条bus连到memory controller。如果一个处理器饱和了FSB,则共享同一bus的两个或四个处理器将进一步限制每个处理器可用的带宽。就算每个处理器有自己的bus连接memory controller(Figure 2.2),那么处理器到memory module的bus还是会被共享的。在多线程/多进程程序里,总有一些synchronization的需求,这些synchronization则是使用memory实现的,所以就会有一些RFO消息。所以concurrency严重地受限于可供synchronization的有限带宽。程序应该最小化从不同处理器、不同核访问同一块内存区域的操作。Multi Threaded Measurements用之前相同的程序测试多线程的表现,采用用例中最快的线程的数据。所有的处理器共享同一个bus到memory controller,并且只有一条到memory modules的bus。case 1: Sequential Read Access, Multiple ThreadsFigure 3.19: Sequential Read Access, Multiple Threads这个测试里没有修改数据,所有cache line都是shared,没有发生RFO。但是即便如此2线程的时候有18%的性能损失,在4线程的时候则是34%。那么可能的原因就只可能是一个或两个瓶颈所造成的:处理器到memory controller的bus、memory controller到memory modules的bus。一旦working set超过L3尺寸之后,就要从main memory prefetch数据了,带宽就不够用了case 2: Sequential Increment, Multiple Threads这个测试用的是 “Sequential Read and Write, NPAD=1,Inc”,会修改内存。Figure 3.20: Sequential Increment, Multiple Threads注意图中的Y轴不是线性增加的,所以看上去很小的差异实际上差别很大。2线程依然有18%的性能损失,而4线程则有93%的性能损失,这意味4线程的时候prefetch流量核write-back流量饱和了bus。图中也可以发现只要有多个线程,L1d基本上就很低效了。L2倒不像L1d,似乎没有什么影响。这个测试修改了内存,我们预期会有很多RFO消息,但是并没有看见2、4线程相比单线程有什么性能损失。这是因为测试程序的关系。case 3: Random Addnextlast, Multiple Threads下面这张图主要是为了展现令人吃惊的高数字,在极端情况下处理list中的单个元素居然要花费1500个周期。Figure 3.21: Random Addnextlast, Multiple Threads总结case 1、2、3把case 1、2、3中的最大working set size的值总结出多线程效率:#ThreadsSeq ReadSeq IncRand Add21.691.691.5442.982.071.65Table 3.3: Efficiency for Multiple Threads这个表显示了在最大working set size,使用多线程能获得的可能的最好的加速。理论上2线程应该加速2,4线程应该加速4。好好观察这个表里的数字和理论值的差异。下面这张图显示了Rand Add测试,在不同working set size下,多线程的加速效果:Figure 3.22: Speed-Up Through ParallelismL1d尺寸的测试结果,在L2和L3范围内,加速效果基本上是线性的,一旦当尺寸超过L3时,数字开始下坠,并且2线程和4线程的数字下坠到同一点上。这也就是为什么很难看到大于4个处理器的系统使用同一个memory controller,这些系统必须采用不同的构造。不同情况下上图的数字是不一样的,这个取决于程序到底是怎么写的。在某些情况下,就算working set size能套进最后一级cache,也无法获得线性加速。但是另一方面依然有可能在大于两个线程、更大working set size的情况下获得线性加速。这个需要程序员做一些考量,后面会讲。special case: hyper-threadsHyper-Threads(有时候也被称为Symmetric Multi-Threading, SMT),是CPU实现的一项技术,同时也是一个特殊情况,因为各个线程并不能够真正的同时运行。超线程共享了CPU的所有资源除了register。各个core和CPU依然是并行运行的,但是hyper-threads不是。CPU负责hyper-threads的分时复用(time-multiplexing),当当前运行的hyper-thread发生延迟的时候,就调度令一个hyper-thread运行,而发生延迟的原因大部分都是因内存访问导致的。当程序的运行2线程在一个hyper-thread核的时候,只有在以下情况才会比单线程更有效率:2个线程的运行时间之和低于单线程版本的运行时间。这是因为当一个线程在等待内存的时候可以安排另一个线程工作,而原本这个是串形的。一个程序的运行时间大致可以用下面这个简单模型+单级cache来计算:Texe = N[(1-Fmem)Tproc + Fmem(GhitTcache + (1-Ghit)Tmiss)]N = Number of instructions. 指令数Fmem = Fraction of N that access memory. N的几分之几访问内存Ghit = Fraction of loads that hit the cache. 加载次数的几分之几命中cacheTproc = Number of cycles per instruction. 每条指令的周期数Tcache = Number of cycles for cache hit. 命中cache的周期数Tmiss = Number of cycles for cache miss. 没命中cache的周期数Texe = Execution time for program. 程序的执行时间为了使用两个线程有意义,两个线程中每个线程的执行时间必须至多是单线程代码的一半。如果把单线程和双线程放到等式的两遍,那么唯一的变量就是cache命中率。不使线程执行速度降低50%或更多(降低超过50%就比单线程慢了),然后计算所需的最小cache命中率,得到下面这张图:Figure 3.23: Minimum Cache Hit Rate For Speed-UpX轴代表了单线程代码的Ghit,Y代表了双线程代码所需的Ghit,双线程的值永远不能比单线程高,否则的话就意味着单线程可以用同样的方法改进代码了。单线程Ghit < 55%的时候,程序总是能够从多线程得到好处。绿色代表的是目标区域,如果一个线程的降速低于50%且每个线程的工作量减半,那么运行时间是有可能低于单线程的运行时间的。看上图,单线程Ghit=60%时,如果要得到好处,双线程程序必须在10%以上。如果单线程Ghit=95%,多线程则必须在80%以上,这就难了。特别地,这个问题是关于hyper-threads本身的,实际上给每个hyper-thread的cache尺寸是减半的(L1d、L2、L3都是)。两个hyper-thread使用相同的cache来加载数据。如果两个线程的工作集不重叠,那么原95%也可能减半,那么就远低于要求的80%。所以Hyper-threads只在有限范围的场景下有用。单线程下的cache命中率必须足够低,而且就算减半cache大小新的cache命中率在等式中依然能够达到目标。也只有这样使用Hyper-thread才有意义。在实践中是否能够更快取决于处理器是否能够充分交叠一个线程的等待和另一个线程的执行。而代码为了并行处理所引入的其他开销也是要考虑进去的。所以很明白的就是,如果两个hyper-threads运行的是两个完全不同的代码,那么肯定不会带来什么好处的,除非cache足够大到能够抵消因cache减半导致的cache miss率的提高。除非操作系统的工作负载由一堆在设计上真正能够从hyper-thread获益的进程组成,可能还是在BIOS里关掉hyper-thread比较好。3.3.5 Other Details现代处理器提供给进程的虚拟地址空间(virtual address space),也就是说有两种地址:虚拟的和物理的。虚拟地址不是唯一的:一个虚拟地址在不同时间可以指向不同的物理地址。不同进程的相同的地址也可能指向不同的物理地址。处理器使用虚拟地址,虚拟地址必须在Memory Management Unit(MMU)的帮助下才能翻译成物理地址。不过这个步骤是很耗时的(注:前面提到的TLB cache缓存的是虚拟->物理地址的翻译结果)。现代处理器被设计成为L1d、L1i使用虚拟地址,更高层的cache则使用物理地址。通常来说不必关心cache地址处理的细节,因为这些是不能改变的。Overflowing the cache capacity是一件坏事情;如果大多数使用的cache line都属于同一个set,则所有缓存都会提前遇到问题。第二个问题可以通过virtual address来解决,但是无法避免user-level进程使用物理地址来缓存。唯一需要记住的事情是,要尽一切可能,不要在同一个进程里把相同的物理地址位置映射到两个或更多虚拟地址。Cache replacement策略,目前使用的是LRU策略。关于cache replacement程序员没有什么事情可做。程序员能做的事情是:完全使用逻辑内存页(logical memory pages)使用尽可能大的页面大小来尽可能多样化物理地址3.4 Instruction Cache指令cache比数据cache问题更少,原因是:被执行的代码量取决于所需代码的大小,而代码大小通常取决于问题复杂度,而问题复杂度是固定的。程序的指令是由编译器生成的,编译器知道怎么生成好代码。程序流程比数据访问内存更容易预测,现代处理器非常擅长预测模式,这有助于prefetching代码总是具有良好的空间、时间局部性。CPU核心和cache(甚至第一级cache)的速度差异在增加。CPU已被流水线化,所谓流水线指一条指令的执行是分阶段的。首先指令被解码,参数被准备,最后被执行。有时候这个pipeline会很长,长pipeline就意味着如果pipeline停止(比如一条指令的执行被中断了),那它得花一些时间才能重新找回速度。pipeline停止总是会发生的,比如无法正确预测下一条指令,或者花太长时间加载下一条指令(比如从内存里加载)。现代CPU设计师花费了大量时间和芯片资产在分支预测上,为了尽可能不频繁的发生pipeline停止。3.4.1 Self Modifying Code早些时候为了降低内存使用(内存那个时候很贵),人们使用一种叫做Self Modifing Code(SMC)的技术来减少程序数据的尺寸。不过现在应该避免SMC,因为如果处理器加载一条指令到流水线中,而这条指令在却又被修改了,那么整个工作就要从头来过。所以现在到处理器假设code pages是不可变的,所以L1i没有使用MESI,而是使用更简单的SI3.5 Cache Miss Factors我们已经看到内存访问cache miss导致的开销极具增大,但是有时候这个问题是无法避免的,所以理解实际的开销以及如何缓解这个问题是很重要的。3.5.1 Cache and Memory Bandwidth测试程序使用x86和x86-64处理器的SSE指令每次加载16 bytes,working set size从1K到512M,测试的是每个周期能够加载/存储多少个bytes。Figure 3.24: Pentium 4 Bandwidth这个图是64-bit Intel Netburst处理器的测试结果。先看读的测试可以看到:当working set size在L1d以内的时候,16 bytes/cycle当L1d不够用的时候,性能下降很快,6 bytes/cycle到218的时候又下降了一小段是因为DTLB耗尽了,意思是对每个新page需要额外工作。这个测试是顺序读的,很利于prefetching,而在任何working set size下都能够达到5.3/cycle,记住这些数字,它们是上限,因为真实程序永远无法达到这个值。在看写和copy的测试:就算是最小的working set size,也不超过4 bytes/cycle。这说明Netburst处理器使用的Write-through策略,L1d的速度显然受制于L2的速度。copy测试是把一块内存区域copy到另一个不重叠的内存区域,因为上述原因它的结果也没有显著变差。当L2不够用的时候,下降到0.5 bytes/cycle。下面是两个线程分别钉在同一核心的两个Hyper-thread上的测试情况:Figure 3.25: P4 Bandwidth with 2 Hyper-Threads这个结果符合预期,因为Hyper-thread除了不共享register之外,其他资源都是共享的,cache和带宽都被减半了。这意味着就算一个线程在在等待内存的时候可以让另一个线程执行,但是另一个线程实际上也在等待,所以并没有什么区别。下图是Intel Core2处理器的测试结果(对比Figure 3.24):Figure 3.26: Core 2 BandwidthCore 2 L2是P4 L2的四倍。这延迟了write和copy测试的性能下跌。read测试在所有的working set size里都维持在16 bytes/cycle左右,在220处下跌了一点点是因为DTLB放不下working set。能够有这么好的性能表现不仅意味着处理器能够及时地prefetching和传输数据,也意味着数据是被prefetch到L1d的。看write和copy的表现,Core 2处理器的L1d cache没有采用Write-through策略,只有当必要的时候L1d才会evict,所以能够获得和read接近的表现。当L1d不够用的时候,性能开始下降。当L2不够用的时候再下跌很多。下图是Intel Core2处理器双线程跑在两个核心上:Figure 3.27: Core 2 Bandwidth with 2 Threads这个测试两个线程分别跑在两个核心上,两个线程访问相同的内存,没有完美地同步。read性能和Figure 3.26没有什么差别。看write和copy的表现,有意思的是在working set能够放进L1d的表现和直接从main memory读取的表现一样。两个线程访问相同的内存区域,针对cache line的RFO消息肯定会被发出。这里可以看到一个问题,RFO的处理速度没有L2快。当L1d不够用的时候,会将修改的cache line flush到共享的L2,这是性能有显著提升是因为L1d中的数据被flush到L2,那就没有RFO了。而且因为两个核心共享FSB,每个核心只拥有一半的FSB带宽,意味着每个线程的性能大约是单线程的一半。厂商的不同版本CPU和不同厂商的CPU的表现都是不同的。原文里后面比较了AMD Opteron处理器,这里就不写了。3.2.5 Critical Word Load数据是一block为单位从main memory传输到cache line的,一个block大小为64 bits(记得前面说的word也是64 bits),一条cache line的大小是64/128 bytes,所以填满一个cache line要传输8/16次。后面没有看懂,大致意思是Critical word是cache line中的关键word,程序要读到它之后才能继续运行,但是如果critical word不是cache line的第一个,那么就得等前面的word都加载完了之后才行。blah blah blah,不过这个理解可能也是不对的。3.5.3 Cache Placementcache和hyper-thread、core、处理器的关系是程序员无法控制的,但是程序员可以决定线程在哪里执行,所以cache和CPU是如何关联的就显得重要了。后面没仔细看,大致讲了由于不同的CPU架构,决定如何调度线程是比较复杂的。3.5.4 FSB Influence不细讲了,对比了667MHz DDR2和800 MHz DDR2(20%的提升),测试下来性能有18%的提升接近理论值(20%)。Figure 3.32: Influence of FSB Speed所以更快的FSB的确能够带来好处。要注意CPU可能支持更高的FSB,但是主板/北桥可能不支持的情况。 ...

March 18, 2019 · 4 min · jiezi

一文掌握 Linux 性能分析之 CPU 篇

PS:欢迎大家关注我的公众号:CloudDeveloper(ID: cloud_dev),专注技术分享,努力打造干货分享平台,二维码在文末可以扫。平常工作会涉及到一些 Linux 性能分析的问题,因此决定总结一下常用的一些性能分析手段,仅供参考。说到性能分析,基本上就是 CPU、内存、磁盘 IO 以及网络这几个部分,本文先来看 CPU 这个部分。CPU 基础信息进行性能分析之前,首先得知道 CPU 有哪些信息,可以通过以下方法查看 CPU 配置信息。lscpu在 Linux 下,类似 lsxxx 这样的命令都是用来查看基本信息的,如 ls 查看当前目录文件信息,lscpu 就用来查看 CPU 信息,类似还有 lspci 查看 PCI 信息。可以看到我的机器配置很低,1 核 2.5GHz(在阿里云买的最低配的服务器)。/proc/cpuinfo/proc 目录是内核透传出来给用户态使用的,里面记录着很多信息文件,比如还有内存文件 meminfo 等。可以使用 cat /proc/cpuinfo 查看 CPU 信息。这里显示的信息可以具体到每个逻辑核上,由于我只有一个核,所以只显示一组信息。dmidecode这个命令是用来获取 DMI(Desktop Management Interface)硬件信息的,包括 BIOS、系统、主板、处理器、内存、缓存等等。对于 CPU 信息,可以使用 dmidecode -t processor 来查看。CPU 使用情况分析知道了 CPU 的基本信息,我们就可以使用另外的命令来对 CPU 的使用情况分析一通了。top相信大家对下面这玩意不陌生,Windows 的任务管理器,top 的作用和它是一样的。top 显示的效果虽说不像它这么华丽,但已然让人惊呼他俩怎么长得这么像。我们重点关注这么几个字段:load average:三个数字分别表示最近 1 分钟,5 分钟和 15 分钟的负责,数值越大负载越重。一般要求不超过核数,比如对于单核情况要 < 1。如果机器长期处于高于核数的情况,说明机器 CPU 消耗严重了。%Cpu(s):表示当前 CPU 的使用情况,如果要查看所有核(逻辑核)的使用情况,可以按下数字 “1” 查看。这里有几个参数,表示如下:- us 用户空间占用 CPU 时间比例- sy 系统占用 CPU 时间比例- ni 用户空间改变过优先级的进程占用 CPU 时间比例- id CPU 空闲时间比- wa IO等待时间比(IO等待高时,可能是磁盘性能有问题了)- hi 硬件中断- si 软件中断- st steal time每个进程的使用情况:这里可以罗列每个进程的使用情况,包括内存和 CPU 的,如果要看某个具体的进程,可以使用 top -p pid 查看。和 top 一样的还有一个改进版的工具:htop,功能和 top 一样的,只不过比 top 表现更炫酷,使用更方便,可以看下它的效果。ps可能很多人会忽略这个命令,觉得这不是查看进程状态信息的吗,其实非也,这个命令配合它的参数能显示很多功能。比如 ps aux。如果配合 watch,可以达到跟 top 一样的效果,如:watch -n 1 “ps aux”(-n 1 表示每隔 1s 更新一次)vmstat这个命令基本能看出当前机器的运行状态和问题,非常强大。可以使用 vmstat n 后面跟一个数字,表示每隔 ns 显示系统的状态,信息包括 CPU、内存和 IO 等。更多更详细的内容大家可以关注我的公众号查看,那里的阅读体验还更好一些。PS:更多干货可以关注我的微信公众号:CloudDeveloper(ID: cloud_dev),坚持分享干货。 ...

March 12, 2019 · 1 min · jiezi

Dubbo Mesh 在闲鱼生产环境中的落地实践

本文作者至简曾在 2018 QCon 上海站以《Service Mesh 的本质、价值和应用探索》为题做了一次分享,其中谈到了 Dubbo Mesh 的整体发展思路是“借力开源、反哺开源”,也讲到了 Service Mesh 在阿里巴巴的发路径将经历以下三大阶段:撬动做透价值渗透实现技术换代Dubbo Mesh 在闲鱼生产环境的落地,分享的是以多语言为撬动点的阶段性总结。文章首发于「QCon」,阿里巴巴中间件授权转载。闲鱼场景的特点闲鱼采用的编程语言是 Dart,思路是通过 Flutter 和 Dart 实现 iOS、Android 两个客户端以及 Dart 服务端,以“三端一体”的思路去探索多端融合的高效软件开发模式。更多细节请参考作者同事陈新新在 2018 QCon 上海站的主题分享《Flutter & Dart 三端一体化开发》。本文将关注三端中的 Dart 服务端上运用 Dubbo Mesh 去解耦 Dubbo RPC 框架的初步实践成果。Dart 服务端是一个服务调用胶水层,收到来自接入网关发来的 HTTP 请求后,通过 C++ SDK 调用集团广泛提供的 Dubbo 服务完成业务逻辑处理后返回结果。然而,C++ SDK 的功能与 Java 的存在一定的差距,比如缺失限流降级等对于保障大促稳定性很重要的功能。从长远发展的角度,闲鱼团队希望通过 Dubbo Mesh 能屏蔽或简化不同技术栈使用中间件(比如,RPC、限流降级、监控、配置管理等)的复杂性。这一诉求的由来,是闲鱼团队通过几个月的实践,发现在 Dart 语言中通过 C++ SDK 逐个接入不同中间件存在定制和维护成本高的问题。值得说明,所谓的“定制”是因为 C++ SDK 的能力弱于 Java SDK 而要做补齐所致。Dart 服务端自身的业务逻辑很轻且在一些场景下需要调用 20 多次 Dubbo 服务,这对于 Dubbo Mesh 的技术挑战会显得更大。在 Dubbo Mesh 还没在生产环境落地过而缺乏第一手数据的情形下,其性能是否完全满足业务的要求是大家普遍关心的。架构与实现图中的虚框代表了一个Pouch容器(也可以是一台物理机或虚拟机)。左边两个容器部署了 Dubbo Mesh,剩下最右边的则没有。目前 Dubbo Mesh 主要包含 Bonder、Pilot、Envoy 三个进程,以及被轻量化的 Thin SDK。其中:Envoy 承担了数据平面的角色,所有 mesh 流量将由它完成服务发现与路由而中转。Envoy 由 Lyft 初创且目前成为了 CNCF 的毕业项目,我们在之上增加了对 Dubbo 协议的支持,并将之反哺到了开源社区(还有不少代码在等待社区 review 通过后才能进到 GitHub 的代码仓库)。Pilot 和 Bonder 共同承担控制平面的角色,实现服务注册、进程拉起与保活、集群信息和配置推送等功能。Pilot 进程的代码源于开源 Istio 的 pilot-discovery 组件,我们针对阿里巴巴集团环境做了一定的改造(比如,与Nacos进行适配去访问服务注册中心),且采用下沉到应用机器的方式进行部署,这一点与开源的集群化部署很不一样。背后的思考是,Pilot 的集群化部署对于大规模集群信息的同步是非常大的一个挑战,今天开源的 Istio 并不具备这一能力,未来需要 Nacos 团队对之进行增强,在没有完全准备好前通过下沉部署的方式能加速 Service Mesh 的探索历程。Thin SDK 是 Fat SDK 经过裁剪后只保留了对 Dubbo 协议进行编解码的能力。为了容灾,当 Thin SDK 位于 Consumer 侧时增加了一条容灾通道,细节将在文后做进一步展开。数据链路全部采用单条 TCP 长连接,这一点与非 mesh 场景是一致的。Pilot 与 Envoy 两进程间采用的是 gRPC/xDS 协议进行通讯。图中同时示例了 mesh 下的 Consumer 能同时调用 mesh 下的服务(图中以 www.mesh.com 域名做示例)和非 mesh 下的服务(图中以 www.non-mesh.com 域名做示例)。闲鱼落地的场景为了避免对 20 多个依赖服务进行改造,流量走的是 mesh 下的 Consumer 调用非 mesh 下的 Provider 这一形式,读者可以理解为图中最左边的容器部署的是 Dart 服务端,它将调用图中最右边容器所提供的服务去实现业务逻辑。容灾从 Dubbo Mesh 下的 Provider 角度,由于通常是集群化部署的,当一个 Provider 出现问题(无论是 mesh 组件引起的,还是 Provider 自身导致的)而使服务无法调通时,Consumer 侧的 Envoy 所实现的重试机制会将服务请求转发到其他 Provider。换句话说,集群化部署的 Provider 天然具备一定的容灾能力,在 mesh 场景下无需特别处理。站在 Dubbo Mesh 的 Consumer 立场,如果完全依赖 mesh 链路去调用 Provider,当 mesh 链路出现问题时则会导致所有服务都调不通,这往往会引发业务可用性问题。为此,Thin SDK 中提供了一个直连 Provider 的机制,只不过实现方式比 Fat SDK 轻量了许多。Thin SDK 会定期从 Envoy 的 Admin 接口获取所依赖服务的 Provider 的 IP 列表,以备检测到 mesh 链路存在问题时用于直连。比如,针对每一个依赖的服务获取最多 10 个 Provider 的 IP 地址,当 mesh 链路不通时以 round robin 算法向这些 Provider 直接发起调用。由于容灾是针对 mesh 链路的短暂失败而准备的,所以 IP 地址的多少并不是一个非常关键的点。Thin SDK 检测 mesh 链路的异常大致有如下场景:与 Envoy 的长连接出现中断,这是 Envoy 发生 crash 所致。所发起的服务调用收到 No Route Found、No Healthy Upstream 等错误响应。优化在闲鱼落地 Dubbo Mesh 的初期我们走了一个“弯路”。具体说来,最开始为了快速落地而采用了 Dubbo over HTTP 1.1/2 的模式,也即,将 Dubbo 协议封装在 HTTP 1.1/2 的消息体中完成服务调用。这一方案虽然能很好地享受 Envoy 已完整支持 HTTP 1.1/2 协议而带来的开发工作量少的好处,但性能测试表明其资源开销并不符合大家的预期。体现于,不仅 Consumer 侧使用 mesh 后带来更高的 CPU 开销,Provider 侧也因为要提供通过 HTTP 1.1/2 进行调用的能力而导致多出 20% 的 CPU 开销且存在改造工作。最终,我们回到让 Envoy 原生支持 Dubbo 协议的道路上来。Envoy 支持 Dubbo 协议经历了两大阶段。第一个阶段 Envoy 与上游的通讯并没有采用单条长连接,使得 Provider 的 CPU 开销因为多连接而存在不可忽视的递增。第二个阶段则完全采用单条长连接,通过多路复用的模式去除了前一阶段给 Provider 所带去的额外 CPU 开销。Dubbo Mesh 在闲鱼预发环境上线进行性能与功能验证时,我们意外地发现,Istio 原生 Pilot 的实现会将全量集群信息都推送给处于 Consumer 侧的 Envoy(Provider 侧没有这一问题),导致 Pilot 自身的 CPU 开销过大,而频繁的全量集群信息推送也使得 Envoy 不时会出现 CPU 负荷毛刺并遭受没有必要的内存开销。为此,我们针对这一问题做了集群信息按需加载的重大改造,这一优化对于更大规模与范围下运用 Dubbo Mesh 具有非常重要的意义。优化的大致思路是:Thin SDK 提供一个 API 供 Consumer 的应用在初始化时调用,周知其所需调用的服务列表。Thin SDK 通过 HTTP API 将所依赖的服务列表告诉 Bonder,Bonder 将之保存到本地文件。Envoy 启动时读取 Bonder 所保存的服务列表文件,将之当作元信息转给 Pilot。Pilot 向 Nacos 只订阅服务列表中的集群信息更新消息且只将这些消息推送给 Envoy。监控可观测性(observability)是 Service Mesh 非常重要的内容,在服务调用链路上插入了 Envoy 的情形下,愈加需要通过更强的监控措施去治理其上的所有微服务。Dubbo Mesh 的监控方案并没有使用 Istio/Mixer 这样的设计,而是沿用了阿里巴巴集团内部的方式,即信息由各进程以日志的形式输出,然后通过日志采集程序将之送到指定的服务端进行后期加工并最终展示于控制台。目前 Dubbo Mesh 通过 EagleEye 去跟踪调用链,通过ARMS去展示其他的监控信息。性能评估为了评估 Dubbo Mesh 的性能能否满足闲鱼业务的需要,我们设计了如下图所示的性能比对测试方案。其中:测试机器是阿里巴巴集团生产环境中的 3 台 4 核 8G 内存的 Pouch 容器。蓝色方框代表的是进程。测试数据全部从部署了 DartServer 和 Envoy 两进程的测试机 2 上获得。性能数据分别在非 mesh(图中红色数据流)和 mesh(图中蓝色数据流)两个场景下获得。显然,Mesh 场景下的服务流量多了 Envoy 进程所带来的一跳。DartServer 收到来自施压的 Loader 进程所发来的一个请求后,将发出 21 次到 Provider 进程的 RPC 调用。在评估 Dubbo Mesh 的性能时,这 21 次是串行发出的(下文列出的测试数据是在这一情形下收集的),实际闲鱼生产环境上线时考虑了进行并行发送去进一步降低整体调用时延(即便没有 mesh 时,闲鱼的业务也是这样实现的)。Provider 进程端并没有部署 Envoy 进程。这省去了初期引入 Dubbo Mesh 对 Provider 端的改造成本,降低了落地的工作量和难度。设计测试方案时,我们与闲鱼的同学共创了如何回答打算运用 Dubbo Mesh 的业务方一定会问的问题,即“使用 Dubbo Mesh 后对 RT(Response Time)和 CPU 负荷的影响有多大”。背后的动机是,业务方希望通过 RT 这一指标去了解 Dubbo Mesh 对用户体验的影响,基于 CPU 负荷的增长去掌握运用新技术所引发的成本。面对这一问题通常的回答是“在某某 QPS 下,RT 增加了 x%,CPU 负荷增加了 y%”,但这样的回答如果不进行具体测试是无法给出的(会出现“鸡和蛋的问题”)。因为每个业务的天然不同使得一个完整请求的 RT 会存在很大的差别(从几毫秒到几百毫秒),而实现业务逻辑所需的计算量又最终决定了机器的 CPU 负荷水平。基于此,我们设计的测试方案在于评估引入 Dubbo Mesh 后,每经过一跳 Envoy 所引入的 RT 和 CPU 增量。当这一数据出来后,业务方完全可以基于自己业务的现有数据去计算出引入 Dubbo Mesh 后的而掌握大致的影响情况。显然,背后的逻辑假设是“Envoy 对于每个 Dubbo 服务调用的计算量是一样的”,事实也确实如此。测试数据以下是 Loader 发出的请求在并发度为 100 的情形下所采集的数据。表中:Envoy 的 QPS 是 Loader 的 21 倍,原因在上面测试方案部分有交代。“单跳”的数据是从“21 跳合计”直接除以 21 所得,其严谨性值得商榷,但用于初步评估仍具参考价值(有数据比没有数据强)。“整机负荷”代表了在 mesh 场景下测试机器 2 上 DartServer 和 Envoy 两进程的 CPU 开销总和。测试表明,CPU 负荷高时 Envoy 带来的单跳 RT 增幅更大(比如表中 Loader 的 QPS 是 480 时)。给出整机负荷是为了提醒读者关注引入 mesh 前业务的正常单机水位,以便更为客观地评估运用 Dubbo Mesh 将带来的潜在影响。“CPU 负荷增幅”是指 CPU 增加的幅度。由于测试机是 4 核的,所以整机的 CPU 负荷是 400。从表中数据来看,随着机器整体负荷的增加“CPU 负荷增幅”在高段存在波动,这与 RT 在高段的持续增大存在相关,从 RT 在整体测试中完全符合线性增长来看整体数据合理。当然, 后面值得深入研究数据背后的隐藏技术细节以便深入优化。线上数据Dubbo Mesh 正式生产环境上线后,我们通过对上线前后的某接口的 RT 数据进行了全天的比对,以便大致掌握 mesh 化后的影响。2019-01-14 该接口全面切成了走 Dubbo Mesh,我们取的是 2019-01-20 日的数据。图中蓝色是 mesh 化后的 RT 表现(RT 均值 3.3),而橙色是 mesh 化前的 RT 表现(RT 均值 3.27,取的是 2019-01-13 的数据)。由于线上每天的环境都有所不同,要做绝对的比较并不可能。但通过上面的比较不难看出,mesh 化前后对于整体 RT 的影响相当的小。当整体 RT 小于 5 毫秒是如此,如果整体 RT 是几十、几百毫秒则影响就更小。为了帮助更全面地看待业务流量的波动特点,下面分别列出了两天非 mesh(2019-01-06 和 2019-01-13)和两天 mesh(2019-01-20 和 2019-01-23)的比对数据。总之,生产环境上的数据表现与前面性能评估方案下所获得的测试数据能很好地吻合。洞见Dubbo Mesh 在闲鱼生产环境的落地实践让我们收获了如下的洞见:服务发现的时效性是 Service Mesh 技术的首要关键。 以集群方式提供服务的情形下(这是分布式应用的常态),因为应用发布而导致集群中机器状态的变更如何及时准确地推送到数据平面是极具挑战的问题。对于阿里巴巴集团来说,这是 Nacos 团队致力于解决的问题。开源版本的 Istio 能否在生产环境中运用于大规模分布式应用也首先取决于这一能力。频繁的集群信息推送,将给控制平面和数据平面都带去负荷扰动,如何通过技术手段控制好扰动是需要特别关注的,对于数据平面来说编程语言的“确定性”(比如,没有 VM、没有 GC)在其中将起到不可忽视的作用。数据平面的软件实现最大程度地减少内存分配与释放将显著地改善性能。有两大举措可以考虑:逻辑与数据相分离。 以在 Envoy 中实现 Dubbo 协议为例,Envoy 每收到一个 RPC 请求都会动态地创建 fitler 去处理,一旦实现逻辑与数据相分离,filter 的创建对于每一个 worker 线程有且只有一次,通过这一个 filter 去处理所有的 RPC 请求。使用内存池。 Envoy 的实现中基本没有用到内存池,如果采用内存池对分配出来的各种 bufffer 通过链表进行缓存,这将省去大量的内存分配与释放而改善性能。再则,对于处理一个 RPC 请求而多次分散分配的动作整合成集中一次性分配也是值得运用的优化技巧。数据平面的 runtime profiling 是关键技术。 Service Mesh 虽然对业务代码没有侵入性,但对服务流量具有侵入性,如何在出现业务毛刺的情形下,快速地通过 runtime profiling 去发现问题或自证清白是非常值得关注的点。心得一年不到的探索旅程,让团队更加笃定“借力开源,反哺开源”的发展思路。随着对 Istio 和 Envoy 实现细节的更多掌握,团队很强列地感受到了走“站在巨人的肩膀上”发展的道路少走了很多弯路,除了快速跟进业界的发展步伐与思路,还将省下精力去做更有价值的事和创新。此外,Istio 和 Envoy 两个开源项目的工程质量都很高,单元测试等质量保证手段是日常开发工作中的基础环节,而我们也完全采纳了这些实践。比如,内部搭建了 CI 环境、每次代码提交将自动触发单元测试、代码经过 code review 并完成单元测试才能入库、自动化性能测试等。展望在 2019 年接下来的日子,我们将着手:与 Sentinel 团队形成合力,将 Sentinel 的能力纳入到 Dubbo Mesh 中补全对 HTTP 和 Dubbo 协议的限流、降级和熔断能力。在阿里巴巴集团大范围 Kubernetes(Sigma 3.1)落地的背景下,与兄弟团队探索更加优雅的服务流量透明拦截技术方案。迎合 Serverless 的技术发展趋势,深化通过 Dubbo Mesh 更好地轻量化应用,以及基于 Dubbo Mesh 对服务流量的天然敏感性去更好地实现 auto-scaling。在产品的易用性和工程效率方面踏实进取。未来,我们将及时与读者分享阿里巴巴集团在 Service Mesh 这一新技术领域的探索成果,也期待与大家有更多的互动交流。本文作者:至简,阿里巴巴中间件高级技术专家,是阿里巴巴集团 Service Mesh 方向的重要参与者和推动者。关于 Dubbo Mesh 的首次公开分享本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 11, 2019 · 3 min · jiezi

线程很闲,cpu很忙

前两天跟一位同事讨论问题。他坚持他的线程设计,“如果一段时间内没有工作,比如20秒内没有请求,才让线程进入idle状态(wait),其它时候线程处于活跃状态”。这样设计是有问题的,因为在这个情况里线程在工作状态的时候也不是100%满负荷的,而是间隙性的处理一个一个工作,所以在线程处理一个工作后就应该让他进入wait状态,下一个工作来了再进入活跃状态,而不是让线程处于一段活跃时间。会造成线程空跑,占有cpu过高,带来性能和功耗问题。在多核cpu上也是一样,会占住某个核心,这样设计有问题。这样的情况用top -H -m 10就可以看到这个线程一直处在前边。画了个图说明。做线程不要太自私,占住所有cpu时间,cpu很忙它要照顾多个线程。最后说服了他。

February 22, 2019 · 1 min · jiezi

《深入理解计算机系统》读书笔记:5.5 vs 5.6

0x00 前言没有看过或者没有看到这里的小伙伴们,看到这个标题一定觉得摸不着头脑。那这里就先来解释一下背景。double poly(double a[], double x, long degree){ long i; double result = a[0]; double xpwr = x; for (i = 1; i <= degree; i++) { result += a[i] * xpwr; xpwr = x * xpwr; } return result;}double polyh(double a[], double x, long degree){ long i; double result = a[degree]; for (i = degree; i >= 0; i–) { result = a[i] + x * result; } return result;}这是 CSAPP 的两道题,每一题是一段代码,这两段代码实现了同一个功能。这两道题有一个共同的问题,比较这两段代码的性能。0x01 答案这里的答案是,poly 的性能比 polyh 的性能要高。poly 的 CPE 是 5,而 polyh 的 CPE 是 8。这就显得很尴尬了,我原以为两个函数的 CPE 都是 8。0x02 我的猜想polyh 的 CPE 是 8 我没有疑问,因为这个循环里的操作是无法并行的,也就是下一次迭代会依赖上一次迭代产生的结果。所以,CPE = 5 + 3,5 是浮点数乘法的延迟下届,3 是浮点数加法的延迟下界。poly 的 CPE 我原本认为也是 8,两个乘法是可以并行的,但是这个加法的是依赖于第一个乘法的值,无法并行,所以 CPE = 5 + 3 = 8。0x03 指令集并行和流水线上面的是我的猜想,所以我认为这里的答案是它们的 CPE 是相同的,性能也是相同的。但是如前面所写,答案并不是这样的。于是,我把之前看的东西都翻出来想了一下,真的不是这样的。现代 CPU 是有一个流水线的概念的。什么是流水线呢,想象一下汽车车间,我们造一辆汽车,是分成了很多道工序的,比如装配发动机、装车门、轮子等等。现代 CPU 也是类似的,我们看到的一条指令,在执行的时候,经历了一长串的流水线,导致了指令真正的执行顺序和我们看到的可能是不一样的,但是由于现代出来的这种机制,可以确保最后的结果是和我们看到的是一样的。0x04 解释poly 函数,在执行的时候,由于有两个浮点数乘法单元,所以 a[i] * xpwr 和 xpwr = x * xpwr 可以并行执行。而 a[i] * xpwr 可以通过流水线的数据转移,让这个加法 result + a[i] * xpwr 可以在下一次迭代的时候执行,因为每次迭代的时候,两个乘法都不会依赖 result 这个结果。这样,加法和乘法可以并行执行。浮点乘法的延迟下界是 5,浮点加法的延迟下界是 3,所以浮点乘法是关键路径,CPE 也自然就是 5 了。再来看看 polyh 函数。这个函数的循环里只有一个浮点乘法运算和一个浮点加法运算。先来看看浮点乘法运算,x * result,很显然,每一次乘法都需要依赖上一次迭代的结果,导致了加法无法和乘法并行执行。于是,CPE 就成了 5 + 3 = 8 了。0x05 最后这个例子,我觉得很有趣,因为它涉及到了一个流水线的细节。同时,也说明了,并不是操作少的代码,效率就高。本文为作者自己读书总结的文章,由于作者的水平限制,难免会有错误,欢迎大家指正,感激不尽。0x06 参考文献《深入理解计算机系统(第 3 版)》第 4、5 章 ...

February 19, 2019 · 1 min · jiezi

CPU优化建议使用cpupower设置CPU Performance模式

前言CPU动态节能技术用于降低服务器功耗,通过选择系统空闲状态不同的电源管理策 略,可以实现不同程度降低服务器功耗,更低的功耗策略意味着CPU唤醒更慢对性能 影响更大。对于对时延和性能要求高的应用,建议关闭CPU的动态调节功能,禁止 CPU休眠,并把CPU频率固定到最高。通常建议在服务器BIOS中修改电源管理为Performance,如果发现CPU模式为conservative或者powersave,可以使用cpupower设置CPU Performance模式,效果也是相当显著的。CPU优化建议使用cpupower设置CPU Performance模式更新历史2019年01月18日 - 初稿阅读原文 - https://wsgzao.github.io/post…扩展阅读CPU frequency scaling - https://wiki.archlinux.org/in...cpufreq的五种模式cpufreq是一个动态调整cpu频率的模块,系统启动时生成一个文件夹/sys/devices/system/cpu/cpu0/cpufreq/,里面有几个文件,其中scaling_min_freq代表最低频率,scaling_max_freq代表最高频率,scalin_governor代表cpu频率调整模式,用它来控制CPU频率。cd /sys/devices/system/cpu/cpu0/cpufreq/affected_cpusbios_limitcpuinfo_cur_freqcpuinfo_max_freqcpuinfo_min_freqcpuinfo_transition_latencyfreqdomain_cpusrelated_cpusscaling_available_frequenciesscaling_available_governorsscaling_cur_freqscaling_driverscaling_governorscaling_max_freqscaling_min_freqscaling_setspeed# 查看当前的调节器cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governorconservative# 查看频率信息cpupower frequency-infoanalyzing CPU 0: driver: acpi-cpufreq CPUs which run at the same hardware frequency: 0 CPUs which need to have their frequency coordinated by software: 0 maximum transition latency: 10.0 us hardware limits: 800 MHz - 2.10 GHz available frequency steps: 2.10 GHz, 2.10 GHz, 2.00 GHz, 1.90 GHz, 1.80 GHz, 1.70 GHz, 1.60 GHz, 1.50 GHz, 1.40 GHz, 1.30 GHz, 1.20 GHz, 1.10 GHz, 1000 MHz, 900 MHz, 800 MHz available cpufreq governors: conservative userspace powersave ondemand performance current policy: frequency should be within 800 MHz and 2.10 GHz. The governor “performance” may decide which speed to use within this range. current CPU frequency: Unable to call hardware current CPU frequency: 2.10 GHz (asserted by call to kernel) boost state support: Supported: yes Active: yesperformance: 顾名思义只注重效率,将CPU频率固定工作在其支持的最高运行频率上,而不动态调节。Userspace:最早的cpufreq子系统通过userspace governor为用户提供了这种灵活性。系统将变频策略的决策权交给了用户态应用程序,并提供了相应的接口供用户态应用程序调节CPU 运行频率使用。也就是长期以来都在用的那个模式。可以通过手动编辑配置文件进行配置powersave: 将CPU频率设置为最低的所谓“省电”模式,CPU会固定工作在其支持的最低运行频率上。因此这两种governors 都属于静态governor,即在使用它们时CPU 的运行频率不会根据系统运行时负载的变化动态作出调整。这两种governors 对应的是两种极端的应用场景,使用performance governor 是对系统高性能的最大追求,而使用powersave governor 则是对系统低功耗的最大追求。ondemand: 按需快速动态调整CPU频率, 一有cpu计算量的任务,就会立即达到最大频率运行,等执行完毕就立即回到最低频率;ondemand:userspace是内核态的检测,用户态调整,效率低。而ondemand正是人们长期以来希望看到的一个完全在内核态下工作并且能够以更加细粒度的时间间隔对系统负载情况进行采样分析的governor。 在 ondemand governor 监测到系统负载超过 up_threshold 所设定的百分比时,说明用户当前需要 CPU 提供更强大的处理能力,因此 ondemand governor 会将CPU设置在最高频率上运行。但是当 ondemand governor 监测到系统负载下降,可以降低 CPU 的运行频率时,到底应该降低到哪个频率呢? ondemand governor 的最初实现是在可选的频率范围内调低至下一个可用频率,例如 CPU 支持三个可选频率,分别为 1.67GHz、1.33GHz 和 1GHz ,如果 CPU 运行在 1.67GHz 时 ondemand governor 发现可以降低运行频率,那么 1.33GHz 将被选作降频的目标频率。conservative: 与ondemand不同,平滑地调整CPU频率,频率的升降是渐变式的,会自动在频率上下限调整,和ondemand的区别在于它会按需分配频率,而不是一味追求最高频率;cpupower设置performance从conservative或者powersave切换到performance的效果还是杠杠的# CentOS 安装 kernel-toolsyum install kernel-tools# Ubuntu 安装 CPU 模式无图形化切换器apt install cpufrequtils# cpupower设置performancecpupower frequency-set -g performance# 查看当前的调节器cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governorperformance ...

January 18, 2019 · 1 min · jiezi

分析core,是从案发现场,推导案发经过

分析core不是一件容易的事情。试想,一个系统运行了很长一段时间,在这段时间里,系统会积累大量正常、甚至不正常的状态。这个时候如果系统突然出现了一个问题,那这个问题十有八九跟长时间积累下来的状态有关系。分析core,就是分析出问题时,系统产生的“快照”,追溯历史,找出问题发生源头。这有点像是从案发现场,推导案发经过一样。soft lockup!今天这个“案件”,我们从soft lockup说起。soft lockup是内核实现的夯机自我诊断功能。这个功能的实现,和线程的优先级有关系。这里我们假设有三个线程A、B、和C。他们的优先级关系是A<B<C。这意味着C优先于B执行,B优先于A执行。这个优先级关系,如果倒过来叙述,就会产生一个规则:如果C不能执行,那么B也没有办法执行,如果B不能执行,那基本上A也没法执行。soft lockup实际上就是对这个规则的实现:soft lockup使用一个内核定时器(C线程),周期性地检查,watchdog(B线程)有没有正常运行。如果没有,那就意味着普通线程(A线程)也没有办法正常运行。这时内核定时器(C线程)会输出类似上图中的soft lockup记录,来告诉用户,卡在cpu上的,有问题的线程的信息。具体到这个“案件”,卡在cpu上的线程是python,这个线程正在刷新tlb缓存。老搭档ipi和tlb如果我们对所有夯机问题的调用栈做一个统计的话,我们肯定会发现,tlb和ipi是一对形影不离的老搭档。其实这不是偶然的。系统中,相对于内存,tlb是处理器本地的cache。这样的共享内存和本地cache的架构,必然会提出一致性的要求。如果每个处理器的tlb“各自为政”的话,那系统肯定会乱套。满足tlb一致性的要求,本质上来说只需要一种操作,就是刷新本地tlb的同时,同步地刷新其他处理器的tlb。系统正是靠tlb和ipi这对老搭档的完美配合来完成这个操作的。这个操作本身的代价是比较大的。一方面,为了避免产生竞争,线程在刷新本地tlb的时候,会停掉抢占。这就导致一个结果:其他的线程,当然包括watchdog线程,没有办法被调度执行(soft lockup)。另外一方面,为了要求其他cpu同步地刷新tlb,当前线程会使用ipi和其他cpu同步进展,直到其他cpu也完成刷新为止。其他cpu如果迟迟不配合,那么当前线程就会死等。不配合的cpu为什么其他cpu不配合去刷新tlb呢?理论上来说,ipi是中断,中断的优先级是很高的。如果有cpu不配合去刷新tlb,基本上有两种可能:一种是这个cpu刷新了tlb,但是做到一半也卡住了;另外一种是,它根本没有办法响应ipi中断。通过查看系统中所有占用cpu的线程,可以看到cpu基本上在做三件事情:idle,正在刷新tlb,和正在运行java程序。其中idle的cpu,肯定能在需要的时候,响应ipi并刷新tlb。而正在刷新tlb的cpu,因为停掉了抢占,且在等待其他cpu完成tlb刷新,所以在重复输出soft lockup记录。这里问题的关键,是运行java的cpu,这个我们在下一节讲。java不是问题,踩到的坑才是问题java线程运行在0号cpu上,这个线程的调用栈,满满的都是故事。我们可以简单地把线程调用栈分为上下两部分。下边的是system call调用栈,是java从系统调用进入内核的执行记录。上边的是中断栈,java在执行系统调用的时候,正好有一个中断进来,所以这个cpu临时去处理了中断。在linux内核中,中断和系统调用使用的是不同的内核栈,所以我们可以看到第二列,上下两部分地址是不连续的。netoops持有等待分析中断处理这部分调用栈,从下往上,我们首先会发现,netoops函数触发了缺页异常。缺页异常其实就是给系统一个机会,把指令踩到的虚拟地址,和真正想要访问的物理机之间的映射关系给建立起来。但是有些虚拟地址,这种映射根本就是不存在的,这些地址就是非法地址(坑)。如果指令踩到这样的地址,会有两种后果,segment fault(进程)和oops(内核)。很显然netoops踩到了非法地址,使得系统进入了oops逻辑。系统进入oops逻辑,做的第一件事情就是禁用中断。这个非常好理解。oops逻辑要做的事情是保存现场,它当然不希望,中断在这个时候破坏问题现场。接下来,为了保存现场的需要,netoops再一次被调用,然后这个函数在几条指令之后,等在了spinlock上。要拿到这个spinlock,netoops必须要等它当前的owner线程释放它。这个spinlock的owner是谁呢?其实就是当前线程。换句话说,netoops拿了spinlock,回过头来又去要这个spinlock,导致当前线程死锁了自己。验证上边的结论,我们当然可以去读代码。但是有另外一个技巧。我们可以看到netoops函数在踩到非法地址的时候,指令rip地址是ffffffff8137ca64,而在尝试拿spinlock的时候,rip是ffffffff8137c99f。很显然拿spinlock在踩到非法地址之前。虽然代码里的跳转指令,让这种判断不是那么的准确,但是大部分情况下,这个技巧是很有用的。缺页异常,错误的时间,错误的地点这个线程进入死锁的根本原因是,缺页异常在错误的时间发生在了错误的地点。对netoops函数的汇编和源代码进行分析,我们会发现,缺页发生在ffffffff8137ca64这条指令,而这条指令是inline函数utsname的指令。下图中框出来的四条指令,就是编译后的utsname函数。而utsname函数的源代码其实就一行。return &current->nsproxy->uts_ns->name;这行代码通过当前进程的task_struct指针current,访问了uts namespace相关的内容。这一行代码,之所以会编译成截图中的四条汇编指令,是因为gs寄存器的0xcbc0项,保存的就是current指针。这四条汇编指令做的事情分别是,取current指针,读nsproxy项,读uts_ns项,以及计算name的地址。第三条指令踩到非法地址,是因为nsproxy这个值为空值。空值nsproxy我们可以在两个地方验证nsproxy为空这个结论。第一个地方是读取当前进程task_sturct的nsproxy项。另外一个是看缺页异常的时候,保存下来的rax寄存器的值。保存下来的rax寄存器值可以在图三中看到,下边是从task_struct里读出来的nsproxy值。正在退出的线程那么,为什么当前进程task_struct这个结构的nsproxy这一项为空呢?我们可以回头看一下,java线程调用栈的下半部分内容。这部分调用栈实际上是在执行exit系统调用,也就是说进程正在退出。实际上参考代码,我们可以确定,这个进程已经处于僵尸(zombie)状态了。因而nsproxy相关的资源,已经被释放了。namespace访问规则最后我们简单看一下nsproxy的访问规则。规则一共有三条,netoops踩到空指针的原因,某种意义上来说,是因为它间接地违背了第三条规则。netoops通过utsname访问进程的namespace,因为它在中断上下文,所以并不算是访问当前的进程,也就是说它应该查空。另外我加亮的部分,进一步佐证了上一小节的结论。/*``* the namespaces access rules are:``*``* 1\. only current task is allowed to change tsk-&gt;nsproxy pointer or``* any pointer on the nsproxy itself``*``* 2\. when accessing (i.e. reading) current task's namespaces - no``* precautions should be taken - just dereference the pointers``*``* 3\. the access to other task namespaces is performed like this``* rcu_read_lock();``* nsproxy = task_nsproxy(tsk);``* if (nsproxy != NULL) {``* / *``* * work with the namespaces here``* * e.g. get the reference on one of them``* * /``* } / *``* * NULL task_nsproxy() means that this task is``* * almost dead (zombie)``* * /``* rcu_read_unlock();``*``*/回顾最后我们复原一下案发经过。开始的时候,是java进程退出。java退出需要完成很多步骤。当它马上就要完成自己使命的时候,一个中断打断了它。这个中断做了一系列的动作,之后调用了netoops函数。netoops函数拿了一个锁,然后回头去访问java的一个被释放掉的资源,这触发了一个缺页。因为访问的是非法地址,所以这个缺页导致了oops。oops过程禁用了中断,然后调用netoops函数,netoops需要再次拿锁,但是这个锁已经被自己拿了,这是典型的死锁。再后来其他cpu尝试同步刷新tlb,因为java进程关闭了中断而且死锁了,它根本收不到其他cpu发来的ipi消息,所以其他cpu只能不断的报告soft lockup错误。本文作者:声东阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 3, 2018 · 1 min · jiezi