GQBBBB / GQBBBB.github.io

My Blog
3 stars 0 forks source link

阅读:An Analysis of Performance Evolution of Linux's Core Operations #9

Open GQBBBB opened 4 years ago

GQBBBB commented 4 years ago

这篇Paper是SOSP 2019 session 11的一篇文章, 在这里可以找到PDF, 演讲PPT和视频.

个人感觉这篇还是比较好懂的, 可能我是菜鸡, 作者的实验思路, 方法, 成果都介绍的很清楚. 另外小姐姐演讲的时候一直在笑.

Summary

过去对OS性能工作大多集中于可伸缩性或特定工作负载的服务方面, 而本文则专注于linux最基本的:内核操作延迟.

通过分析过去7年41个版本的linux核心内核操作变化, 得出了目前大多数内核操作的性能都恶化或者出现大幅波动(例如, select操作比两年前慢了一倍)

并给出原因: 内核子系统容纳越来越多的安全性增强和新功能, 他们增加了内核开销. 并且简单而错误的配置也严重影响了内核的性能.

作者提出导致性能下降的11个原因. 并验证用户自主配置操作系统来达到性能, 功能, 安全三者平衡的可行性和重要性. 重新配置内核可以避免11项中的8项, 而其他3个可以通过简单补丁禁用.

以下使用KO代替kernel operations.

1 Intr

通过一个问题: 近些年来KO到底是变快了还是变慢了, 引出本文主题linux KO性能演变分析:

作者通过测试Apache spark, redis, PostgreSQL, Chromium brower和linux build toolchain中调用频率最高的KO, 选取了

  1. Context switch
  2. fork, Thread create
  3. Page fault
  4. read, write, mmap, munmap(I/O System call)
  5. send, recv, select, poll, epoll(Network System call) 这几种作为test写了一个叫LEBench的microbenchmark对linux's core operations进行测试.(PPT 第三页)

结果表明, 除了big-write和big-munmap外的, 所有KO都比四年前慢75%. 过去的七年中, 大多数慢了50%, 有一些(mmap, poll & select, send & recv)慢了100%.

作者找出了影响性能下降和波动的11个原因, 分为三类:

  1. 安全性增强
  2. 新功能(例如对容器和虚拟化的支持)
  3. 配置更改

前两种更改大都以复杂且侵入式的修改添加到内核中, 这KO速度的稳定下降以及在许多版本中持续存在的破坏性速度下降(例如, 在六个版本中持续出现的速度超过100%). 最后一种由于一些是简单的错误配置, 导致内核操作之间的严重速度下降.

本文做出以下贡献

  1. 全面分析了Linux核心内核操作的性能演变以及导致显着性能趋势下降的根本原因. 并且可以减轻两项安全增强功能的性能开销.
  2. LEBench, 从代表性工作负载中收集信息的microbenchmark, 以及能够评估一系列Linux版本性能的回归测试框架.
  3. 评估了11种已确定的根本原因对三个应用程序的影响, 它们可能导致Redis, Apache和Nginx的速度分别降低56%, 33%和34%.

2 method

使用k-best方法反复提取这些导致性能变化的原因: 对于每次测试,

  1. 首先, 确定最显着的导致性能变化的根本原因;
  2. 然后, 我们禁用根本原因. 重复此过程, 直到最慢和最快的内核版本之间的性能差异不超过10%.

3 result

11个导致KO性能下降的根本原因分为三类:安全增强(4/11), 新功能(4/11)和配置更改(3 / 11).

通过配置禁用11个根本原因中的8个, 而通过简单补丁可以禁用其他3个根本原因. 不需要新功能或安全保证的用户可以禁用它们, 以避免付出不必要的性能损失. 此外Linux附带的静态配置无法适应具有各种特征的工作负载. 在这种情况下, 可以通过主动的内核重新配置来避免内核配置错误(相对于工作负载)或Linux性能下降.

4 Performance impacting root causes

4.1 Security Enhancements

内核的四项安全增强功能导致LEBench的性能显着下降. 前两个是对最近发现的CPU漏洞的响应.后两个是为了防止错误的内核代码.

4.1.1 Remove Kernel Mappings in Userspace

KPTI是在内核版本4.14之后引入的, 是一个安全补丁, 旨在缓解Meltdown漏洞.

