“梁之书”

2020-07-03 05:53:26

这本书不是关于如何编写正确而漂亮的代码,我假设您已经知道如何做到这一点。这本书实际上也不是关于剖析和性能调优。不过,本书中有一章是关于跟踪和分析的,它可以帮助您找到瓶颈和不必要的资源使用。还有一章是关于性能调优的。

这两章是本书的最后一章,整本书正在为这两章做准备,但本书的真正目标是给您提供您需要的所有信息,所有令人毛骨悚然的细节,以便真正理解Erlang应用程序的性能。

对于任何想要调优Erlang安装的人。想知道如何调试虚拟机崩溃。我想提高Erlang应用程序的性能。想要了解Erlang到底是如何工作的。我想了解如何构建您自己的运行时环境。

如果您想要调试VM如果您想要扩展VM如果您想要进行性能调整-​跳到最后一章…。但是要真正理解这一章,你需要阅读这本书。

Erlang运行时系统(ERTS)是一个包含许多相互依赖的组件的复杂系统。它是以一种非常便携的方式编写的,因此它可以在任何东西上运行,从口香糖棒状计算机到最大的具有TB内存的多核系统。为了能够针对您的应用程序优化这样一个系统的性能,您不仅需要了解您的应用程序,还需要对ERTS本身有全面的了解。

了解了ERTS的工作原理后,您将能够了解您的应用程序在ERTS上运行时的行为,您还将能够发现和修复应用程序的性能问题。在本书的第二部分中,我们将介绍如何成功运行、监控和扩展您的ERTS应用程序。

您不需要是Erlang程序员就可以阅读本书,但是您需要对Erlang是什么有一些基本的了解。下面这一节将向您介绍一些Erlang背景知识。

在本节中,我们将了解一些基本的Erlang概念,这些概念对于理解本书的其余部分至关重要。

Erlang已经被调用,尤其是Erlang的创建者之一JoeArmstrong,一种面向并发的语言。并发性无疑是Erlang的核心,要理解Erlang系统是如何工作的,您需要了解Erlang的并发模型。

首先,我们需要区分并发和并行。在本书中,并发是具有两个或多个可以彼此独立执行的进程的概念,这可以通过先执行一个进程然后执行另一个进程,或者通过交叉执行,或者通过并行执行这些进程来实现。对于并行执行,我们的意思是,通过使用几个物理执行单元,进程实际上在完全相同的时间执行。并行可以在不同的层次上实现,通过执行流水线中的多个执行单元在一个核心中,在一个CPU上的多个核心中,通过在一台机器上的多个CPU或通过多台机器来实现。

Erlang使用进程来实现并发。从概念上讲,Erlang进程类似于大多数操作系统进程,它们以并行方式执行,可以通过信号进行通信。实际上,有一个巨大的不同之处,那就是Erlang进程比大多数OS进程要轻量级得多。许多其他并发编程语言将其等价物称为Erlang进程代理。

Erlang通过交错执行Erlang虚拟机(BEAM)上的进程来实现并发。在多核处理器上,BEAM还可以通过在每个内核上运行一个调度器并在每个调度器上执行一个Erlang进程来实现并行性。Erlang系统的设计者可以通过将系统分布在多台计算机上来实现进一步的并行性。

典型的Erlang系统(Erlang中内置的服务器或服务)由多个Erlang应用程序组成,对应于磁盘上的一个目录,每个应用程序由几个与目录中的文件对应的Erlang模块组成。每个模块包含许多函数,每个函数都由表达式组成。

因为Erlang是一种函数式语言,所以它没有语句,只有表达式。Erlang表达式可以组合成Erlangfunction。函数接受多个参数并返回avalue。在Erlang代码示例中,我们可以看到一些Erlang表达式和函数的示例。

%%某些Erlang表达式:TRUE。1+1。IF(X>;Y)->;X;TRUE->;Y结束。%%an Erlang函数:MAX(X,Y)->;if(X>;Y)->;X;TRUE->;Y结束。

Erlang有许多由VM实现的内置函数(或BIF)。这要么是出于效率原因,就像List:append(可以用Erlang实现)的实现一样。它还可以提供一些低级功能,这些功能很难或不可能在Erlang本身实现,比如ist_to_atom。

