Erlang 内存管理

Erlang核心开发者Lukas Larsson在2014年3月份Erlang Factory上的一个演讲详细介绍了Erlang内存体系的原理以及调优案例:

http://www.erlang-factory.com/conference/show/conference-6/home/#lukas-larsson

在这里可以下载slides和观看视频(为了方便不方便科学上网的同学,我把视频搬运到了 http://v.youku.com/v_show/id_XNzA1MjA0OTEy.html )

这个演讲的内容很丰富,原理介绍很细致,但是slides比较简略,所以我把这个演讲的整个内容听写出来了,收获颇多,完整的笔录如下:


演讲pdf

Page 2

当我写这篇演讲时,我以为我会举出一大堆关于如何分析内存的例子,看看有哪些不同的分配器,以及 Erlang 虚拟机是如何工作的。
结果发现,这些东西的理论相当复杂,所以更像是在讲理论,最后再讲一些故事,而不是以故事为主。
因此,我将谈谈问题所在,以及 Erlang 虚拟机中为什么会有内存分配器,它们的用途和不同的处理方法,介绍内存分配器的所有不同概念,以便你掌握正确的术语,了解你想要设置的不同选项,等等。
我要看看你可以收集到的统计数据。统计数据有很多,我们之所以采用分配器这种方式,主要是因为我们可以收集系统的统计数据,并尝试找出使用内存的最佳方式。
我们将看看两个不同的案例,在这些案例中,我们可以从 Erlang 分配器中获得统计数据,并对其进行修改,希望能让系统运行得更好。
最后,我们会看看一些新功能,因为分配器中正在进行大量开发,我们已经花了很多时间来优化这些东西,并试图将碎片变得越来越小。

Page 3

我们之所以在 Erlang 虚拟机中使用内存分配器,是因为普通的 malloc 等方法对于非常小的分配来说相对较慢,所以当你在某个地方分配小的 ets 对象时,必须一路调用系统调用才能完成的操作相当繁重,因此我们在虚拟机中缓存内存。
此外,为了防止碎片化,我们在分配数据时采用了不同的策略,分配模块之类的数据时,你需要花很多时间寻找正确的位置来分配内存,但分配发送给其他进程的消息时,你就不需要花费那么多 CPU 周期来寻找内存中的最佳位置了,因为收到消息后,内存就会被重新分配。因此,我们要根据分配内存的类型采取不同的策略。
此外,我们也无法以跨平台的方式获取统计数据。现在,如果你想使用 malloc 或类似的工具,我们就可以使用分配器。
如果你想在你的系统上尝试不使用 Erlang 分配器,只需从一开始就使用 malloc,将该标记传递给 Erlang 虚拟机,它就会禁用一切。你只需运行普通的 malloc。
有时,这样会更快,我在看一个名为 ESTONE 的基准测试,我想看看这些分配器到底快了多少,于是我关掉了它们,结果基准测试比使用分配器更快。
所以,这并不总是,但这就是某些统计基准的结果。
在实际生产环境中,它们的帮助很大。我一直在运行另一个基准,做一个 4G 电话基准测试,结果发现这些分配器比使用 malloc 等工具要快 40%。
因此,在现实世界的应用中,小型基准测试通常会更好,因为它们有时会作用过度,位数过多。
在进行 NUMA 时,分配器也有很大帮助,因为有很多内核,很多东西。

Page 4

接下来,我要谈谈概念。
这些都是我们正在谈论的成型事物。
载体和区块、单区块载体与多区块载体、多区块如何工作以及线程专用分配器是什么。

Page 5

关于区块。
这是一个术语,这样我们才能谈论这些事情。
区块是 Erlang 虚拟机所需的一块连续内存,例如,当你插入一个 ets 时,那块数据就变成了一个区块,Erlang 进程的堆和栈就是一个区块,从一个进程发送到另一个进程的消息也是在一个区块内复制的,所以它是一块连续的 C 内存。
我们有载体。
载体是一种容器,包含一个或多个块。所以我们有类似的东西。
我们有一个头文件,说明这是一个这种类型的载体,有这些设置等等。然后我们把数据块放进去。
当我们继续对 ETS 表进行更多操作时,我们会将越来越多的数据放入区块中,希望这些数据能放在一个好的位置。如果我们删除了某些内容,就会出现碎片。最后我们可能会得到这样的结果。
现在这些数据块是这样的。它们通常…我们对齐它们,通常按 18 位限制对齐,因此它们最小约为 256 KB,按此限制对齐,因此我们可以做很多优化。
分配内存的大小可以通过许多不同的设置来控制。

Page 6

在此之前,我们先谈谈单块和多块载体。
因此,在查看分配器的统计数据时,我们有两个不同的概念,单块载体是指包含一个块的东西,它是包含一个内存块的载体。
通常情况下,你想把大内存块放入单块载体。
这是因为操作系统非常擅长处理连续的大内存块,而单块载体通常只使用像 malloc 这样的分配器。
大内存取决于你的上下文,有时大内存可能是 256 字节,有时可能是 5 MB,所以这取决于你的上下文。
默认情况下是半个 MB,这似乎适用于大多数情况。因此,如果你有一个小于 512 KB 的数据块,它就会被放置在多块载波上;如果数据块更大,它就会被放置在单块载波上。
你可以通过单块载体阈值来控制。
通常情况下,你希望将大部分数据放在多块载体上。
因此,在一个正常运行的系统中,80%85%90% 的数据都是多块载体的一部分。
因此,如果你在运行的系统中发现有大量的单块载体,而不是大量的多块载体,你可能会想提高阈值来看看,好吧,我从我的代码中知道,我一直在从 TCP 套接字读取 1 MB 的信息,这就变成了 1 MB 的二进制数据,这比 512 KB 要大,这些数据总是被放到单块载体中,所以我可能想把阈值调整到 1.5 MB,然后,它们就会把所有这些数据块放到多块载体中,希望你的系统会快很多。
在调整阈值时,你可能还想改变正在分配的载体大小。因此,如果增加单个区块的大小,就会增加实际创建的载体。因此,如果你将阈值增加到 1 MB,那么所创建的载体大小就会增加。我认为多区块载体的默认大小是 2 MB,也就是最小的一个,然后会根据载体的数量增加到 8 MB。
既然你把初始容量增加了一倍,那么下一个容量可能也要增加一倍,所以你从 4 MB 开始的容量可能会增加到 16 MB,或者类似的效果。

Page 7

分配器有多种不同类型。你可以对这些分配器的工作方式进行单独配置。
因此,我们有堆分配器、二进制分配器、驱动分配器和 ets 分配器。作为 Erlang 程序员,你可以对这些东西进行推理。
你通常会对这些分配器进行调整。我还没有见过任何分配器出现问题。通常情况下,如果有人分配的二进制文件失控,或者有许多小的 ets stuff 被放入表中,就会产生碎片。这就是两种常见的问题。
此外,还有临时分配、短期分配、标准分配、长期分配和固定大小分配,这些都是模拟器内部的事情。

Page 8

这些模拟器内部事物之间的区别:
临时是一个非常短的时间,对于一些只是存在于 C 语言函数中的东西,可以举一些例子来说明。
标准的是链接、监视器等这类 C 结构。
短时间是指…我不知道…erlang 函数的调度程序,只存在 4 或 5 毫秒之类的时间。
长是指代码、原子等我们知道可能永远存在的东西。
固定的是我们知道有一定大小的东西,我们可以因此进行优化,比如进程控制块、端口控制块和其他很多东西。
如果你想找到确切的东西,erlang/otp 资源库中的 erts 下有一个名为 erl_alloc.types 的文件,所有映射都在那里设置,如果你运行的是半字模拟器,如果你运行的是 64 位或 32 位,如果你运行的是 windows、unix 或…,映射都是不同的。
许多事情都取决于你为之构建 erlang 虚拟机的操作系统,你对这些事情有不同的设置。

Page 9

说到这里,我想说说单块载体。
单块载体非常简单,你不需要管理它们,你有一个块一个载体,就这样,一个载体就是向操作系统请求的一块内存,所以非常容易处理。
另一方面,多块内存则相当困难。你有很多小区域,因为你需要跟踪我可以把这块内存放在哪里。我需要放置一个 8 字节大的东西,我需要寻找最佳放置位置,并为此花费大量时间。
因此,我们可以使用一些~7 种不同的策略来分配多块载体中的块。
因此,我们有面向区块的策略,这些策略跨越载体,因此,如果你有两个载体,并用它们与该分配器中所有载体的所有空闲区块一起构建树。
我们有最佳拟合,只是,最佳拟合寻找的是浪费最少的那个,所以,如果你有一个 8 字节的区块,你找到了 9 字节的插槽,而下一个较小的插槽是 7 字节,那么你就把它放到 9 字节的插槽中,以此类推。
地址顺序最佳匹配的工作原理与此相同,只是如果出现并列,它会使用地址最小的那个,而不是只使用最后放入队列的那个。
地址顺序最匹配会尽量选择地址最低的那个。
好的适配会选择好的适配。因此,如果你说浪费 10%的区块是可以的,如果我有一个 10 字节大的东西,那么好的拟合就是不浪费超过 15 字节的东西。因此,你可以调整你想要的匹配度。
拟合主要用于快速的临时分配,因此只需找到一些东西。
我们有面向载体的策略。这些策略被细分为:首先是载体的组织策略,然后是载体内块的组织策略。
我想我会详细介绍…。

Page 10

我这里有一张图片,举个小例子说明最佳匹配是如何工作的。我们有两个载体,阴影部分是已占用的内存,蓝色和红色部分是空闲的插槽,也就是空闲的内存区域。
在不同的载体之后,我们会建立一棵类似这样的树,好了,我们来做一棵二进制搜索树,我认为这是一棵红黑平衡的二进制搜索树,你可以在这棵树里寻找区块。
最后看起来就像这样,最小的红色块一直向右,然后你就建立起了这棵树,巧妙的是,建立这棵树当然不需要内存,因为我们将这棵树的指针保存在空闲槽中。然后我们搜索这棵树,看看哪一个可以被找到,然后我们取一个插槽,然后你从树中取出,我们进行平衡,然后希望你能找到好的插槽。
我已经说了很多关于 "块 "的东西,所以你有不同的 “块”,但 "载体 "是从哪里来的呢?

Page 12

因此,我们在运行时系统中有两种不同的载体分配方式。
一种叫 mseg alloc,另一种叫 sys alloc。
mseg 分配器基本上是尽可能地将操作系统拒之门外,只分配大的区段。这通常用于多块载体分配。
在 Linux 系统中,它使用 /dev/zero 并将其作为文件打开,同时使用不同参数的 mmap 来获取大块内存。
mseg 分配的关键之一是它有一个缓存,因此它可以缓存刚刚返回给它的一定大小的载体。我认为默认值是 10 或类似的数值,只有在正常系统中,我看到的大多数情况下,缓存命中率约为 80%~85%,通常是在系统中使用大量载体的情况下。
sys alloc 只是调用 malloc、free 或 posix_memalign,具体取决于操作系统。它分配的内存块是你可以在这里设置的变量的倍数,我们称之为 Muxxxx。我认为默认值是 2 MB,所以无论你想分配的内存是 1 MB 还是 2 MB,它都会分配 2 MB,这样可以让操作系统更容易减少碎片。
我们在项目中遇到过很多虚拟内存空间碎片化的问题,这就是为什么我们要帮助 malloc 来管理这些大块内存,并使其达到完美。
sys alloc 也用于分配我们所说的主载体。主载体是一个初始载体,因此是启动过程中始终存在的第一个载体。
使用主载体的目的是让你有足够的内存来正常启动 erlang 模拟器,而不需要为了加快启动速度而进行额外的分配。

Page 13

所以又是一张图片。现在,我们开始使用不同的调度程序。
我们有短实时分配器、eheap、二进制和所有不同的东西,它们都向 mseg alloc 请求载体。
所有这些,系统中每个调度器都有一个链。因此,所有这些都是本地的,调度器 1 有一个 ets alloc,调度器 2 有一个 ets alloc,等等。因此,它们都是本地的,可以利用系统的 NUMA 架构,让分配始终就在你身边。
当然,我们必须在调度器之间传递内存,所以如果我们想释放或释放传递过来的内存,就必须向该调度器发送消息才能释放,因为我们在实际分配器上没有任何锁,因为我们希望这些分配器是无锁的,所以我们通过调度器之间的消息传递进行分配。
在 R16B02 中,我们添加了一个不同调度器共享的载体池,如果使用我之前提到的面向载体的算法,就可以访问这个池。如果调度器 n 上的载波利用率为 20%,碎片率很高,那么调度器就可以将该载体交给载体池,而需要载体的人,比如调度器 1 需要一个新载体来分配数据,就可以从载体池中获取载体,而不是从其他地方获取。
当然,你会失去一些 NUMA 的东西,但你会得到更好的内存利用率,这很好。
对于所有异步线程和所有驱动线程,都有一个全局分配器,它前面有一个大锁,所有数据都会被放入其中,因此,如果你正在读取一个包含二进制的文件,这些二进制就会被放入这个全局分配器中。
所有这些 mseg,就像它们在这里的层数一样,我们在底部还有一个叫做 erts_mmap 的东西,它是在 R16B03 中添加的,也很有趣。如果你想知道细节,可以事后问我。

Page 14

以上就是这些问题的一般理论背景。为了弄清统计数据出了什么问题,让我们来深入研究其中的一些东西。
我们有不同类型的分配器,当我们讨论其中的统计部分时,它们被称为类似这样的东西。
因此,如果执行 erlang:system_info(allocator),就能获得系统中哪些分配器处于活动状态的信息。这些信息并不总是相同的,而且在不同的版本中也绝对不总是相同的。但通过此方法,你可以获得系统上启用了哪些馈送的信息。你可以获得系统上所有不同的设置,你可以知道你的阈值是多少,或者你的多区块载体策略是什么,以及其他类似的信息。还有一些功能,比如如果你的系统中有 posix_memalign,你就可以锁定物理内存,而不是试图用 linux 的方式来进行内存管理。还有很多其他功能。

Page 15

现在,你可以挖掘更多细节,甚至更多。
因此,如果你说 erlang:system_info,并输入我们在上一张幻灯片中看到的类型之一:eheap_alloc、ets_alloc,类似的东西,你就会得到包含大量数据的元组列表。
你可以看到,我们的第一个结构是实例。
实例 0 是用于异步线程的全局分配器。
实例 1 用于调度程序 1,实例 2 用于调度程序 2,等等等等。
对于每个实例,我们都有分配器使用的版本、选项集、多区块载体的统计数据、单区块载体的统计数据以及载体调用的统计数据。
我将为您细分这些数据。

Page 16

这就是您从数据中获得的信息。

Page 17

我把它放在一个表格里,这样你就不用看数据了。
因此,我们得到的是当前值、上次调用统计数据以来该部件的最大值,以及系统寿命的最大值。
我们可以看到数据块的数量,有 100 万、100 万和 180 万个数据块。
在这里,我们可以获得所有区块的总大小,我想我实际上有一个…所以我们有一个 820 MB,820 MB,我们有最大值,当我们有一个 3.3 GB 的内存峰值。
我们可以看到载体,我们将这 100 万个区块装入了 455 个载体。
我们还可以看到下面的载体大小。

Page 19

当然,单块载体的块数与载体相同。
我们可以看到,区块大小为 6、6、21,但由于我们分配过多,载体大小为 7.5、7.5 和 25。非常简单。

Page 20

你可以看到的调用次数。我们可以看到,我们已经进行了多次调用。

Page 21

二进制分配器的调用次数相当多,达到 2.8 万次。
因此有相当多的二进制在这里分配。
我们释放了大致相同数量的内存,这是好事,否则就会出现内存泄漏。
我们还重新分配了一些内存。
我们可以看到,mseg alloc 完成了 24000 次分配,而 sys alloc 则为零。
这就是单块和多块的情况。

Page 22

你还可以获得载体分配器的统计数据。
我们可以获取段的数量、缓存信息,查看分配、去分配和销毁。
在这里,我们可以看到缓存的工作情况。
我们有 464 个 mseg 分配,而实际上我们只创建了 40 个载体。
因此,我们的缓存命中率相当高。我们进行了 464 次调用,其中只有 40 次最终被操作系统调用。
我只是在想我有多少时间…似乎还很多…这很好…

Page 23

因此,我们需要进行一些案例研究,看看如何分析这些事情,并在理解其含义后获得这些数据。
我遇到了两个案例。我遇到过很多案例,但这些案例对开源社区非常有用,因为你们似乎比我们的商业客户遇到得更多。

Page 24

我想谈的第一个案例是大型二进制文件。
我知道这些分配器应该是非常高效的,它应该使用 mmap 来分配我的东西,但出于某种原因,它没有使用 mmap,而是使用了 malloc。它用了很多 malloc。

Page 25

因此,我申请了二进制分配器的统计数据。
你可以看到,二进制分配器的调用次数大约有 300 兆次。
而对 mseg alloc 的调用只有 0.4 兆次。
这就造成了一个差异,因为我们希望把大部分东西放到 mseg allocator 中,而不是放到 system allocator 中,因为 mseg allocator 意味着我们使用多块载体,而 system allocator 则意味着我们主要使用单块载体。
我们可以看到,系统分配器的调用次数为 1.4 兆次,因此差异相当大。
所以,这时候我的耳边响起了钟声,说这些设置出了问题。
因此,此时你可以推理一下分配器,是阈值设置出了问题。我注意到这是一个二进制分配器,所以这个人分配的二进制文件比大多数二进制文件的实际单块阈值都要大,因此他们推入了单块载体,而这并不高效。
回过头来,我们可以看到多块载体大小约为 2.4 GB,而单块载体大小为 11 GB 内存。
因此,这是一个很大的差距,通常情况下,我们希望多块载体的大小与之相反,甚至更多。
那么,我们能做些什么呢?
我们想调整单块载波阈值,但该如何调整呢?
从统计数据来看,我们也可以得到单区块载波中的平均区块大小。
用块的数量除以载体的总大小,我们可以看到块的大小约为 1.68 MB,在这种情况下,它是从 TCP 套接字读取的。
现在我们有了区块大小,这就是平均区块大小。因此,在调整分配器时,我们可能希望比这个大小大一些。
最后,我们将其设置为 2MB,这是一个合理的上限,也是一个干净利落的数字。
因此,我们将大于 2MB 的二进制文件放入单块载体,而将小于 2MB 的二进制文件放入多块载体。
由于多区块载体是由 mmap 完成的,而且你还能获得 mseg 的缓存等所有功能,我相信性能提升会相当不错。
当然,还可以增加最大的多块载体大小和最小的多块载体大小,正如最后两条所说的那样,只针对二进制分配器,这就是 erlang runtime allcoators 的优势所在,因为你可以说,我只设置了二进制分配器,因为如果你设置了所有分配器的设置,那么你就会遇到问题,很可能是 ETS 或类似的问题,在你的系统中过度分配内存,而不是你真正想要的内存。
因此,你可以指定说,我只在二进制分配器上有问题,所以我只在模拟器中进行二进制调整。

Page 27

我遇到的第二个情况是…实际上是在帮助 Fred Hebert 解决分配器的问题,他为此写了一篇大博文,如果你读了博文,这就是数据。
他的症状是,erlang:memory(total) 显示已使用的内存约为 7 GB,但操作系统的顶部却显示 BEAM 正在使用 15 GB 内存。
然后突然间,他遇到了一个小的流量高峰,然后他就因为 ets alloc 分配失败而崩溃了。他很困惑,因为 "嘿,我只有…它说我只有 7 GB,然后又变成了 15 GB,我没有内存来分配这些东西了。
关于 erlang:memory(total),要知道的是,它是系统中所有块的总面积。
它不是系统中载体的总面积。
这就是差异所在,Erlang 计算的是已使用的内存块总面积,而不是操作系统实际分配的内存块总面积。
因此,如果你看到 erlang:memory 所给出的数据与操作系统顶部给出的数据存在差异,那么很可能是你的分配器出现了某种碎片化问题。

Page 28

因此,再次查看其中的统计数据,我们可以看到他有很多块,并且他的块大小目前为 1.6 GB,我相信这只是一个实例,所以它只有一个调度器。
但我们可以看到,最大区块大小为 6.8 GB,但我们可以看到,载波大小在当前和最大时基本相同,所以这就是我们的问题所在,你在分配这些二进制文件时出现了碎片。
因此,出于某种原因,分配器将二进制文件分配到了许多不同的载体中,但有一天在释放它们时,却没有释放载体中的所有二进制文件,而是一个二进制文件在那里,一个二进制文件在那里,根本无法释放这些载体。然后,他发现 ETS 插入激增,无法将 ets 数据放入载体中进行二进制分配。因此,如果试图创建 ETS 载波,内存就会耗尽。
因此,我们必须再次了解数据块的平均大小,我们可以看到数据块大小约为每块 800 字节,当我们处于当前状态时,数据块的峰值为每块 1.7 KB,而载波约为每载波 7.8 和 7.7 MB。这是你意料之中的。
但是,如果用数据块大小除以载体大小,我们当前运行时的利用率约为 22%,这一点也不理想。但在最大值时,我们的利用率为 93%,还算可以。
因此,我们要做的是找到一种更好的策略来分配这些二进制文件,以免在释放二进制文件时出现载体无法返回操作系统的情况。

Page 29

二进制文件的默认分配器是一个普通的最佳拟合分配器,通过使用地址顺序最佳拟合,我们试图将分配进一步挤压到内存空间,从而压缩它们。花几个 CPU 周期来做这件事,…(没听清),会发现有什么不同。
我们还缩小了最大的多区块载波的尺寸,这样做是希望统计数据对我们有利,这样我们就能释放更多的载波。结果也是如此。
我不知道他的 xxx(没听清,应该指的是内存使用率)现在具体是多少,但不是 20%,更像是 50%或 40%,这在很多情况下拯救了系统。因此,再也不用处理这些崩溃了。

Page 30

最近出现了一些新功能。
我刚才说的是 R16B01 中的池,因此只能从相同类型的东西中迁移。这是在调度器之间的迁移,但仍然是二进制到二进制的迁移,所以你还不能将二进制分配迁移到 ets 分配。这可能是我们最终要解决的问题。
因此,这并不能解决这个池的碎片问题,但当你在高负载情况下分配大量调度程序时,它能帮助你解决这个问题。之所以要介绍这一点,是因为我们有一个客户,当系统负载减少时,他的内存也在减少,这并不是你想要看到的,因为你曾有过一个高峰期,然后我们分配了大量内存,大量内存,然后负载缩小了,他只是在一个调度器中分配,但他无法使用其他调度器中的载体,因此该调度器的内存增加了,它无法对其他调度器进行去分配。因此,通过将这些载体迁移到调度器 1,我们解决了这个问题。顺便说一下,在 17.0 中这是默认开启的,所以请检查你的系统,看看你是否需要它,我们只看到了它的正面效果,但也有可能存在一些负面影响。
在 R16B03 中,我们有一种叫做超级载波的东西,erts_mmap 功能就是在这里添加的。因此,这是一种在启动时为 Erlang 虚拟机预分配大块内存的方法,这样我们就不必向操作系统申请内存。
我们之所以加入这个功能,是因为我们有一个虚拟化环境,我们的一个客户在这个环境中运行,我们做一次 malloc 需要大约 20 秒。所以耗时很长,而且有一次测试由于某种原因超时了。因此,我们将其安装到位,这样他们就可以预先分配 8 GB 内存,而不必在如此长的时间间隔内向内存系统请求内存。
它也可以用来限制 Erlang 虚拟机的内存使用量,但 ulimit 是比它更好的工具。

(问:为什么?为什么 ulimit 更好地…?)
答案是 结果是一样的。我认为操作系统一般更擅长管理这些事情,因为你有点…我们使用单块载体作为操作系统分配的原因是,它可以通过从缓存等处获取内存来作弊,如果我们使用 mmap 分配,我们就无法获得 malloc 使用的缓存等,所以我们无法访问系统中的所有内存。因此,如果使用 ulimit,就不会有这样的弊端,却能获得同样的好处。

问与答

  1. 问题:整个系统只有一个超级载波。那么 NUMA 架构是如何工作的?
    答:是的: 我想我们还没有做过任何基准测试,以了解其工作原理。关于如何使用载波等问题,我可能有点模糊,所以不能百分百确定,但在分配方面,我们会采取一些相同的做法。不知道,不幸的是这就是答案。

  2. 问:在 NUMA 系统中,载波的迁移是如何进行的?
    答:可以: 是的,肯定会有问题。你可以按照自己的意愿进行远程内存访问。但如果你不希望出现这种情况,很容易就可以关闭。在没有与 NUMA 进行大量交互的系统中,这样做是有益的。通常情况下,你不会希望出现这样的情况,即内存分配时没有从其他部分释放出来。

(调度程序中的进程迁移就有这个问题)。
是的,只要你做的是消息传递或交互环境…,我们就不会在 NUMA 节点中为进程分组,以免它们产生开销,而且如果你从一个进程向另一个 NUMA 节点中的某个进程发送消息,该消息的内存将保留在原来的 NUMA 节点中,所以无论如何,我们都有很多这样的事情。…

  1. 问:S+1 是什么意思?
    回答:是的: 我想我说的 S+1 就是…1 加…就是两个 1。
    线程零…然后 1 是第一个调度程序,2 是第二个调度程序,3 是第三个调度程序,以此类推。我可能在幻灯片中犯了一个错误,所以我会改正的。
    不,最后什么都没有,只是到最后一个调度程序结束。

  2. 问题 如果 NIF 使用驱动分配器?
    答:不是: 当然,如果你制作的二进制文件大于 64 字节,你就会在二进制分配器中结束。但 NIF 有直接在堆上构建文件的优势,这就是为什么它们在调用小文件时比驱动程序快一些。

当然,除非你超出了堆的大小,否则就会创建堆片段,它是堆分配器的一部分,但在不同的内存区域。
在进行垃圾回收时,所有片段都会被折叠。

5 问:在不阅读源代码的情况下,我在哪里可以找到关于这些东西的信息?
答:可以: 你可以读懂我的想法,还有两个人的想法你也可以读懂(笑)。为了解决碎片问题,我和弗雷德一起完成了部分工作…recon(听不清,就是一起开发了 recon)。它大概有 7、8 个函数,它们的数据分析了不同方面的事情,以及我们遇到的问题。我们正在尝试添加更多内容,但我发现你遇到的大多数问题都是独一无二的。这并不是说每个人都会遇到的普遍问题,因为那将是一个普遍的问题,而几乎都是某个人遇到的特殊问题,因为他们以特定的方式编写代码,所以才会出现这种情况。但对于碎片化部分,即发现有许多单块载波而不是多块载波,这个库中的函数可以做到这一点。
大量的文档解释了所有这些东西,文档几乎比工具还要好。我会这么说。因此,只需阅读文档,就能解释我在幻灯片中做的大部分事情。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值