在修补程序之前, 内核和用户模式使用一个共享页表共享相同的地址空间, 其中内核内存通过要求更高的特权级别进行访问而受到保护. 但是此保护对Meltdown无效. 使用KPTI, 内核空间页表仍然包含内核和用户映射. 而用户空间页表则删除了绝大多数内核映射, 仅保留了为陷阱提供服务所需的最低限度的映射(例如, 用于系统调用和中断的处理程序).

保留两个单独的页表的开销很小. KPTI只需要为内核和用户页面表保留一个单独的顶层页面表副本;可以从内核页表访问用户页表中的所有较低级页表. 顶层页面表仅包含512个条目, 很少修改, 因此两个副本之间几乎不需要“同步”. KPTI最严重的开销来源是在在每次内核进入和退出时交换页面表指针时, 每次强制TLB刷新. 这导致两次写入页表指针寄存器(在Intel处理器上为CR3)的成本不变, 并且由于增加的TLB未命中而导致了成本变动. 使用KPTI时, 恒定成本的下限约为400–500个周期, 而没有使用KPTI时, 内核的进入和退出开销小于100个周期. 1TLB丢失的可变成本取决于不同工作负载的内存访问模式. 例如, small-read和big-read分别在TLB未命中处理程序上花费额外的700和6000个周期.

内核开发人员发布了针对KPTI修补程序可避免具有进程上下文标识符(PCID)功能的处理器上进行TLB刷新. 此功能允许使用与地址空间有关的唯一PCID标记每个TLB条目, 从而仅使当前活动PCID的条目被CPU使用. 内核开发人员使用此功能为内核和用户TLB条目分配单独的PCID, 因此内核不再需要在每个条目和出口上刷新TLB. 性能提升非常明显. 尽管避免了TLB刷新, 但我们发现恒定成本的下限KPTI的周期仍然是400-500个周期. 这是因为内核仍然需要在每个入口和出口写入CR3寄存器, 因为在Intel处理器上, 活动PCID存储在CR3的一部分中. 写入CR3的成本很高, 大约需要200个周期.
PCID优化可以使除med-munmap测试之外的所有测试受益, 在启用PCID的情况下, 其速度从18%增加到53%.

4.1.2 Avoiding Indirect Branch Speculation

Retpoline补丁在版本4.14中引入, 它绕过了处理器对间接分支的推测执行, 从而减轻了Spectre攻击的第二个变种.

该补丁会使一半的测试速度降低10%以上, 并严重影响select, poll和epoll测试的性能, 导致平均速度降低66%. 特别是poll和epoll的速度分别降低了89%和72%.

间接分支是跳转或调用指令, 其目标不是静态确定的, 只能在运行时解决. 一个示例是jmp [rax], 它跳转到rax寄存器中存储的地址. 现代处理器使用间接分支预测器在预测目标上推测性地执行指令. 但是, Intel和AMD处理器不能完全消除错误推测的所有副作用, 例如, 如第4.1.1节[1, 22]中所述将数据保留在缓存中. 攻击者可以通过仔细污染间接分支目标的历史记录来利用这种“旁通道”, 从而诱使处理器推测性地执行所需的分支目标.

Retpoline通过在编译过程中使用一系列指令(称为“ thunk”)替换每个间接分支来缓解Spectre v2. Retpoline造成的减速与测试中的间接跳转和调用的次数正相关. 每条这样的指令的惩罚类似于分支错误预测的惩罚. 如果没有Retpoline, 则select测试平均执行31个间接分支, 所有这些分支都是间接调用. 这些错误预测率低于30, 000分之一. 进一步的分析表明, 这些间接调用中的95%来自仅三个程序位置, 这些位置使用函数指针来调用特定资源类型的处理程序. 图5显示了程序位置之一, 该位置也位于poll和epoll的关键路径上. 在select的主循环中反复调用poll函数指针, 而实际目标则由文件类型(在我们的示例中为套接字)决定.

使用Retpoline, 可以执行所有31个间接分支被select替换为thunk, thunk中的ret总是会导致返回地址预测错误, 该错误预测具有30-35个惩罚周期, 导致测试总速度降低68%.

我们通过把每个间接调用转换为switch语句减轻了性能下降, 即Spectre-V2无法利用的直接条件分支. 图6显示了我们在图5所示程序位置上的补丁. 它在匹配资源类型后直接调用特定目标. 它将select的降幅从68%降低到5.7%, big-select的降幅从55%降低到2.5%. 此补丁程序还减少了Retpoline在poll和epoll上的开销.