从Erlang/OTP R13B03开始,您还可以使用本地实现函数(NIF)接口提供您自己的函数(用C实现)。

首先,我要感谢爱立信的整个OTP团队维护Erlang和Erlang运行时系统,并耐心地回答我的所有问题,特别要感谢Kenneth Lundin、Björn Gustavsson、Lukas Larsson、Rickard Greenand Raimo Niskanen。

我也要感谢田中义弘、罗伯托·阿洛伊和Dmytro Lytovchenko对这本书的主要贡献,以及HappiHacking和TubiTV对这本书的赞助。

Erlang运行时系统(ERTS)是一个包含许多相互依赖的组件的复杂系统。它是以一种非常便携的方式编写的,因此它可以在从口香糖棒计算机到最大的具有TB内存的多核系统的任何设备上运行。为了能够针对您的应用程序优化这样一个系统的性能,您不仅需要了解您的应用程序,还需要对ERTS本身有一个透彻的了解。

任何Erlang运行时系统和Erlang运行时系统的特定实现之间都是不同的。爱立信的Erlang/OTP&34;是Erlang和Erlang运行时系统事实上的标准化实现。在本书中,我将此实现称为ERTS,或拼写为大写为T的ErlangRunTime系统。(有关动态口令的定义,请参见第1.3节)。

对于什么是Erlang Runtime系统,或者什么是Erlang虚拟机,没有正式的定义。你可以在某种程度上想象这样一个理想的柏拉图系统会是什么样子,只要取下ERTS,去掉所有具体实现的细节。不幸的是,这是一个循环定义,因为您需要知道一般定义才能识别实现的具体细节。在二郎人的世界里,我们通常太务实了,不会担心这一点。

我们将尝试使用术语Erlang运行时系统来指代任何Erlang运行时系统的一般概念,而不是爱立信的具体实现,我们称之为Erlang运行时系统,或者通常简称为ERTS。

注这本书主要是一本关于ERTS的书,特别是关于ERTS的书,只在很小程度上介绍了一般的Erlang Runtime系统。如果你假设我们谈论的是爱立信的实现,除非清楚地表明我们谈论的是一般原则,否则你很可能是对的。

在本书的第二部分中,我们将了解如何为您的应用程序调优运行时系统,以及如何评测和调试您的应用程序和运行时系统。为了真正知道如何调优系统,您还需要了解系统。在本书的第一部分中,您将深入了解运行时系统是如何工作的。

第一部分的后续章节将单独介绍系统的每个组件。您应该能够阅读其中的任何一个字符,而不必完全理解其他组件是如何实现的,但是您需要对每个组件有一个基本的了解。本介绍性章节的其余部分应该会给你足够的基本理解和词汇,以便能够按照你喜欢的顺序在第一部分的其余章节之间跳转。

但是,如果你有时间,第一次读这本书要按顺序。本书中特定于Erlang和ERTS或以特定方式使用的单词通常在第一次出现时进行解释。然后,当您掌握了词汇表后,无论何时遇到特殊组件的问题,您都可以重新使用第一部分作为参考。

在本文中,我们对ERTS的主要组件进行了基本概述,并介绍了理解以下各章中对每个组件的更详细描述所需的一些词汇。

当您启动Elixir或Erlang应用程序或系统时,您真正启动的是Erlang节点。该节点运行ErlangRunTime系统和虚拟机束。(或者可能是Erlang的另一个实现(参见第1.4节))。

您的应用程序代码将在Erlang节点中运行,该节点的所有层都将影响您的应用程序的性能。我们将查看组成节点的层的堆栈。这将帮助您了解在不同环境中运行系统的选项。

在OO术语中,可以说Erlang节点是Erlang Runtime System类的对象。Java世界中的等价物是JVM实例。

Elixir/Erlang代码的所有执行都在一个节点内完成。一个Erlang节点在一个操作系统进程中运行,您可以在一台机器上运行多个Erlang节点。

完全正确地说,根据Erlang OTP文档,阳极实际上是一个已命名的执行运行时系统。也就是说,如果您在启动Elixir时没有通过命令行开关之一提供名称--name name@host或--sname name(对于Erlang运行时,则是-name和-sname)。您将拥有一个运行时,但没有节点。在这样的系统中,函数Node.live?(或在Erlang中为is_live())返回FALSE。

