BCLinux 6 性能调优手册


  • BCLinux Developers

    BCLinux 6 性能调优手册


    1 CPU

    1.1 CPU 拓扑

    1.1.1 使用 taskset 设置 CPU 亲和性

    taskset 搜索并设定运行进程的 CPU 亲和性(根据进程 ID)。它还可用于启动给定 CPU 亲和性的进程,这样就可将指定的进程与指定的 CPU 或者一组 CPU 绑定。但 taskset 不保证本地内存分配。如果您需要本地内存分配的额外性能利益,我们建议您使用 numactl,而不是 taskset。

    CPU 亲和性使用位掩码表示。最低位对应第一个逻辑 CPU,且最高位对应最后一个逻辑 CPU。这些掩码通常是十六进制,因此 0x00000001 代表处理器 0,0x00000003 代表处理器 3 和 1。
    要设定运行进程的 CPU 亲和性,请执行以下命令,使用处理器或者您要绑定到的处理器掩码替换 mask,使用您要更改亲和性的进程的进程 ID 替换 pid。

    # taskset -p mask pid  
    

    要启动给定亲和性的进程,请运行以下命令,使用处理器或者您要绑定的处理器的掩码替换mask,使用程序、选项以及您要运行的程序参数替换 program。

    # taskset mask -- program  
    

    除了将处理器指定为位码,您还可以使用 -c 选项提供逗号分开的独立处理器,或者一组处理器列表,类似如下:

    # taskset -c 0,5,7-9 -- myprogram   
    

    1.1.2 使用 numactl 控制 NUMA 策略

    numactl 使用指定的调度或者内存放置策略运行进程。所选策略是为那个进程及其所有子进程设定。numactl 还可以为共享内存片段或者文件设定永久策略,并设定 CPU 亲和性和进程的内存亲和性。它使用 /sys 文件系统设置系统拓扑。

    /sys 文件系统包含有关 CPU、内存和外设是如何通过 NUMA 互联连接的。特别是/sys/devices/system/cpu 目录中包含有关系统的 CPU 是如何互相连接的信息。/sys/devices/system/node 目录包含有关系统中 NUMA 节点以及那些节点间相对距离的信息。

    在 NUMA 系统中,处理器和内存条之间的距离越大,处理器访问那个内存条的速度就越慢。应将对性能敏感的程序配置为可以从最接近的内存条分配内存。

    还应将对性能敏感的程序配置为在一组核上执行,特别是在多线程程序的情况下。因为一级缓存一般都很小,如果在一个核中执行多个线程,每个线程有可能清除之前线程访问的缓冲的数据。当操作系统尝试在这些线程间执行多任务,且线程总是清除其他的缓存的数据时,则大部分的执行时间将用于缓存线替换。这个问题也称缓存贬值。因此建议您将多线程的程序绑定到节点而不是单一核,因为这样可以让线程在多个层级(第一、第二和最后一级缓存)共享缓存线,并尽量减小缓存填充操作的需要。但如果所有线程都访问同一缓存的数据,则将程序绑定到单一核可能获得高性能。

    numactl 可让您将程序绑定到特定核或者 NUMA 节点,同时在与那个程序关联的核或者一组核上分配内存。numactl 提供的一些有用选项有:

    --show  
    	显示当前进程的 NUMA 策略设置。这个参数不需要进一步的参数,且可按以下方式使用:numactl --show。  
    
    --hardware  
    	显示系统中可用节点清单。  
    
    --membind  
    	只从指定节点分配内存。当使用这个参数时,如果这些节点中的内存不足则分配会失败。这个参数的用法为 numactl --membind=nodes program,其中 nodes 是您要从中分配内存的节点列表,program 是要从那个节点分配内存的程序。节点号可以采用用逗号分开的列表、范围或者两者的结合方式提供。有关详情请参考 numactl man page: man numactl
    
    --cpunodebind  
    	只执行属于指定节点的 CPU 中的命令(及其子进程)。这个参数的用法为 numactl--cpunodebind=nodes program,其中 nodes 是指定程序(program)要绑定的 CPU 所属节点列表。节点号可以采用用逗号分开的列表、范围或者两者的结合方式提供。有关详情请参考 numactl man page: man numactl  
    
    --physcpubind  
    	只执行指定 CPU 中的命令(及其子进程)。这个参数的用法为 numactl --physcpubind=cpu program,其中 cpu 是用逗号分开的物理 CPU 号列表,这些数据在 /proc/cpuinfo 的processor 字段中显示,program 是应只在哪些 CPU 中执行的程序。还要将 CPU 指定为与当前cpuset 关联。详情请参考 numactl man page:man numactl。  
    
    --localalloc  
    	指定永远要在当前节点中分配的内存。 
    
    --preferred  
    	在可能的情况下分配到指定节点中的内存。如果内存无法分配到指定的节点,则返回其他节点。这个选项只能有一个节点号,例如:numactl --preferred=node。    
    

    1.1.3 NUMA 亲和性管理守护进程(numad)

    numad 是一个自动NUMA亲和性管理守护进程,它监控系统中的NUMA拓扑以及资源使用以便动态提高NUMA资源分配和管理(以及系统性能)。

    根据系统负载,numad 可对基准性能有 50% 的提高。要达到此性能优势,numad 会周期性访问 /proc文件系统中的信息以便监控每个节点中的可用系统资源。该守护进程然后会尝试在有足够内存和 CPU 资源的 NUMA 节点中放置大量进程以优化 NUMA 性能。目前进程管理阈值为至少是一个 CPU 的 50%,且至少有300 MB 内存。numad 会尝试维护资源使用水平,并在需要时通过在 NUMA 节点间移动进程平衡分配。

    numad 还提供放置前建议服务,您可以通过各种任务管理系统进行查询以便提供CPU初始绑定以及进程内存资源的支持。这个放置前建议服务无论系统中是否运行numad都可以使用。

    numad 有两种使用方法:
    * 作为服务使用
    * 作为可执行文件使用

    将 numad 作为服务使用
    在 numad 服务运行时,它将会尝试根据其负载动态调节系统。要启动该服务,请运行:

    # service numad start
    

    要让该服务在重启后仍保留,请运行:

    # chkconfig numad on
    

    将 numad 作为可执行文件使用

    要将 numad 作为可执行文件使用,请运行:

    # numad
    

    numad 将运行直到将其停止。在它运行时,其活动将被记录到 /var/log/numad.log 文件中。
    要将 numad 限制为管理具体进程,请使用以下选项启动它。

    # numad -S 0 -p pid
    

    要停止 numad,请运行:

    # numad -i 0
    

    停止 numad 不会删除它对改进 NUMA 亲和性所做的更改。如果系统有改动,再次运行 numad 将调整亲和性以便在新条件下提高性能。

    1.2 CPU调度

    为程序线程选择正确的调度程序策略不是简单的任务。通常应在关键时间或者需要迅速调度且不能延长运行时间的重要任务中使用实时策略。一般策略通常相对于实时策略有更好的数据吞吐量,因为它们让调度器更有效地调度进程(即他们不需要经常重新调度进程)。

    如果您要管理大量进程,且担心数据流量(每秒网络数据包,写入磁盘等等),那么请使用SCHED_OTHER,并让系统为您管理 CPU 使用。

    如果您担心事件响应时间(延迟),则请使用 SCHED_FIFO。如果您只有少量线程,则可以考虑隔离一个CPU插槽,并将线程移动到那个插槽的核中以便没有其他线程与之竞争。

    1.3 中断和IRQ调节

    /proc/interrupts 文件列出每个 CPU 上每个 I/O 设备的中断数。它显示出IRQ数,每个 CPU 核处理的中断数,中断类型,以及用逗号分开的注册为接收中断的驱动程序列表。

    IRQ 有一个关联的“亲和性”属性 smp_affinity,该参数包含允许为 IRQ 执行 ISR 的 CPU 核。这个属性还用来提高程序性能,方法是分配中断亲和性和程序的线程亲和性到一个或多个特定的CPU 核。这可在指定的中断和程序线程之间共享缓存。

    具体 IRQ 的中断亲和性保存在相应的 /proc/irq/IRQ_NUMBER/smp_affinity 文件中,您可以作为 root 用户查看并修改该值。保存在这个文件中的值是一个十六进制字节掩码,代表系统中所有 CPU核。

    例如:要为四核服务器指定以太网驱动程序,首先要确定与该以太网驱动程序关联的 IRQ 数:

    # grep eth0 /proc/interrupts
    32: 0 140 45 850264 PCI-MSI-edge eth0
    

    使用 IRQ 数定位正确的 smp_affinity 文件:

    # cat /proc/irq/32/smp_affinity
    f
    

    smp_affinity 的默认值为 f,即可为系统中任意 CPU 提供 IRQ。将这个值设定为 1,如下,即表示只有CPU 0 可以提供这个中断:

    # echo 1 >/proc/irq/32/smp_affinity
    # cat /proc/irq/32/smp_affinity
    1
    

    可使用逗号为不连续的 32 位组限定 smp_affinity 值。在有 32 个以上核的系统有这个要求。例如:以下示例显示在一个 64 核系统的所有核中提供 IRQ 40。

    # cat /proc/irq/40/smp_affinity
    ffffffff,ffffffff
    

    要只在 64 核系统的高 32 核中提供 IRQ 40,请执行:

    # echo 0xffffffff,00000000 > /proc/irq/40/smp_affinity
    # cat /proc/irq/40/smp_affinity
    ffffffff,00000000
    

    1.4 CPU频率管理器

    CPU的频率影响系统的性能和电源使用。cpufreq 管理器基于一些规则来决定一个 CPU 在特定时间的频率。

    大多数情况下建议使用 cpufreq_ondemand 管理器,因为它既提供了高负载下更好的性能,也提供了低负载下更好的电源使用。

    要追求最大的性能,可以使用 cpufreq_performance 管理器。这个管理器使用最高频率来尽快执行任务。不建议在数据中心和相似的部署中使用。

    配置管理器

    1 保证 cpupowerutils 已经安装

    # yum install cpupowerutils   
    

    2 检查驱动可用

    # cpupower frequency-info --governors
    

    3 如果驱动不可用, 可用modprobe 加载

    # modprobe cpufreq_ondemand
    

    4 用cpupower 临时设置管理器

    # cpupower frequency-set --governor ondemand
    

    2 内存

    2.1 HugeTLB

    将物理内存地址转译为性能内存地址是内存管理的一部分。物理地址和虚拟地址的映射关系保存在名为页表的数据结构中。因为为每个地址映射读取页表会很消耗时间和资源,所以最近使用的地址都有缓存。这个缓存就称为转译后备缓冲器(TLB)。

    但 TLB 只能缓存大量地址映射。如果所要求的地址映射没有在 TLB 中,则必须仍读取页表以决定物理到虚拟地址映射。这就是所谓的“TLB 缺失”。因为其内存要求与用来缓存 TLB 中地址映射的页面之间的关系,所以有大内存要求的程序比使用较少内存的程序更容易受 TLB 缺失影响。因为每个缺失都涉及页表读取,因此尽量避免这些缺失很重要。

    超大转译后备缓冲器(HugeTLB)可以很大片段管理内存,以便一次可以缓存更多地址。这样可减少 TLB
    缺失的可能性,进而改进有大内存要求的程序性能。

    有关配置 HugeTLB 的信息可在内核文档中找到:/usr/share/doc/kerneldoc-version/Docum entation/vm/hugetlbpage.txt

    2.2 THP

    BCLinux 也部署了透明超大页面 (THP)。THP 是一个抽象层,可自动创建、管理和使用超大页面的大多数方面。

    THP 系统管理员和开发者减少了很多使用超大页面的复杂性。因为 THP 的目的是改进性能,所以其开发者已在各种系统、配置、程序和负载中测试并优化了 THP。这样可让 THP 的默认设置改进大多数系统配置性能。

    注:THP 目前只能映射匿名内存区域,比如堆和栈空间。

    2.3 调节

    本小节总结了内存、内核以及文件系统容量,与每一部分相关的参数以及调节这些参数所涉及的代价。

    要在调节时临时设定这些值,请将所需值 echo 到 proc 文件系统中的适当文件中。例如:要将
    overcommit_memory 临时设定为 1,请运行:

    # echo 1 > /proc/sys/vm/overcommitmemory
    

    要永久设定这些值,则需要使用 sysctl 命令。

    2.3.1 内存可调参数

    以下参数位于 proc 文件系统的 /proc/sys/vm 目录中。

    overcommit_memory
    	规定决定是否接受超大内存请求的条件。这个参数有三个可能的值:
    	0 — 默认设置。内核执行启发式内存过量使用处理,方法是估算可用内存量,并拒绝明显无效的请求。遗憾的是因为内存是使用启发式而非准确算法计算进行部署,这个设置有时可能会造成系统中的可用内存超载。
    	1 — 内核执行无内存过量使用处理。使用这个设置会增大内存超载的可能性,但也可以增强大量使用内存任务的性能。
    	2 — 内存拒绝等于或者大于总可用 swap 大小以及 overcommit_ratio 指定的物理 RAM 比例的内存请求。如果您希望减小内存过度使用的风险,这个设置就是最好的。
    
    overcommit_ratio
    	将 overcommit_memory 设定为 2 时,指定所考虑的物理 RAM 比例。默认为 50。
    
    max_map_count
    	规定某个进程可能使用的最大内存映射区域。在大多数情况下,默认值 65530 就很合适。如果您的程序需要映射比这个文件数更多的文件可增大这个值。  
    
    nr_hugepages
    	规定在内核中配置的超大页数。默认值为 0。只有系统中有足够的连续可用页时方可分配(或者取消分配)超大页。为这个参数保留的页无法用于其他目的。安装的文件/usr/share/doc/kerneldoc-kernel_version/Docum entation/vm /hugetlbpage.txt 中有详细的内容。
    

    2.3.2 内核可调参数

    以下参数位于 proc 文件系统的 /proc/sys/kernel/ 目录中。

    msgmax
    以字节为单位规定信息队列中任意信息的最大允许大小。这个值一定不能超过该队列的大小(msgmnb)。默认值为 65536。
    
    msgmnb
    以字节为单位规定单一信息队列的最大值。默认为 65536 字节。
    
    msgmni
    规定信息队列识别符的最大数量(以及队列的最大数量)。64 位架构机器的默认值为 1985;32位架构机器的默认值为 1736。
    
    shmall
    以字节为单位规定一次在该系统中可以使用的共享内存总量。64 位架构机器的默认值为4294967296;32 位架构机器的默认值为 268435456。
    
    shmmax
    以字节为单位规定内核可允许的最大共享内存片段。64 位架构机器的默认值为 68719476736;32 位架构机器的默认值为 4294967295。注:但内核支持的值比这个值要多得多。
    
    shmmni
    规定系统范围内最大共享内存片段。在 64 位和 32 位架构机器中的默认值都是 4096。
    
    threads-max
    规定一次内核使用的最大线程(任务)数。默认值与 max_threads 相同.
    

    2.3.3 文件系统可调参数

    以下参数位于 proc 文件系统的 /proc/sys/fs/ 目录中。

    aio-max-nr
    规定在所有活动异步 I/O 上下文中可允许的最多事件数。默认值为 65536。注:更改这个值不会预先分配或者重新定义内核数据结构大小。  
    
    file-max
    列出内核分配的文件句柄最大值。默认值与内核中的 files_stat.max_files 映射,该参数可将最大值设定为 (mempages * (PAGE_SIZE / 1024 )) / 10 或者 NR_FILE(在红帽企业版
    Linux 中是 8192)。增大这个值可解决由于缺少文件句柄而造成的错误。  
    

    2.3.4 OOM可调参数

    内存不足(OOM)指的是所有可用内存,包括 swap 空间都已被分配的计算状态。默认情况下,这个状态可造成系统 panic,并停止如预期般工作。但将 /proc/sys/vm/panic_on_oom 参数设定为 0 会让内核在出现 OOM 时调用 oom_killer 功能。通常 oom_killer 可杀死偷盗进程,并让系统正常工作。

    可在每个进程中设定以下参数,提高您对被 oom_killer 功能杀死的进程的控制。它位于 proc 文件系统中 /proc/pid/ 目录下,其中 pid 是进程 ID。

    oom_adj
    定义 -16 到 15 之间的一个数值以便帮助决定某个进程的 oom_score。oom_score 值越高,被oom_killer 杀死的进程数就越多。将 oom_adj 值设定为 -17 则为该进程禁用 oom_killer。
    

    2.4 调整虚拟内存

    虚拟内存一般由进程、文件系统缓存以及内核消耗。虚拟内存的使用由很多因素决定,受以下参数影响:

    swappiness
    	参数值可为 0-100,控制系统 swap 的程序。高数值可优先系统性能,在进程不活跃时主动将其转换出物理内存。低数值可优先互动性并尽量避免将进程转换处物理内存,并降低反应延迟。默认值为 60。  
    
    min_free_kbytes
    	保证系统间可用的最小 KB 数。这个值可用来计算每个低内存区的水印值,然后为其大小按比例分配保留的可用页。
    
    dirty_ratio
    	规定百分比值。当脏数据组成达到系统内存总数的这个百分比值后开始写下脏数据(pdflush)。默认值为 20。
    
    dirty_background_ratio
    	规定百分比值。当脏数据组成达到系统内存总数的这个百分比值后开始在后端写下脏数据(pdflush)。默认值为 10。
    
    drop_caches
    	将这个值设定为 1、2 或者 3 让内核放弃各种页缓存和 slab 缓存的各种组合。  
    	1
    		系统无效并释放所有页缓冲内存。  
    	2
    		系统释放所有未使用的 slab 缓冲内存。  
    	3
    		系统释放所有页缓冲和 slab 缓冲内存。
    
    	这是一个非破坏性操作。因为无法释放脏项目,建议在运行 sync 设定这个参数值。
    

    3 输入输出

    3.1 配置调度器

    首先要决定使用那个 I/O 调度程序。本小节概述了主要调度程序以帮助您确定最适合您负载的那一款。

    3.1.1 完全公平调度(CFQ)

    CFQ 尝试根据启动 I/O 的进程决定公平的 I/O 调度。可提供三个不同的调度等级:实时(RT),最佳效果(BE)和闲置。可使用 ionice 命令手动分配调度等级,或者使用 ioprio_set 系统调用编程分配。默认情况下将进程设定为最佳效果调度等级。在实时调度等级和最佳效果调度等级中又分为八个 I/O 优先级,其中 0 代表最高优先权,7 代表最低优先权。采用实时调度等级的进程比采用最佳效果和闲置等级的进程会被更频繁地调度,因此所有调度的实时 I/O 总是在最佳效果或者闲置 I/O 前执行。这意味着实时优先权 I/O可使最佳效果和闲置等级的 I/O 饥饿。最佳效果调度是默认调度等级,并且等级中的默认优先权为 4。闲置调度等级中的进程只有在系统中没有其他等待处理的 I/O 时才会执行。因此请记住只有在进程 I/O 完全不需要向前进行时方可将进程调度等级设定为闲置。

    CFQ 通过为每个执行 I/O 的进程分配时间片段提供公平。在其时间片段中,进程每次最多可有八个请求(默认)。调度程序会尝试根据历史数据估计某个程序是否会在近期发出更多 I/O,然后 CFQ 会闲置,等待那个I/O,即使有其他进程正在等待发出 I/O。

    由于 CFQ 执行的闲置操作通常并不适合哪些有查询惩罚(seek penalty)的硬件,比如快速的外置存储阵列或者固态硬盘。如果要求在此类存储中使用 CFQ(例如:如果您还喜欢使用 cgroup 比例 I/O 调度程序),则需要调节一些设置以改进 CFQ 性能。请在 /sys/block/device/queue/iosched/ 中同一名称的文件中设定以下参数:

    slice_idle = 0 
    quantum = 64  
    group_idle = 1  
    

    将 group_idle 设定为 1 后,可能会产生 I/O 停止(而后端存储并不繁忙)。但这些停止的情况并不比系统队列中的闲置情况出现得频繁。

    CFQ 是非工作守恒(non-working-conserving)I/O 调度程序,也就是说即使在有等待处理的请求时也可以闲置(如前所述)。非工作守恒调度程序可在 I/O 路径中引入很大的延迟。例如在基于主机的硬件RAID 控制器顶层使用 CFQ。该 RAID 可部署其自身的非工作守恒调度程序,因此可在该栈的两级中造成延迟。非工作负载调度程序在有尽可能多的数据供其操作时表现最佳。使用该调度算法时,最底层的调度程序只能看到上层调度程序发送的数据。因此底层看到的 I/O 模式并不完全代表实际负载。

    可调参数

    back_seek_max
    	反向查询通常对性能有负面影响,因为它比正向查询标头重置时间要长很多。但如果负载较小,则CFQ 仍执行此查询。这个可调参数以 KB 为单位控制 I/O 调度程序允许反向查询的间距。默认为16 KB。
    
    back_seek_penalty
    	由于反向查询的效率低,每项反向查询都有惩罚与之关联。惩罚是一个乘数。例如:磁头位置在1024KB。假设在队列中有两个请求,一个在 1008KB,一个在 1040KB。这两个请求到当前磁头位置登距。但如果应用反向查询惩罚(默认:2),则磁盘中距离较远的请求现在与较近的请求的距离缩短了一倍。因此磁头将向前移动。
    
    fifo_expire_async
    	这个可调参数控制异步(缓存写入)请求等待的时间长度。过期后(以毫秒计)会将无法满足的异步请求移动到调度表中。默认为 250 毫秒。
    
    fifo_expire_sync
    	这个参数与 fifo_expire_async 相同,用于同步请求(读取和 O_DIRECT 写入)。默认为 125 毫秒。
    
    group_idle
    	设定后,CFQ 会在最后一个进程 cgroup 中发出 I/O 后闲置。当使用比例 I/O cgroup 并将 slice_idle 设定为 0 后将其设定为 1(通常用于快速存储)。
    
    group_isolation
    	如果启用组隔离(设定为 1),它可以以吞吐量为代价提供组群间更强大的隔离。一般来说,如果禁用组隔离,则只为连续负载提供公平机制。启用组隔离则会为连续以及随机负载提供公平机制。默认值为 0(禁用)。详情请参考 Docum entation/cgroups/blkio-controller.txt。
    
    low_latency
    	启用低延迟后(设定为 1),CFQ 会尝试为设备中每个发出 I/O 的进程提供最长 300 ms 的等待时间。禁用低延迟(设定为 0)可忽略目标延迟,这样就可允许系统中的每个进程获得全部时间片段。默认启用低延迟。
    
    quantum
    	quantum 参数控制 CFQ 每次向该存储发出的 I/O 数,主要是限制设备队列深度。默认情况下将其设定为 8。该存储可能支持更深的队列深度,但增加 quantum 还将对延迟产生负面影响,特别是有大量连续写操作的时候。
    
    slice_async
    	这个可调参数控制分配给每个发出异步(缓存写入)I/O 的进程的时间片段。默认将其设定为 40毫秒。
    
    slice_idle
    	这个参数指定 CFQ 在等待进一步请求时应闲置的时间。BCLinux 6.1 以及更早版本中的默认值为 8 毫秒。在BCLinux 6.2 以及之后的版本中默认值为 0。这个 0 值可通过删除队列以及服务树层中的所有闲置提高外置 RAID 存储流量。但 0 值可降低内置非 RAID 存储的流量,因为它会增加查询总量。对于非 RAID 存储建议您将 slice_idle 值设定在 0 以上。
    
    slice_sync
    	这个可调参数专门用于发出同步(读取或者直接写入)I/O 进程的时间片段。默认值为 100 毫秒。
    

    3.1.2 最后期限 I/O 调度程序

    最后期限 I/O 调度程序尝试为请求提供保证的延迟。请注意只有当请求进入 I/O 调度程序后方开始计算延迟(这个区别非常重要,因为程序可能会进入睡眠等待模式以便释放请求描述符)。默认情况下读取比写入的优先权高,因为程序更容易因读取 I/O 而被阻断。

    最后期限调度以批形式分派 I/O。一批是一些列连续的读或者写 I/O,按 LBA 顺序递增(单向递增)。处理完每批进程后,I/O 调度程序会检查是否有写请求已等待太久,然后决定是否开始新一批读或者写操作。只在开始新一批时才检查过期请求的FIFO 列表。因此,如果选择批写入,且同时有过期的读取请求,那么只有在批写入完成后方可执行读取请求。

    可调参数

    fifo_batch
    	这样可以决定单一批次中发出的读取或者写入数。默认为 16。设为更高的数值可获得更好的流量,但也会增加延迟。
    
    front_merges
    	如果负载永远不会生成预合并,则您可以将这个可调参数设定为 0。除非您已了解这个检查的代价,建议将其设定为默认值,即 1。
    
    read_expire
    	这个可调参数可让您以毫秒为单位设定读取操作的速度。默认将其设定为 500 毫秒(即半秒)。
    
    write_expire
    	这个可调参数可让您以毫秒为单位设定写入操作的速度。默认将其设定为 5000 毫秒(即五秒)。
    
    writes_starved
    	这个可调参数控制处理写入批之前可以处理多少读取批。这个值越高,越倾向于读取操作。
    

    3.1.3 Noop

    Noop I/O 调度程序采用先入先出(FIFO)调度算法。合并原始块层中的请求,但只是一个最后命中缓存(last-hit cache)。如果系统与 CPU 绑定,且使用高速存储,这就是最佳的 I/O 调度程序。
    以下是块层中可以使用的可调参数。

    /sys/block/sdX/queue 可调参数

    add_random
    	在某些情况下,为 /dev/random 做贡献的 I/O 事件的成本是不能忽略的。在这种情况下要求将其设定为 0。
    
    max_sectors_kb
    	默认将发送到磁盘的最大请求设定为 512 KB。这个可调参数可用来增大或者减小该值。最小值为逻辑块大小;最大值由 max_hw_sectors_kb 设定。有些 SSD 会在 I/O 大小超过内部删除块大小时性能下降。在此类情况下建议将 max_hw_sectors_kb 降低到删除块大小。您可以使用类似iozone 或者 aio-stress 的 I/O 生成程序对此进行测试,记录大小从 512 字节到 1 MB 不等。
    
    nomerges
    	这个可调参数主要用于故障排除。大多数负载都可从请求合并中获益(即使类似 SSD 的高速存储也是如此)。但在有些情况下要求禁用合并,比如当您要查看后端存储可处理多少 IOPS 而无需禁用预读或者执行随机 I/O 时。
    
    nr_requests
    	每个请求队列都有为每个读和写 I/O 分配的请求描述符总数限制。这个数的默认值为 128,即在将某个进程转入睡眠模式时可将 128 个读和 128 个写操作。转入睡眠模式的进程是下一个要分配请求描述符的进程,不一定是分配完所有可用请求描述符的进程。
    
    	如果您有一个延迟敏感的程序,则应考虑在您的请求队列中降低 nr_requests 值,并将命令队列深度降低到较低的数值(甚至可以降低为 1),这样写回 I/O 就无法分配所有可用请求描述符并用写 I/O 填满该设备队列。分配 nr_requests 后,所有其他尝试执行 I/O 的进程都会转入睡眠模式等待请求可用。这样更为公平,因为这样会以轮循模式分配请求(而不是让一个进程很快消耗完所有资源)。注:只有在使用最后期限或者 noop 调度程序时才会有此问题,因为默认 CFQ 配置可防止出现此类情况。
    
    optimal_io_size
    	在有些情况下,底层存储会报告最佳 I/O 大小。这在硬件和软件 RAID 中很常见,其中最佳 I/O 大小是条带大小。如果报告该值,则程序应该发出等于或成倍数的最佳 I/O 大小的 I/O。
    
    read_ahead_kb
    	操作系统可探测到程序从文件或者磁盘中连续读取数据。在这种情况下,它可执行智能预读算法,把用户要求的数据从磁盘中读取处出来。这样当用户继续读取数据块时,它已经在操作系统的页缓存中了。潜在的缺点是操作系统可能从磁盘中读取过多数据,这样就会占用页缓存直到内存压力过大将其清除。如果有多个进程执行错误的预读就会增加内存压力。对于设备映射设备,一般应该增大 read_ahead_kb 值,比如 8192。理由是设备映射设备通常有多个基础设备组成。将其设定为默认的值(128 KB)然后乘以要映射的设备数是个好的调整起点。
    
    rotational
    	传统硬盘一般都采用轮换模式(比如转盘),但 SSD 不是。大多数 SSD 会声明这一点。但如果您遇到的设备没有说明有此功能,则可能需要手动将轮换模式设定为 0;禁用轮换模式后,I/O 电梯程序就不使用减少查询的逻辑操作,因为在非轮换介质中几乎没有查询操作惩罚。
    
    rq_affinity
    	可在与发出 I/O 不同的 CPU 中处理 I/O。将 rq_affinity 设定为 1 可让内核向发出 I/O 的 CPU传递完成信息。这样可以改进 CPU 数据缓存效果。
    

    4 文件系统

    4.1 注意事项

    在所有文件系统有一些通用的注意事项:文件系统中选择的格式化和挂载选项,程序可使用的提高其所在系统中性能的操作。

    4.1.1 格式化选项

    文件系统块大小

    可在执行 mkfs 时选择块大小。不同的系统其有效范围各有不同:上限为主机系统的最大页大小,下限取决于所使用的文件系统。默认块大小适用于大多数情况。

    如果您希望创建大量小于默认块大小的块,您可以设定较小的块大小以尽量减少磁盘空间浪费。注:但设定较小的块大小可能会限制该文件系统中的最大块,并可以造成额外运行费用,特别是对那些比所选块大小更大的块。

    文件系统几何学

    如果您的系统使用带状存储,比如 RAID5,您可以通过在执行 mkfs 时将数据和元数据与基础存储的几何结构对齐以提高性能。对于软件 RAID(LVM 或者 MD)以及有些企业级存储,可查询并自动设置这些信息,但在很多情况下必须由管理员在命令行中使用 mkfs 手动设定。

    外部日志

    需要大量使用元数据的负载意味着日志文件系统(比如 ext4 和 XFS)的 log 会非常频繁地更新。要尽量减少文件系统查询日志的时间,您可以将日志放在专用存储中。注:如果将日志放在速度比主文件系统慢外部存储中可抵消所有可能的与使用外部存储有关的优势。外部日志在运行 mkfs 时创建,并要在挂载时指定日志设备。有关详情请参考mke2fs(8)、mkfs.xfs(8) 和 mount(8) man page。

    4.1.2 挂载选项

    Barriers

    写入 barrier 是保证在永久存储中正确写入并排列文件系统元数据的内核机制,即使在存储设备会经常断电的情况也不例外。启用了写入 barrier 的文件系统还可以保证在断电时保存使用 fsync() 进行的所有数据传输。BCLinux 默认在所有支持此功能的硬件上启用 barrier。

    但启用写入 barrier 可显著延缓一些程序的速度,特别是使用很多 fsync() 的程序,或者延缓创建和删除大量小文件的速度。对于没有不稳定写入缓存的存储,或者罕见的文件系统不一致以及断电后出现可以承受的数据丢失的情况,可使用 nobarrier 挂载选项禁用 barrier。

    访问时间(noatime)

    以前在读取文件时,对那个文件的访问时间(atime)必须在 inode 元数据中更新,这样就造成额外的 I/O写入操作。如果不需要准确的 atime 元数据,则请使用 noatime 选项挂载该文件系统以便消除这些元数据更新。但在大多数情况下,由于BCLinux 6内核的默认相对 atime(或者 relatime)行为,atime不是一个大的消耗。relatime 行为只在原有 atime 比修改时间(mtime)或者状态更改时间(ctime)旧时更新 atime。

    增加的预读支持

    预读可通过预取数据并将其载入页面缓存以提高文件访问速度。有些负载,比如那些涉及连续 I/O 大量流操作的负载可得益于高的预读值。

    4.1.3 文件系统维护

    丢弃不使用的块

    批丢弃和在线丢弃操作是根据文件系统的功能丢弃那些文件系统没有使用的块。这些操作在固态硬盘和精简配置存储中很有帮助。

    批丢弃操作由用户明确使用 fstrim命令运行。这个命令丢弃文件系统中所有匹配用户标准的未使用的块。在BCLinux 6.2 以及之后的版本上 XFS 和 ext4 文件系统都支持这两种操作类型,只要文件系统下面的基础设备支持物理丢弃操作。只要 /sys/block/device/queue/discard_max_bytes 不为零就表示支持物理忽略操作。

    在线丢弃操作是在挂载时使用 -o discard 选项指定(可以是在 /etc/fstab 中或者使用 mount 命令),并实时运行而无需任何用户干预。在线丢弃操作只丢弃那些正在从使用转换到可用状态的块。BCLinux 6.2 以及之后版本中的 ext4 文件系统以及BCLinux 6.4 以及之后版本中的 XFS 文件系统支持在线忽略操作。

    建议使用批丢弃操作,除非系统负载不可使用批丢弃,或者需要使用在线丢弃操作以保持性能。

    4.1.4 应用程序注意事项

    预分配

    ext4、XFS 和 GFS2 文件系统支持使用 glibc fallocate(2) 调用来有效的空间预分配。由于写入模式造成大量碎片的文件中会导致读取性能极差,这种情况下空间预分配是一个很有用的技术。预分配可将磁盘空间标记为已分配给某个文件而无需向这个空间中写入任何数据。实际数据写入预分配空间前,读取操作将返回 0。

    4.2 文件系统

    4.2.1 Ext4 文件系统

    ext4 文件系统是BCLinux 6 中默认的文件系统,支持最大文件系统为 16TB,单一文件最大值为16TB。它还去除了 ext3 中最多只能有32000 个子目录的限制。

    ext4 文件系统的默认设置是大多数负载是最佳化的,但如果性能分析显示文件系统影响到性能,则可以使用以下几个调节选项:

    inode表初始化

    对于超大的文件系统,mkfs.ext4 进程要花很长时间初始化文件系统中到所有 inode 表。可使用 -E
    lazy_itable_init=1 选项延迟这个进程。如果使用这个选项,内核进程将在挂载文件系统后继续初始化该文件系统。可使用 mount 命令的 -o init_itable=n 选项控制初始化的比例,其中执行这个后台初始化的时间约为 1/n。n 的默认值为 10。

    Auto-fsync 行为

    因为在重命名、截取或者重新写入某个现有文件后,有些应用程序不总是执行 fsync(),所以在重命名和截取操作后,ext4 默认自动同步文件。这个行为与原有到 ext3 文件系统行为大致相同。但 fsync()操作可能会很耗时,因此如果不需要这个自动行为,请在 mount 命令后使用 -o noauto_da_alloc 选项禁用它。这意味着该程序必须明确使用 fsync() 以保证数据一致。

    日志 I/O 优先权

    默认情况下,日志提交 I/O 比普通 I/O 的优先权稍高。这个优先权可使用 mount 命令的 journal_ioprio=n 选项控制。默认值为 3。有效值范围为 0-7,其中 0 是最高优先权 I/O。
    其他 mkfs 和调节选项详情请参考 mkfs.ext4 (8) 和 mount(8) man page,同时 kernel-doc 软件包的Documentation/filesystems/ext4.txt 文件也有相关信息。

    4.2.2 XFS 文件系统

    基本调节

    通常默认的 XFS 格式和挂载选项对大多数负载都是最佳选择。建议使用默认值,除非具体配置更改可以对文件系统负载有帮助。如果使用软件 RAID,mkfs.xfs 命令可自动使用正确的条带单位和宽度自行配置以与硬件对应。如果使用硬件 RAID 就需要手动进行配置。

    在大容量(几TB)文件系统中建议使用 inode64 挂载选项,除非是使用 NFS 导出该文件系统和传统 32 位 NFS 客户端需要对该文件系统进行访问。
    建议在经常修改或者迅速增长的文件系统中使用 logbsize 选项。默认值为 MAX(32 KB,日志条带单位),同时最大值为 256 KB。建议在大量修改的文件系统中使用 256 KB。

    高级调节

    更改 XFS 参数前,您需要理解为什么默认 XFS 参数会造成性能问题。这包括理解您的程序在做什么,以及该文件系统如何应对那些操作。

    可以通过调整来修正或者减少的性能问题一般是由文件碎片或资源竞争造成的。处理这些问题有不同的方法,但在有些情况下修复问题需要修改程序配置,而不是修改文件系统配置。

    优化大量文件

    XFS 引入了文件系统中文件数目的限制。通常这个限制不会被触发。如果您知道默认限制无法满足未来的需要,您可以使用 mkfs.xfs 命令增加可使用的 inode 文件系统空间的百分比。如果您在创建文件系统后达到文件限制(通常在尝试创建文件或者目录时出现 ENOSPC 错误信息,即使有可用空间),您可以使用 xfs_growfs 命令调整该限制。

    优化单个目录中的大量文件

    文件系统的目录块大小是固定的,且无法更改,除非最初使用 mkfs 格式化。最小的目录块是文件系统块大小,默认为 MAX(4 KB,文件系统块大小)。通常不必减少目录块大小。

    因为目录结构是基于 b-tree 的,更改块大小会影响每个物理 I/O 可检索或者修改的命令信息的数量。目录越大,在特定块大小的操作需要的 I/O 就更多。

    但当使用较大的目录块时,相比使用较小目录块的文件系统,同样的修改操作可能要消耗更多的 CPU。就是说相比小目录,大目录块的修改性能较差。当目录达到 I/O 的性能-限制因数,大块目录性能更佳。

    默认配置的 4 KB 文件系统块大小和 4 KB 目录块大小是不超过 1-2 百万条目(每个条目名称长度为 20-40 字节)情况下的最佳选择。如果您的文件系统需要更多条目,使用更大的目录块以便有更佳的性能。 16 KB 块大小是 1-10 百万目录条目文件系统的最佳选择,64 KB 块大小是超过 1 千万目录条目文件系统的最佳选择。

    如果负载使用目录随机查询而不是修改(即目录读取比目录写入更常用或更重要),那么以上增加块大小的幅度就要减少一个数量级。

    优化并行性

    与其他文件系统不同,XFS 可以在非共享对象中同时执行很多种类的分配和取消分配操作。扩展的分配或者取消分配可在不同的分配组中同时进行。同样,inode的分配和取消分配也可以在不同的分配组中同时进行。

    使用有多个 CPU 的机器和多线程程序会执行并发操作时,分配组的数量就变得很重要。如果只有 4 个分配组,那么持续的、平行的元数据操作将只限于这四个 CPU(系统提供的并发性限制)。对小文件系统,请确保系统提供的并发性支持分配组的数量。对于大文件系统(10TB 以上),默认的格式化选项通常可生成足够多的分配组以避免并发性受到限制。

    应用程序必须知道竞争点以便使用 XFS 文件系统中的并行性。不能并发的修改同一个目录,因此创建和删除大量文件的应用程序应避免在一个目录中保存所有的文件。应将每个创建的目录放在不同的分配组,这样类似多目录哈希文件的技术就可以提供比使用单一大目录更有扩展性的存储方式。

    优化使用扩展属性的程序

    如果inode中有可用空间,则 XFS 可以直接在inode中保存小的属性。如果该属性适合保存在 inode 中,那么可以无需额外 I/O 检索独立属性块的情况下检索并修改它。内嵌属性和外部属性之间的性能差异可以简单归结为外部属性的低一个数量级的性能。

    对于默认的 256 字节 inode ,约有 100 字节属性空间可用,具体要看同样保存在 inode 中的数据扩展指针数。默认 inode 大小只适于保存少量小属性。

    在执行 mkfs 时增加 inode 的大小可以增大用来保存内嵌属性的可用空间。512 字节的 inode 约可增加用于保存属性的空间 350 字节,2 KB 的 inode 约有 1900 字节的空间可用。

    但对于每个内嵌属性有一个大小限制:即属性名称和值占用空间最多为 254 字节(即如果属性的名称长度和值长度都在 254 字节以内则为内嵌属性)。超过这个限制则会将属性强制保存为外部属性,即使在该 inode 中有足够到空间保存所有属性。

    优化持续元数据修改

    日志的大小是决定持续元数据修改等级的主要因素。日志设备是循环使用的,因此在可以覆盖尾部数据前,必须将日志中的所有修改写入磁盘的实际位置中。这可能会涉及大量写入脏元数据的操作。默认配置会让日志大小与文件系统总体大小成比例,因此在大多数情况下不需要调整日志大小。

    小的日志设备可导致非常频繁的元数据写回操作,即日志会一直从结尾写入以便释放空间,这会导致频繁修改的元数据频繁写入磁盘,导致操作变慢。

    增加日志大小会增加从结尾处填充数据之间的间隔。这样可以更好地聚合脏元数据,形成更好的元数据写回模式,减少频繁修改的元数据的写回。代价是较大的日志需要更多内存方可跟踪所有内存中的修改。

    如果您的机器内存有限,大日志就没有好处,因为内存限制可导致元数据写回时间延长抵消了使用大日志带来的好处。在这些情况下,通常较小的日志比较大的日志性能更好,因为缺少空间的日志元数据写回比内存重新回收形成的写回更有效。

    您应该总是将日志与包含该文件系统的底层设备的条带保持对齐。mkfs 命令可自动为 MD 或者 DM 设备完成此功能,但如果是硬件 RAID 则需要手动设置。设定这个特性可以避免在磁盘写入修改时所有可能的由于未同步 I/O 以及后续读取-修改-写入操作造成的日志 I/O。

    通过修改挂载选项可进一步提高日志操作性能。增大内存中的日志缓存(logbsize)的大小可增加写入日志的速度。默认日志缓存大小为 MAX(32 KB,日志条带单元),且最高可达 256 KB。通常该数值越大性能越快。但如果是在 fsync 负载较重的环境中,小的日志缓存比使用大条带单元的大缓存速度明显快很多。

    delaylog 挂载选项也可以改进持续元数据修改的性能,方法是减少日志更改的次数。它通过写入日志前先在内存中整合单个更改:频繁修改的元数据是阶段性的写入日志,而不是每次修改时都写入。这个选项增加了跟踪脏元数据的内存用量,同时也增加了崩溃发生时可能的损失,但可以将元数据修改速度和扩展性提高一个等级。使用这个选项不会在使用 fsync, fdatasync 或者 sync 来确保数据和元数据写入磁盘时减少数据或者元数据完整性。

    5 网络

    5.1 优化网络设置

    对于大多数负载,自动配置的网络设置已经可以提供最优化的性能。对于大多数负载,不需要为性能重新配置网络设置。

    通常我们会在运行程序或者部署系统前调整已知变量。如果调整不起作用,则会尝试调整其他变量。此想法的逻辑是默认情况下,系统并不是以最佳性能水平运作;因此我们认为需要相应对系统进行调整。在有些情况下我们根据计算推断进行调整。

    如前所述,网络栈在很大程度上是自我优化的。另外,有效调整网络要求网络栈有深入的理解,而不仅仅值知道网络栈是如何工作,同时还要知道具体系统的网络资源要求。错误的网络性能配置可能会导致性能下降。

    例如:缓存浮点问题。增加缓存队列深度可导致 TCP 连接的拥塞窗口比允许连接的窗口更大(由深度缓存造成)。但这些连接还有超大的 RTT 值,因为帧在队列中等待时间过长,从而导致欠佳的输出,因为它可能根本无法探测到堵塞。

    当讨论网络性能时,建议保留默认设置,除非具体的性能问题变得很明显。此类问题包括帧损失,流量极大减少等等。即便如此,最佳解决方法通常是经过对问题的细致入微的研究,而不是简单地调整设置(增加缓存/队列长度,减少中断延迟等等)。

    例如:在 /proc/net/snmp 中增长的 UDP 输入错误表示当网络栈尝试将新帧插入程序的socket时,一个或者多个socket接受队列已经满了。这代表至少一个socket队列存在瓶颈,页就是说socket队列发送数据包的速度太慢,或者对于该socket队列该数据包过大。如果是后者,那么可通过查看依赖网络的程序的日志查找丢失数据的现象来验证并解决这个问题,这就应该优化或者重新配置受到影响的程序。

    socket接收缓存大小

    socket发送和接收大小都是动态调节的,因此基本不需要手动编辑。如果进一步分析,比如 SystemTap 网络示例中演示的分析,sk_stream_wai_memory.stp 认为该socket队列的排放速度过慢,那么您可以增大该程序socket队列深度。要做到这一点,请增大socket接收缓存,方法是配置以下值之一:

    rmem_default
    控制socket使用的接收缓存默认大小的内核参数。要配置此参数,请运行以下命令:sysctl -w net.core.rmem_default=N 用所需缓存大小以字节为单位替换 N。要确定这个内核参数值请查看 /proc/sys/net/core/rmem_default。请记住 rmem_default 值不得大于 rmem_max);如果需要请增大 rmem_max 值。
    
    SO_RCVBUF
    控制socket接收缓存最大值的socket选项,单位为字节。有关 SO_RCVBUF 的详情请参考其 man page:man 7 socket。
    要配置 SO_RCVBUF,请使用 setsockopt 工具,您可以使用 getsockopt 查询当前 SO_RCVBUF 值。有关这两个工具的详情请参考 setsockopt man page: man setsockopt。
    

    5.2 优化包接收

    CPU/缓存亲和性

    要保持接收路径的高流量,建议让 L2 缓存处于热状态。网络包由 IRQ 所在的同一 CPU 接收。就是说该缓存数据将保持在接收 CPU 的 L2 缓存中。

    要利用这个特性,请配置要接收共享 L2 缓存的 NIC 上大多数数据的程序的进程亲和性。这可以最大化缓存命中率,并以此提高性能。

    5.3 队列/帧丢失问题

    到目前为止,帧丢失最常见的原因是队列溢出。内核设定了队列长度限制,在有些情况下队列填充的速度超过排出的速度。当出现这种情况的时间过长,则会开始出现帧丢失。

    在数据包接收路径中有两种主要队列:NIC 硬件缓冲和socket队列。这两种队列都需要进行配置以防止队列溢出。

    5.3.1 NIC 硬件缓冲

    NIC 用帧填充其硬件缓冲;然后该缓冲会被 softirq 发送。要查询这个队列的状态,请使用以下命令:

    ethtool -S ethX
    

    用 NIC 的对应设备名称替换 ethX。这会显示在 ethX 中已丢失的帧数。丢帧经常是因为该队列超过保存那些帧的缓冲空间所致。

    解决这个问题有一些不同方法,即:

    输入流量

    您可以通过放缓输入流量防止队列溢出。方法是过滤、减少联合多播组数、降低广播流量等等。

    队列长度

    另外,您也可以增加队列长度。这包括将指定队列的缓冲数增加到该驱动程序允许的最大数。方法是编辑 ethX 的 rx/tx 环参数,命令为:

    ethtool --set-ring ethX
    

    在上面的命令中附加恰当的 rx/tx 值。详情请参考 man ethtool。

    设备权重

    您还可以增加排空队列的速度。方法是调整 NIC 的设备权重。这个属性是指 softirq 通知 CPU 并重新调度其本身前 NIC 可以接收的帧的最大值。它由/proc/sys/net/core/dev_weight 变量控制。

    大多数管理员倾向于选择第三个选项。但请注意这样做会有一定的后果。在一个操作中增加可以从 NIC 接收的帧数会占用更多的 CPU 周期,在此期间这个 CPU 上无法调度任何应用程序。

    5.3.2 socket队列

    和 NIC 硬件队列一样,socket队列是由网络栈在 softirq 上下文中填充。然后程序通过 read、recvfrom 等调用清空其对应的socket的队列。

    要监控队列的状态请使用 netstat 程序。Recv-Q 列显示队列大小。一般来说对socket队列中的溢出的处理与对 NIC 硬件缓冲溢出的处理相同:

    输入流量

    第一个方法是延缓输入流量,方法为配置队列填充速度。具体步骤可以是过滤帧或者抢先丢掉它们。您还可以通过降低 NIC 的设备权重来延缓输入流量。

    队列深度

    您还可以通过增大队列深度避免socket队列溢出。方法是增大 rmem _default 内核参数或者
    SO_RCVBUF socket选项值。

    程序调用频率

    尽可能优化程序以便更频繁地执行调用。这包括修改或者重新配置网络程序以便执行更频繁的POSIX 调用(比如 recv、read)。反过来,这也可以让程序更快地排空队列。

    很多管理员更喜欢使用增加队列深度的方法。这是最简单的解决方案,但并不总是能够长久有效。因为网络技术发展迅速,socket队列将继续以更快的速度填充。随着时间的推移这意味着要相应重新调整队列深度。

    最好的解决方法是提高或者将程序配置为更迅速地从内核中排空数据,即使这意味着需要让数据在程序空间排队也无妨。这样数据的保存就变得更灵活,因为这样就可以根据需要置换出数据或者缓存到内存中。