4.1.3 SLAB Freelist Randomization

自4.7版引入以来, SLAB自由列表随机化增加了利用内核中的缓冲区溢出错误的难度.

SLAB是一块连续的内存, 用于存储大小相等的对象. 内核使用它来分配内核对象. 特定类型或大小的一组SLAB称为缓存. 例如, fork使用内核的SLAB分配器从mm_struct缓存中的SLAB分配mm_struct . 分配器使用“ freelist”来跟踪SLAB中对象的可用空间, 该列表是连接内存中相邻对象空间的链表. 结果, 一个接一个分配的对象将在内存中相邻. 攻击者可以利用这种可预测性来执行缓冲区溢出攻击.

SLAB自由列表随机化功能可将SLAB空闲列表中对象的可用空间顺序随机化, 以使列表中的连续对象在内存中不可靠相邻. 在初始化期间, 该功能为每个缓存生成一个随机数数组. 然后, 对于每个新的SLAB, 按照对应的随机数数组的顺序构造自由列表.

此补丁导致顺序访问大量内存的测试的显着开销. 它导致了big-fork的速度降低了37%, 而测试组(bigselect, big-poll和big-epoll)的平均速度降低了41%. 性能下降来自两个方面: 首先是花在初始化空闲列表上的时间. 特别是, big-fork花费了大约6%的执行时间来随机化自由列表, 因为它需要为新流程分配多个SLAB. 速度下降的第二个更重要的原因是由于将顺序对象访问模式转换为随机访问模式而导致的局部性差. 例如, big-fork的L3缓存未命中率增加了约13%.

4.1.4 Hardened Usercopy

自4.8版引入以来, 经过强化的usercopy补丁程序可验证在用户空间和内核之间复制数据时使用的内核指针.

没有此修补程序, 内核中的错误可能被利用来从用户空间复制太多数据时引起缓冲区溢出攻击, 或者在将过多数据复制到用户空间时导致数据泄漏. 此修补程序通过在每次复制操作期间对内核指针执行一系列健全性检查来防止此类错误. 但是, 这给已经验证了指针的内核代码增加了不必要的开销.

例如, 考虑select, 对于每个用户想要监视的每种事件类型, 它都会使用一组文件描述符. 调用时, 内核从用户空间复制该集合, 对其进行修改以指示发生了哪些事件, 然后将该集复制回用户空间. 在此操作过程中, 内核已检查内核内存分配是否正确, 并且仅复制与分配的字节数相同的字节. 但是, 强化用户复制补丁程序会为此过程添加一些冗余的完整性检查. 其中包括检查以下各项:i)内核指针不为空; ii)所涉及的内核区域不与文本段重叠;以及iii)如果对象是使用SLAB分配器分配的, 则其大小不超过其SLAB的大小限制. 为了评估这些冗余检查的成本, 我们仔细修补了内核以将其删除.

强化用户复制的成本取决于以下类型:复制的数据和数量. 作为选择, 检查成本增加了30ns的开销. 这会使测试速度减慢最多18%. poll的操作类似于select, 还必须在用户空间之间来回复制文件描述符和事件. 有趣的是, epoll由于复制的数据较少, 因此不会出现相同程度的减速;要监视的事件列表保留在内核中, 并且仅将已发生的事件复制到用户空间. 相反, read测试一次将一个页面复制到用户空间, 但是该页面不属于SLAB. 结果仅执行基本检查(例如检查有效地址), 每页复制仅花费5ns左右. 即使对于读取10, 000页的大阅读量, 这种开销来源也并不明显.

4.2 New Features

接下来的根本原因是新的内核功能. 其中之一即fault around(第4.2.1节), 实际上是一种优化. 它以某些特性为代价来提高具有某些特性的工作负载的性能. 禁用透明大页面(第4.2.3节)也可以提高某些工作负载的性能. 但是, 这些功能还给LEBench的微基准带来了不小的负担. 其他两个功能是主要用于虚拟化或容器化需求的新内核功能.

4.2.1 Fault Around

如果在生成错误时页面已加载到内存中, 但是在内存管理单元中未将页面标记为已加载到内存中, 则该页面称为次要页面错误或软页面错误.