$iexErlang/OTP19[ERTS-8.1][源-0567896][64位][smp:4:4][异步线程:10][Hipe][内核轮询:FALSE]交互式Elixir(1.4.0)-按Ctrl+C退出(键入h()Enter for Help)IEX(1)>;Node.live?false seiex(2)>;

运行时系统本身对术语的使用并不严格。即使没有给节点命名,也可以询问节点的名称。在Elixir中使用带参数的函数Node.list:this,在Erlang中使用调用节点(This):

在本书中,我们将使用术语节点来表示运行时的任何运行实例,无论是否给它一个名称。

您的程序(应用程序)将在一个或多个节点上运行,程序的性能不仅取决于您的应用程序代码,还取决于ERTS堆栈中代码下面的所有层。在图1中,您可以看到在一台机器上运行两个Erlang节点的ERTS堆栈。

让我们看看堆栈的每一层,看看如何根据应用程序的需要调整它们。

堆栈的底部是您正在运行的硬件。提高应用程序性能的最简单方法可能是在更好的硬件上运行它。如果经济或物理限制或环境问题不允许您升级硬件,您可能需要开始探索更高级别的堆栈。

对于您的硬件来说,最重要的两个选择是它是多核还是32位还是64位。您需要不同版本的ERTS,这取决于您是否想要使用多核,以及您想要使用32位还是64位。

堆栈中的第二层是操作系统级别。ERTS可以在大多数版本的Windows和大多数兼容POSIX&34;的操作系统上运行,包括Linux、VxWorks、FreeBSD、Solaris和Mac OS X。目前,ERTS的大部分开发都是在Linux和OS X上完成的,您可以期待在这些平台上获得最佳性能。然而,爱立信已经在许多项目中内部使用了Solaris,而且ERTS已经针对Solaris调优了很多年。根据您的用例,您可能会在Solaris系统上获得最佳性能。操作系统的选择通常不是基于性能要求,而是受到其他因素的限制。如果您正在构建嵌入式应用程序,您可能会受限于Raspbian或VxWork,如果您出于某种原因正在构建最终用户或客户端应用程序,您可能必须使用Windows。到目前为止,ERTS的Windows端口还没有最高的优先级,从性能或维护的角度来看,它可能不是最佳选择。如果您想要使用64位的ERTS,那么您当然需要同时拥有64位的机器和64位的操作系统。在本书中,我们不会涉及很多特定于操作系统的问题,大多数示例都假设您在Linux上运行。

堆栈中的第三层是Erlang Runtime系统。在我们的情况下,这将是ERTS。这一层和第四层,即Erlang虚拟机(BEAM),就是本书的全部内容。

第五层OTP提供Erlang标准库。OTP最初代表开放电信平台(Open Telecom Platform),是一些Erlang库,为构建健壮的应用程序(如电话交换)提供构建块(如Supervisor、gen_server和gen_tcp)。早期,OTPget的库和含义与ERTS附带的所有其他标准库混为一谈。如今,大多数人将OTP与Erlang一起使用,作为ERTS和爱立信提供的所有Erlang库的名称。了解这些标准库以及如何以及何时使用它们可以极大地提高应用程序的性能。本书不会深入标准库和OTP的任何细节,还有很多其他书籍涵盖了这些方面。

如果您运行的是长生不老药剂程序,第六层提供了长生不老药环境和长生不老药剂库。

最后,第七层(APP)是您的应用程序,以及您使用的任何第三方库。应用程序可以使用底层提供的所有功能。除了升级硬件之外,这里可能是您最能提高应用程序性能的地方。在第18章中,有一些提示和一些工具可以帮助您分析和优化您的应用程序。在第19章中,我们将研究如何查找应用程序崩溃的原因,以及如何查找应用程序中的bug。

有关如何构建和运行Erlang节点的信息,请参阅附录A,并阅读本书的其余部分以了解有关Erlang节点组件的所有信息。

Erlang语言设计者的一个重要见解是,为了构建一个全天候工作的系统,您需要能够处理硬件故障。因此,您需要将系统分布在至少两台物理机上。您可以通过在每台计算机上启动一个节点来实现这一点,然后您可以将这些节点相互连接,以便进程可以跨节点相互通信,就像它们在同一节点上运行一样。

