
为Linux管理员课程的学生准备了本文的翻译。
之前,我谈到了如何在Linux中测试和启用Hugepages。
仅当您确实有在哪里使用Hugepages时,本文才有用。 我遇到了许多人,他们被Hugepages神奇地提高生产力的前景所迷惑。 但是,巨传是一个复杂的主题,如果使用不当,则会降低性能。
第1部分:验证Linux中是否包含大页面( 此处为 )
问题:
您需要检查系统上是否启用了HugePages。
解决方案:
这很简单:
cat /sys/kernel/mm/transparent_hugepage/enabled
您将获得如下内容:
always [madvise] never
您将看到可用选项的列表( 始终为madvise,从不 ),而当前的活动选项将括在方括号中(默认情况下为madvise )。
madvise意味着transparent hugepages
仅包含在使用madvise(2)显式请求大页的内存区域中。
始终意味着始终为所有进程启用transparent hugepages
。 这通常可以提高性能,但是如果您有一个用例,其中许多进程消耗少量内存,则总内存负载可能会急剧增加。
绝不意味着即使使用madvise进行请求时也不会包含transparent hugepages
。 有关更多信息,请参见Linux内核文档 。
如何更改默认值
选项1 :直接更改sysfs
(重新启动后,该参数将恢复为默认值):
echo always >/sys/kernel/mm/transparent_hugepage/enabled echo madvise >/sys/kernel/mm/transparent_hugepage/enabled echo never >/sys/kernel/mm/transparent_hugepage/enabled
选项2 :通过使用修改后的配置重新编译内核来更改系统默认值(仅当您使用自己的内核时才建议使用此选项):
第2部分:HugePages的优点和缺点
我们将尝试有选择地解释使用Hugepages时的优点,缺点和可能的错误。 因为对于那些被认为是“灵丹妙药”的灵丹妙药而受骗的人来说,技术上复杂且学究的文章可能很难,所以为了简单起见,我会牺牲准确性。 值得记住的是,许多主题确实很复杂,因此大大简化了。
请注意,我们所谈论的是在Linux上运行的64位x86系统,我只是假设该系统支持透明的大页面(因为不替换大页面并不是不利条件),几乎在任何现代系统中都是如此。 Linux环境。
在下面的链接中,我将附上更多技术说明。
虚拟记忆体
如果您是C ++程序员,则知道内存中的对象具有特定的地址(指针值)。
但是,这些地址不一定反映内存中的物理地址(RAM中的地址)。 它们是虚拟内存中的地址。 该处理器具有一个特殊的MMU(内存管理单元)模块,可帮助内核将虚拟内存映射到物理位置。
这种方法有很多优点,但是最基本的是:
- 性能(由于各种原因);
- 程序隔离,即没有一个程序可以从另一个程序的内存中读取。
什么是页面?
虚拟内存分为页面。 每个单独的页面都指向一个特定的物理内存,它可以指向RAM中的一个区域,也可以指向分配给物理设备(例如视频卡)的地址。
您处理的大多数页面都指向RAM或交换,即它们存储在硬盘或SSD中。 内核控制每个页面的物理布局。 如果访问了伪造的页面,内核将停止尝试访问内存的线程,将页面从硬盘驱动器/ SSD读取到RAM中,然后继续执行该线程。
此过程对流是透明的,也就是说,它不一定直接从硬盘驱动器/ SSD读取。 普通页面的大小为4096字节。 大页面的大小为2 MB。
关联转换缓冲区(TLB)
当程序访问存储器页面时,中央处理器必须知道从哪个物理页面读取数据(即,具有虚拟地址映射)。
核心具有数据结构(页面表),该数据结构包含有关所用页面的所有信息。 使用此数据结构,您可以将虚拟地址映射到物理地址。
但是,页表相当复杂且运行缓慢,因此,每次进程访问内存时,我们根本无法分析整个数据结构。
幸运的是,我们的处理器具有TLB,可缓存虚拟地址和物理地址的映射。 这意味着尽管事实上我们需要在首次尝试获得访问权限时分析页面表,但所有后续页面调用都可以在TLB中进行处理,从而确保了快速操作。
由于它是作为物理设备实现的(因此速度很快),因此其容量有限。 因此,如果您想访问更多页面,TLB将无法存储所有页面的映射,因此您的程序将运行得慢得多。
大量的页面来解救
那么,如何避免TLB溢出呢? (我们假设程序仍然需要相同数量的内存)。
这是出现大页面的地方。 现在,TLB中的一个条目不再需要4096个字节,而仅需要一个条目,而现在却可以指向2兆字节。 我们将假设TLB有512个条目,在这里没有Hugepages可以匹配:
4096 b⋅512=2 MB
而我们可以与他们进行比较:
2 MB⋅512=1 GB
这就是Hugepages很棒的原因。 他们无需费力即可提高生产率。 但是有很大的保留。
大页面欺骗
内核会自动跟踪每页内存的使用频率。 如果物理内存(RAM)不足,则内核会将不太重要的页面(不经常使用的页面)移至硬盘驱动器,以释放一些RAM来存储更重要的页面。
基本上,Hugepages也是如此。 但是,内核只能交换整个页面,而不能交换单个字节。
假设我们有一个像这样的程序:
char* mymemory = malloc(2*1024*1024); // Hugepage! // mymemory - // , // mymemory // ... // putchar(mymemory[0]);
在这种情况下,内核将需要从硬盘驱动器/ SSD替换(读取)多达2 MB的信息,仅读取一个字节即可。 对于常规页面,仅需要从硬盘驱动器/ SSD读取4096字节。
因此,如果替换了一个巨大的页面,则仅当您需要访问整个页面时,其读取速度才会更快。 这意味着,如果您尝试随机访问内存的不同部分并仅读取几千字节,则应使用常规页面,而不必担心其他任何事情。
另一方面,如果您需要顺序访问大多数内存,那么大页面将提高您的生产率。 但是,您需要自己检查(而不是在抽象软件的示例中),然后看看能更快地工作。
内存分配
如果使用C语言编写,则知道可以使用malloc()
从堆中请求任意少量(或几乎任意大)的内存。 假设您需要30个字节的内存:
char* mymemory = malloc(30);
在程序员看来,您从操作系统“请求”了30个字节的内存,并返回了指向某个虚拟内存的指针。 但是实际上malloc ()
只是一个C函数,它从内部调用brk和sbrk函数以从操作系统中请求或释放内存。
但是,为每个分配请求越来越多的内存效率很低; 任何内存段很可能已经被释放(free())
,我们可以重用它。 malloc()
实现了相当复杂的算法来重用释放的内存。
同时,所有事情对于您来说都是被忽视的,那么为什么它会引起您的关注呢? 但是因为对free()
的调用并不意味着内存将立即返回给操作系统 。
内存碎片之类的东西。 在极端情况下,有些堆段只使用了几个字节,而两者之间的所有内容都被释放了(free())
。
请注意,内存碎片是一个非常复杂的主题,即使对该程序进行很小的更改也会对其产生重大影响。 在大多数情况下,程序不会引起显着的内存碎片,但是您应该记住,如果碎片发生在堆的特定区域中,那么大页只会加剧这种情况。
大页面的自定义应用
阅读本文之后,您已经确定了程序的哪些部分可以从使用大页面中受益,而哪些则不能。 那么应该完全包括大页面吗?
幸运的是,您可以使用madvise()
仅在有用的内存区域启用巨集。
首先,请按照本文开头的说明 ,验证大页面是否可以在madvise()模式下工作。
然后,使用madvise()
告诉内核确切地在哪里使用大型页面。
#include <sys/mman.h> // , size_t size = 256*1024*1024; char* mymemory = malloc(size); // hugepages… madvise(mymemory, size, MADV_HUGEPAGE); // … madvise(mymemory, size, MADV_HUGEPAGE | MADV_SEQUENTIAL)
请注意,此方法只是对内核进行内存管理的建议。 这并不意味着内核将自动为给定的内存使用大页面。
有关内存管理的更多信息,请参见madvise联机帮助页 ;有关该主题的madvise()
是非常陡峭的学习曲线,请参见。 因此,如果您打算真正理解它,请准备数周的阅读和测试,然后再指望至少获得一些积极的结果。
读什么?
有问题吗? 写评论!