在版本3.15中引入的“ fault around”是一项旨在减少次要页面错误数量的优化. 当所需页面不存在页表条目(PTE), 但该页面位于页面高速缓存中时, 将发生次要页面错误. 对于页面故障, fault around不仅尝试为故障页面建立映射, 而且还尝试为周围页面建立映射. 假设工作负载具有良好的局部性, 并且与所需页面相邻的多个页面驻留在页面缓存中, 则故障排除将减少后续的次要页面错误的数量. 但是, 如果这些假设不成立, 则故障排除可能会导致开销. 例如, Roselli等. 研究了几种文件系统的工作负载, 发现较大的文件倾向于随机访问, 这使得预取无益.

big-pagefault测试的速度降低了54%. big-pagefault通过访问较大的内存映射区域中的单个页面来触发页面错误. 处理此页面错误时, “故障排除”功能会在页面高速缓存中搜索周围的页面并建立它们的映射, 从而导致额外的开销.

4.2.2 Control Groups Memory Controller

在2.6版中引入的cgroup内存控制器记录并限制了不同控制组的内存使用情况. 控制组允许用户隔离不同进程组的资源使用情况.

它们是诸如Docker 和Linux Containers之类的容器化技术的基础. 此功能与内核的核心内存控制器紧密结合, 因此它可以记录每个页面的释放情况, 也可以将每个页面的分配给某个cgroup. 即使不使用cgroup功能, 它也会在大量使用内核内存控制器的测试上产生开销.

munmap测试由于页面释放期间增加的开销而经历了最显着的减慢. 尤其是在早于3.17版的内核中, big-munmap和med-munmap的速度分别降低了81%和48%.

有趣的是, 内核开发人员从cgroup引入6.5年后的3.17版本才开始优化cgroup的开销. 在munmap期间, 内存控制器需要从cgroup中“释放”内存使用量. 在版本3.17之前, 对每个已释放的页面进行一次卸载. 它还需要同步, 以使卸载和实际的页面重新分配保持原子性. 从版本3.17开始, 批量释放, 即对所有已删除的映射仅执行一次. 当从TLB取消映射无效时, 释放也会在以后发生, 因此不再需要同步. 因此在内核版本3.17之后, big-munmap和med-munmap的速度分别降低到9%和5%.

相比之下, 内存控制器仅增加页面错误测试的2.7%的开销. 处理页面错误时, 内存控制器首先确保cgroup的内存使用量将在页面分配后保持在其限制之内, 然后为页面的cgroup“收费”. 这里我们没有看到像munmap那样明显的速度下降, 因为在每个页面错误期间, 仅“收费”一页-内存控制器的开销仍然因处理页面错误本身的成本而微不足道. 相比之下munmap经常将多个页面取消映射, 从而聚集了很小的“释放”的成本. 请注意, mmap通常不受此更改的影响, 因为每个映射的页面在以后访问时均按需分配. 此外, read和write测试也不会受到影响, 因为它们使用页面缓存中的预分配页面.

4.2.3 Transparent Huge Pages

从版本3.13到4.6, 再从版本4.8到4.11启用, 透明大页面(THP)功能会自动调整默认页面大小[38]. 它分配2MB页面(大页面), 并且还具有一个后台线程, 该线程定期将最初分配给基本页面(4KB)的内存区域提升为大页面.

在内存压力下, THP可能会决定退回到4KB页面或通过压缩释放更多内存. THP可以减小页表的大小(因为page size变大, 所以页表项减少, 页表变小)并减少页错误的数量;它还增加了“ TLB覆盖率”, 因此减少了TLB miss的次数.

但是, THP也会对性能产生负面影响. 它可能导致大页面内部的碎片化. (与FreeBSD不同, Linux可以将具有未分配基础页的2MB区域升级为使用大页). 此外, 后台线程还可能导致开销. 考虑到这种折衷, 内核开发人员一直在反复讨论是否默认启用THP. 从4.8版到现在, 默认情况下禁用THP.

总的来说, THP对访问大量的内存有正面影响. 特别是在禁用了THP的版本中, huge-read速度减慢了83%. 值得注意的是, THP还减少了其他根本原因导致的速度下降. 例如, THP减少了内核页表隔离(第4.1.1节)的影响, 因为KPTI增加了每个内核陷阱的开销, 而THP减少了页表错误的数量.