Erlang编译器负责将Erlang源代码从.erl文件编译成BEAM(虚拟机)的虚拟机代码。编译器本身是用Erlang编写的,并自行编译成BEAM代码,通常在运行中的Erlang节点中可用。要引导运行时系统,在bootstrap目录中有许多预编译的BEAM文件,包括编译器。

BEAM是用于执行Erlang代码的Erlang虚拟机,就像JVM用于执行Java代码一样。梁在安二郎节点运行。

正如ERTS是更一般的Erlang运行时系统概念的实现一样,BEAM也是更通用的Erlang虚拟机(EVM)的实现。没有定义什么构成EVM,但是BEAM实际上有两个层次的指令、通用指令和特定指令。通用指令集可以看作是EVM的蓝图。

Erlang进程的工作方式基本上类似于OS进程。每个进程都有自己的内存(一个邮箱、一个堆和一个堆栈)和一个进程控制块(PCB),其中包含有关该进程的信息。

所有Erlang代码执行都是在进程上下文中完成的。一个Erlang节点可以有多个进程,这些进程可以通过消息传递和信号进行通信。只要节点已连接,Erlang进程还可以与其他Erlang节点上的进程通信。

调度器负责选择要执行的Erlang进程。基本上,调度器保留两个队列,一个是准备运行的进程的就绪队列,另一个是等待接收消息的进程的等待队列。当等待队列中的进程收到消息或Getsa超时时,它将被移至就绪队列。

调度器从就绪队列中挑选第一个进程,并将其交给BEAM以执行一个时间片。当时间片用完时,BEAM抢占正在运行的进程,并将该进程添加到就绪队列的末尾。如果该进程在时间片用完之前在接收中被阻塞,则会将其添加到等待队列中。

Erlang本质上是并发的,也就是说,每个进程在概念上与所有其他进程同时运行,但实际上只有一个进程在VM中运行。在多核机器上,Erlang实际上运行多个调度器,通常每个物理核一个调度器,每个调度器都有自己的队列。这样,Erlang就实现了真正的并行性。要使用多个核心,必须在SMP模式下构建ERTS(见附录A)。SMP代表对称多处理,即在多个CPU中的任何一个上执行进程的能力。

实际上,情况更为复杂,进程之间的优先级和等待队列是通过计时轮实现的,所有这些和更多内容将在第11章中详细描述。

Erlang是一种动态类型的语言,运行时系统需要跟踪每个数据对象的类型。这是通过标签方案来完成的。每个数据对象或指向数据对象的指针还具有具有关于对象的数据类型的信息的标签。

基本上,指针的一些位被保留给标签,然后仿真器可以通过查看标签的位模式来确定对象的类型。

这些标记用于模式匹配、类型测试和forprimitive操作以及垃圾收集器。

Erlang使用自动内存管理,程序员不必担心内存分配和释放。每个进程都是一个堆和一个堆栈,两者都可以根据需要增长和缩小。

当进程用完堆空间时,VM将首先尝试通过垃圾收集回收空闲堆空间。然后,垃圾收集器将遍历进程堆栈和堆,并将活动数据复制到新堆中,同时丢弃所有死数据。如果没有足够的堆空间,将分配一个新的更大的堆,并将实时数据移到那里。

当前世代复制垃圾收集器的细节,包括引用计数二进制文件的处理,可以在第12章中找到。

在使用HIPE编译的本机代码的系统中,每个进程实际上有两个堆栈,一个波束堆栈和一个本机堆栈,详细信息可以在第17章中找到。

当您使用erl启动Erlang节点时,您会得到一个命令提示符,这是Erlang读取求值打印循环(REPL)或命令行界面(CLI),或者仅仅是Erlang shell。

实际上,您可以输入Erlang代码并直接从shell执行它。在这种情况下,代码不会编译成BEAM代码并由BEAM执行,而是由Erlang解释器解析和解释。一般说来,解释代码的行为与编译代码完全一样,但也有一些细微的差异,这些差异和shell的其他方面将在第20章解释。

本书主要涉及Ericsson/OTP的名为ERTS的标准Erlang实现,但也有一些其他可用的实现,在本节中,我们将简要介绍其中的一些实现。

Xen上的Erlang(http://erlangonxen.org)是Erlang实现运行目录。

..