4.2.4 Userspace Page Fault Handling

用户空间页面错误处理在版本4.6、4.8和更高版本中启用, 允许用户空间进程处理指定内存区域的页面错误. 这对于用户空间虚拟机监视器(VMM)来说可以更好地管理内存.

VMM可以通知内核将guest存储范围内的页面错误传递给VMM. 一种用途是用于虚拟机迁移, 以便可以一经请求就能迁移页面. 当guest VM页面出现故障时, 该故障将被传递到VMM, 然后VMM可以与远程VMM通信以获取页面.

总体而言, 除了big-fork测试(平均慢了4%)外, 用户空间页面错误处理带来的开销可忽略不计. 这是因为fork必须检查父进程中的每个内存区域以获取相关的用户空间页面错误处理信息, 并在必要时将其复制到子级. 当父级具有大量被映射的页面时, 此检查将变得很昂贵.

4.3 Configuration Changes

根本原因中的剩余三个是非最佳配置. 强制上下文跟踪(§4.3.1)是内核和Ubuntu开发人员的错误配置, 它导致此类情况下的最大速度下降. 另外两个是较旧的内核版本缺乏 针对我们实验中使用的较新硬件的规范 的结果, 因此导致做出的决策不是最优的. 虽然这反映了我们方法的局限性(即在新硬件上运行旧内核), 但这些错误配置可能会影响实际的Linux用户. 首先, 关于硬件规格的内核补丁可能无法及时发布:指定二级TLB大小的(简单)补丁直到发布Haswell处理器后六个月才发布. 新硬件的用户在某些工作负载上的时间可能会降低50%(第4.3.2节). 这种错误配置可能会影响具有第二级TLB的任何现代处理器. 此外, 流行的Haswell处理器系列的硬件规范并未反向移植到仍然声称仍受到积极支持的旧内核版本(第4.3.3节).

4.3.1 Forced Context Tracking

强制上下文跟踪(FCT)在版本3.10和3.12–15中错误地发布到内核中, 是一种调试功能, 用于开发另一项功能, 减少了调度时钟的滴答声. 尽管如此, 由于配置错误, FCT已在多个Ubuntu发行版内核中启用. 这在每次往返内核的过程中至少造成大约200–300ns的开销, 从而显着影响我们的所有测试(见图1). 平均而言, FCT将28项测试中的每项速度减慢50%, 其中7项速度减慢100%以上, 另外8项速度减慢25%至100%.

减少的调度时钟滴答(RSCT)功能允许内核以禁用向空闲的CPU内核或仅运行一项任务的内核传递计时器中断. 这样可以减少空闲内核的功耗, 并减少运行单个计算密集型任务的内核的中断. 但是原来必须在这些计时器中断期间正常完成的工作现在必须在其他用户内核模式转换(例如系统调用)期间完成. 这种工作称为上下文跟踪.

上下文跟踪涉及两项任务-CPU使用率跟踪并参与了只读复制更新(RCU)算法. 跟踪在用户空间和内核中花费了多少时间通常是通过计算计时器中断的数量来执行的. 如果没有计时器中断, 则必须在其他内核条目上执行此操作, 然后退出. 上下文跟踪还参与了RCU, RCU是提供无锁同步的内核子系统. 从概念上讲, 在RCU中每个对象都是不可变的. 写入对象时, 将对其进行复制和更新, 从而生成该对象的新版本. 由于写操作不会干扰现有的读操作, 因此可以随时执行. 但是, 仅当不再读取对象时, 才可以删除该对象的旧版本. 因此, 每次写操作还设置了一个回调, 以便在安全的情况下删除该对象的旧版本, 以供以后调用. reader在开始和结束阅读对象时会主动通知RCU. 通常RCU检查就绪的回调函数, 并在每个计时器中断时调用它们. 但是在RSCT下, 必须在其他内核入口和出口处执行此操作.

FCT在每个内核的每个用户内核模式转换上执行上下文跟踪, 即使在未启用RSCT的内核上也是如此. FCT最初是由Linux开发人员引入的, 用于在RSCT准备就绪之前测试上下文跟踪, 并通过RSCT自动启用. Ubuntu开发人员错误地在发行版中启用了RSCT, 因此无意中启用了FCT. 当这被报告为性能问题时, Ubuntu开发人员禁用了RSCT. 但是这仍然无法禁用FCT, 因为Linux开发人员即使在RSCT工作之后也意外地启用了FCT. 最初的错误配置后11个月, 由于另一个错误报告, 此问题仅在以后的Ubuntu发行版中得到修复.

4.3.2 TLB Layout Change

此补丁在内核版本3.14中引入, 通过使Linux能够识别较新的英特尔处理器上第二级TLB(STLB)的大小来提高性能.

知道TLB的大小对于确定在munmap期间如何使TLB条目无效至关重要. 有两种选择:一种是删除(即使无效)单个条目, 另一种是刷新整个TLB. 如果要删除的映射数相对于TLB的容量较小, 则应使用删除;而当要无效的条目数与TLB的容量相当时, TLB刷新则更好.

在引入此补丁之前, Linux使用的大小为第一级数据和指令TLB(在我们的测试机上为64个条目)作为TLB的大小, 并且不知道有1024个条目的较大的第二级TLB. 这导致错误的TLB无效决策:对于64的TLB容量, Linux计算刷新阈值为64/64 =1. 这意味着如果没有该补丁, 使多个条目无效将导致完整的TLB刷新. 结果, 由于增加了TLB miss, 删除10个条目的med-munmap测试在随后读取1024个页面的内存映射文件时会遭受多达50%的速度降低. 有了补丁TLB刷新阈值在我们的处理器上增加到16(1024/64), 因此med-munmap不再引起完全刷新.

但是, 该修补程序仅在最早的Haswell系列处理器版本发布六个月后发布. 请注意, small-munmap和big-munmap不会受到影响是因为内核仍然可以通过使small-munmap中的单个条目无效, 并刷新big-munmap中的整个TLB来做出正确的决定.

4.3.3 CPU Idle Power-State Support

此补丁在内核3.9版中引入, 使用 我们服务器使用的Haswell微体系结构 指定了Intel处理器的细粒度空闲省电模式.

现代处理器通过使它们的组件空闲来节省功耗. 据称, 随着更多组件的闲置, 处理器将进入更深的闲置状态, 并消耗更少的功率. 但是更深的空闲状态也需要更长的时间才能恢复, 并且这种等待时间会导致总体有效工作频率降低.

在此补丁之前, 内核仅识别粗粒度省电状态. 因此在尝试节省电量时, 它总是使处理器进入最深的空闲状态. 通过此补丁, 内核的空闲驱动程序可以控制处理器的电源管理, 并利用更轻的空闲状态. 这将有效频率提高了31%. 平均而言, 此修补程序将LEBench的速度提高了21%, 而CPU密集型选择测试的最高速度提高了31%.

虽然此补丁是在至强处理器发布之前发布的, 它没有向后移植到当时仍受支持的LTS内核(包括3.0、3.2和3.4). 这意味着, 为了在更新的硬件上获得最佳性能, 可能会迫使用户采用更新的内核产品线, 但代价是潜在的不稳定功能.

5 Macrobenchmark

要了解11种已确定的根本原因如何影响现实世界的工作量, 我们在测试LEBench的Linux内核中 评估了Redis, Apache HTTP Server 和Nginx Web server.

Redis的工作负载用于构建LEBench, 而其他两个应用程序的工作负载则用作验证. 我们分别使用Redis和Apache的内置基准Redis Benchmark和ApacheBench. 我们还使用ApacheBench评估Nginx. 每个基准配置为通过50个(对于Redis)或100个(对于Apache和Nginx)并发连接发出100, 000个请求.

所有这三个应用程序在内核上花费大量时间并表现出与LEBench相似的性能趋势. 对于每个测试, 吞吐量趋势倾向于与等待时间趋势相反. 为简便起见, 我们仅显示Redis Benchmark的三个最耗费内核的写测试, 它们负责从键值存储中插入(RPUSH)或删除(SPOP, RPOP)记录, 以及两个最耗费内核的读取测试, 用于返回值键(GET)并返回键的值范围(LRANGE).

我们禁用11个根本原因并评估它们对应用程序的影响. 总体而言, 禁用这11个根本原因可为三个应用程序带来显着的加速, 从而将Redis, Apache和Nginx的性能在所有内核中分别 最多提高56%, 33%和34%, 平均分别提高19%, 6.5%和10%. 四个更改-forced context tracking (§4.3.1), kernel page table isolation (§4.1.1), missing CPU idle power states (§4.3.3), and avoiding indirect jump speculation (§4.1.2)–占所有应用程序速度下降的88%. 这四个更改也对LEBench测试产生了最重大, 最广泛的影响. 其余影响性能的更改会带来更能容忍和稳定的开销来源:在所有内核中, 它们导致Redis的平均综合速度下降4.2%, Apache和Nginx的平均速度下降3.2%;这种观察再次与从LEBench获得的结果一致, 在这些测试中, 这些变化导致平均速度降低了2.6%. 值得注意的是, 这些更改可能会导致更大的个体波动-如果仅计算最差的内核, 则平均而言, 每个更改可能导致Redis, Apache和Nginx的速度分别降低5.8%, 11.5%和12.2%.

总的来说, 我们发现不仅宏基准和LEBench在总体性能趋势上显示出明显的重叠, 它们也以非常相似的方式受到11个更改的影响. 虽然我们无法对微基准测试的结果进行详细分析是有局限性的, 但微基准测试并不总是以与宏基准测试以相同的方式执行内核操作, 但我们注意到, 即使是不同的实际工作负载也不一定会完全相同地执行内核操作. 我们选择从一组具有代表性的实际工作负载中构建LEBench, 而我们的宏基准评估结果证实了LEBench的相关性.

6 Sensitivity Analysis

为了了解不同的硬件如何影响LEBench的结果, 我们在配备2.8GHz Intel i7-4810MQ处理器, 32GB 1600MHz DDR4内存和512GB SSD的笔记本电脑上重复测试.

§4中描述的11项更改中, 有10项对i7笔记本电脑上的LEBench具有类似的性能影响. Updating CPU idle states不会影响i7处理器的频率. 其他10个变化表明, 由于硬件速度不同, 对每台计算机的性能影响程度也不同. 例如, i7笔记本电脑具有更快的处理器和更低的内存. 因此, 由于randomizing the SLAB freelist导致的L3 miss次数增加而导致的速度降低 可能会被big-fork(在v4.6之后可见)夸大, 这可能是因为测试受内存限制. 此外, 由于过热导致CPU节流, 因此从笔记本电脑收集的结果中我们观察到更多性能差异.

7 Discussion

我们的发现表明内核性能调整可以发挥重要作用. 不幸的是, 对Linux内核进行彻底的性能调整会非常昂贵. 例如, RedHat和Suse通常需要6到18个月的时间来优化上游Linux内核的性能, 然后才能作为企业发行版发布. 更加困难的是, Linux是通用的OS内核, 因此必须支持各种硬件配置和工作负载. 除非考虑工作负载的特征, 否则许多形式的性能优化都没有意义. 例如, Google的数据中心内核针对其工作负载进行了精心的性能调整. 这项任务是由100多名工程师组成的团队执行的, 对于每个新内核, 这些工作也可能需要6到18个月的时间.

不幸的是, 这种重量级的性能调整过程无法跟上Linux的发展速度. 我们的研究发现Linux中增加了越来越多的功能和安全性增强功能. 实际上, Linux每2-3个月发布一次新内核, 每个版本包含13, 000至18, 000次提交. 据估计, 主流Linux内核平均每小时接受8.5次更改. 在如此紧张的时间表下, 每个版本实际上仅充当集成和稳定点;因此, 对于大多数内核版本, 内核或发行版开发人员不会进行系统的性能调整.

显然, 性能是要付出高昂代价的, 不幸的是这一代价很难克服. 大多数Linux用户负担不起像Google这样的大型企业用于自定义Linux性能调整的大量资源. 对于普通用户而言, 购买红帽企业Linux(RHEL)许可证可能更经济, 或者他们可能必须通过投资硬件(例如, 购买功能更强大的服务器或扩展其服务器)来弥补性能调整的不足. 池)以弥补速度较慢的内核. 所有这些事实都表明了内核性能的重要性, 而内核性能的优化仍然是一个艰巨的挑战.

8 Limitations

由于实际限制, 我们限制了我们的研究范围. 首先, 虽然LEBench测试是通过分析一组常用工作负载获得的, 但我们省略了许多其他类型的常用Linux工作负载, 例如HPC或虚拟化工作负载. 其次, 我们在研究中仅使用了两种机器设置, 并且都使用了英特尔处理器. 更全面的研究应该对其他类型的处理器体系结构进行采样, 例如, 在广泛部署Linux的嵌入式设备中使用的那些体系结构. 我们的研究集中在Linux上, 我们的结果可能并不适用于其他OS.

9 Related Work

先前分析核心OS操作性能的工作重点是比较不同体系结构上的相同OS或相同体系结构上的不同OS. 相比之下, 本文是第一个比较同一操作系统的历史版本, 系统分析了性能随时间变化的根本原因.

Ousterhout分析了一定范围内的操作系统性能得出结论, 由于处理器与其他设备的速度之间的差异越来越大, 操作系统无法以与处理器相同的速度增长. 安德森等进一步放大了处理器体系结构, 并详细分析了不同体系结构设计对操作系统的性能影响. Rosenblum评估了架构趋势对操作系统性能的影响. 他们发现, 尽管处理器速度更快, 缓存更大, 但是OS性能仍然受到磁盘I / O和多处理器内存的瓶颈. Chen和Patterson开发了一个自扩展I/O基准, 并用它来分析许多不同的系统. McVoy和Staelin开发了lmbench, 这是一个微基准, 可以测量各种OS操作. Brown和Seltzer进一步扩展了lmbench. lmbench中的大量测试仍然集中在测量不同的硬件速度上. 相比之下, 我们从当今常用的工作负载中选择了测试. 因此, LEBench可能与现代应用更相关.

其他人使用宏基准测试评估linux性能. Phoronix研究了Linux在多个版本上的性能, 但重点关注宏基准测试, 其中许多基准测试不是内核密集型的. 而且他们没有分析这些性能变化的原因. 在2007年, 英特尔的开发人员推出了一套使用微基准和宏基准测试的Linux回归测试框架, 该框架在候选版本中捕获了许多性能下降. 相反我们的研究集中在稳定版本的性能变化上, 这些变化在许多版本上仍然存在, 这很可能会影响实际用户.

其他研究分析了OS性能的其他方面. Boyd-Wickizer分析了Linux的可扩展性, 发现传统的内核设计可以在不改变体系结构的情况下进行扩展. Lozi发现Linux内核错误, 即使存在可运行的任务, 这些错误也导致内核保持空闲状态. Pillai发现Linux文件系统通常会为了确保良好性能而使用崩溃一致性(即, 确保在突然断电或系统崩溃的情况下可以正确恢复应用程序数据) 保证.

最后, Heiser和Elphinstone研究了过去20年中L4微内核的发展, 发现许多设计和实现选择已被淘汰, 因为它们过于复杂或缺乏灵活性, 或者使验证变得复杂.

10 Concluding Remarks

本文对Linux核心操作系统运行性能的演变进行了深入分析. 总体而言, 今天的大多数核心Linux操作都比几年前慢得多, 并且性能波动很普遍. 我们将大多数减速归因于11种更改, 这些更改分为三类:安全性增强, 新功能和配置错误. 详细研究每个更改, 我们发现可以通过更主动地进行性能测试和优化来缓解许多影响性能的更改. 通过自定义内核配置, 可以避免大多数性能影响. 这凸显了对内核性能调整及其潜在收益进行更多投资的重要性.


Q1: 为什么选Ubuntu进行测试?

在server market被最广泛使用.

为什么在一台机器上运行所有测试?

因为我们只对kernel software中的改变有兴趣.

Q2: 这些性能指标是否只用于单核操作, 如果可以的话可以应用到多核上吗?

增加可扩展性可能会增加这些操作延迟问题. 测试所有内核后, 发现导致性能下降的根本原因并没有和可扩展性相关.

Q3:看过所有版本性能变化后, 总体趋势向更坏方向发展?

因为很多安全性功能正在被添加至内核, 这似乎就是现在和趋势.

在幻灯片右边(红色部分)之前, 性能是否仍然有恶化趋势, 因为有些功能在有bug时就被添加?

在左侧, 导致性能下降增速最大的就是错误配置.

Q4:您如何评价有哪些发现是特定于Ubuntu, 以及哪个发现可以推广到linux发行版?

只有Forced Context Tracking是由于Ubuntu的错误配置, 其他的原因都属于所有linux, 并且misconfiguration实际上是缺少一定硬件规格..

Q5:为什么选4.0作为baseline, 不选之前的版本呢?

只是为了好看.