> For the complete documentation index, see [llms.txt](https://man.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://man.bsdcn.org/man7/tuning.7.md).

# tuning(7)

`tuning` — FreeBSD 下的性能调优

## 名称

`tuning`

## 系统设置 - DISKLABEL、NEWFS、TUNEFS、SWAP

对于内存小于 4GB 的系统，交换分区通常应约为主内存的 2 倍；如果内存更多，则约等于主内存的大小。在确定交换分区大小时，请考虑未来的内存扩展。配置过少的交换空间会导致 VM 页面扫描代码效率低下，并且在以后向机器添加更多内存时也会产生问题。在具有多个磁盘的较大系统上，应在每个驱动器上配置交换空间。驱动器上的交换分区大小应大致相同。内核可以处理任意大小，但内部数据结构会按最大交换分区的 4 倍缩放。保持交换分区大小相近将使内核能够在 N 个磁盘上以最优方式条带化交换空间。不必担心设置过多，交换空间是 UNIX 的救星，即使你通常不使用太多交换空间，它也能在被迫重启之前为你提供更多时间来从失控程序中恢复。

创建一个巨大的分区并不是个好主意。首先，每个分区具有不同的操作特性，将它们分开允许文件系统根据这些特性进行自我调优。例如，根分区和 **/usr** 分区主要是读取操作，写入很少，而 **/var/tmp** 中可能会发生大量读写操作。通过正确地对系统进行分区，较小的、写入负载较重的分区中引入的碎片不会蔓延到主要是读取的分区中。

正确地对系统进行分区还允许你调优 newfs(8) 和 tunefs(8) 参数。唯一值得开启的 tunefs(8) 选项是 *softupdates*，使用“`tunefs -n enable /filesystem`”。Softupdates 极大地提高了元数据性能，主要是文件创建和删除。我们建议在大多数文件系统上启用 softupdates；但是，softupdates 有两个限制，在确定是否在文件系统上使用它时应予以注意。首先，softupdates 在崩溃情况下保证文件系统一致性，但可能比物理磁盘上的待处理写入落后几秒（甚至一分钟！）。如果崩溃，你可能会比其他情况损失更多工作。其次，softupdates 延迟文件系统块的释放。如果你的文件系统（如根文件系统）接近已满，对其进行重大更新（例如“`make installworld`”）可能会耗尽空间并导致更新失败。因此，在典型安装期间不会在根文件系统上启用 softupdates。由于根文件系统很少写入，因此不会损失性能。

存在许多运行时 [mount(8)](/man8/mount.8.md) 选项可帮助你调优系统。最明显也最危险的一个是 `async`。仅在结合 gjournal(8) 时使用此选项，因为它在普通文件系统上过于危险。一个不那么危险且更有用的 [mount(8)](/man8/mount.8.md) 选项是 `noatime`。UNIX 文件系统通常在每次访问文件或目录时更新其最后访问时间。此操作在 FreeBSD 中通过延迟写入处理，通常不会给系统带来负担。但是，如果你的系统持续访问大量文件，缓冲区缓存可能会被 atime 更新污染，给系统带来负担。例如，如果你正在运行一个高负载的网站或一个有许多阅读者的新闻服务器，你可能需要考虑使用此 [mount(8)](/man8/mount.8.md) 选项在较大的分区上关闭 atime 更新。但是，你不应随意在任何地方关闭 atime 更新。例如，**/var** 文件系统通常存放邮箱，atime（与 mtime 结合）用于确定邮箱是否有新邮件。对于主要是只读的分区（如 **/** 和 **/usr**），你可能也会保持 atime 开启。这对于 **/** 尤其有用，因为某些系统实用程序使用 atime 字段进行报告。

## 磁盘条带化

在较大的系统中，你可以将来自多个驱动器的分区条带化在一起，创建一个更大的整体分区。条带化还可以通过在两个或更多磁盘上拆分 I/O 操作来提高文件系统性能。gstripe(8) 和 ccdconfig(8) 实用程序可用于创建简单的条带化文件系统。一般来说，对较小的分区（如根分区和 **/var/tmp**）或本质上只读的分区（如 **/usr**）进行条带化完全是浪费时间。你只应对需要严肃 I/O 性能的分区进行条带化，通常是 **/var**、**/home** 或用于存放数据库和网页的自定义分区。选择适当的条带大小也很重要。文件系统倾向于在 2 的幂次边界上存储元数据，你通常希望减少寻道而不是增加寻道。这意味着你想使用较大的、偏离中心的条带大小（如 1152 个扇区），这样顺序 I/O 不会在两个磁盘上都寻道，元数据也分布在两个磁盘上而不是集中在单个磁盘上。

## SYSCTL 调优

[sysctl(8)](/man8/sysctl.8.md) 变量允许在运行时监视和控制系统行为。某些 sysctl 仅报告系统行为；其他允许修改系统行为；某些可在引导时使用 [rc.conf(5)](/man5/rc.conf.5.md) 设置，但大多数将通过 [sysctl.conf(5)](/man5/sysctl.conf.5.md) 设置。系统中有数百个 sysctl，包括许多看似可调优但实际并非如此的参数。本文档仅介绍对系统影响最大的那些。

`vm.overcommit` sysctl 定义了 vm 子系统的过度提交行为。虚拟内存系统始终对交换空间预留进行记账，包括系统总计和每用户。相应值可通过 sysctl `vm.swap_total`（给出可用于交换的总字节数）和 `vm.swap_reserved`（给出可能需要用于支持所有当前分配的匿名内存的字节数）获得。

设置 `vm.overcommit` sysctl 的位 0 会导致虚拟内存系统在内存分配导致 `vm.swap_reserved` 超过 `vm.swap_total` 时向进程返回失败。该 sysctl 的位 1 强制执行 `RLIMIT_SWAP` 限制（见 getrlimit(2)）。root 不受此限制。位 2 允许将大部分物理内存计为可分配，但已锁定和空闲保留页除外（分别由 `vm.stats.vm.v_free_target` 和 `vm.stats.vm.v_wire_count` sysctl 计入）。

由于 FreeBSD 虚拟内存子系统的架构，使用写时复制（CoW）匿名内存（例如在 fork(2) 时）会导致对所有三个区域（VM 对象）进行交换预留：在原始 fork 前映射中，以及其在父进程和子进程中的副本，而不是仅两个。最终，子系统会尝试优化 CoW 跟踪的内部布局，并经常移除（折叠）不再需要的后备对象，将其页面和交换预留重新分配给副本。折叠会释放交换预留，但不保证会发生。

`kern.ipc.maxpipekva` 加载器可调参数用于设置分配给管道缓冲区映射的内核地址空间量的硬限制。使用映射允许内核消除从写者地址空间到内核的数据复制，直接将映射缓冲区的内容复制给读者。将此值增加到更高的设置（如 `25165824`）可能会在映射管道缓冲区空间快速耗尽的系统上提高性能。这种耗尽并非致命的；但是，它只会导致管道回退到使用双重复制。

`kern.ipc.shm_use_phys` sysctl 默认为 0（关闭），可设置为 0（关闭）或 1（开启）。将此参数设置为 1 将导致所有 System V 共享内存段映射到不可分页的物理 RAM。此功能仅在以下情况有效：(A) 跨许多（数百个）进程映射少量共享内存，或 (B) 跨任意数量进程映射大量共享内存。此功能允许内核以将共享内存锁定在内存中使其不可交换为代价，移除大量内部内存管理页面跟踪开销。

`vfs.vmiodirenable` sysctl 默认为 1（开启）。此参数控制系统如何缓存目录。大多数目录都很小，在文件系统中仅使用单个片段（通常为 2K），在缓冲区缓存中甚至更少（通常为 512 字节）。但是，在默认模式下操作时，即使你有大量内存，缓冲区缓存也只缓存固定数量的目录。开启此 sysctl 允许缓冲区缓存使用 VM 页面缓存来缓存目录。优点是所有内存现在都可用于缓存目录。缺点是用于缓存目录的最小内存中大小是物理页大小（通常为 4K）而不是 512 字节。我们建议在内存受限的环境中关闭此选项；但是，开启时，它将大大提高操作大量文件的服务性能。此类服务可包括 Web 缓存、大型邮件系统和新闻系统。开启此选项通常不会降低性能，即使浪费了内存，但你应进行实验以确定。

`vfs.write_behind` sysctl 默认为 1（开启）。这告诉文件系统在收集到完整簇时发出介质写入，这通常在写入大型顺序文件时发生。其理念是避免在不会提高 I/O 性能的情况下用脏缓冲区使缓冲区缓存饱和。但是，这可能会使进程停滞，在某些情况下你可能希望关闭它。

`vfs.hirunningspace` sysctl 确定在任何给定时间可排队到磁盘控制器的系统范围内未完成写 I/O 量。它由 UFS 文件系统使用。默认值是自调优的，通常足够，但在具有高级控制器和大量磁盘的机器上，可调高以匹配控制器缓冲区。将此设置配置为匹配控制器或驱动器的标记队列能力以及生产中使用的平均 IO 大小效果最佳（例如：16 MiB 将使用 128 个标记和 128 KiB 的 IO 请求）。请注意，设置过高的值（超过缓冲区缓存的写入阈值）可能导致极差的簇性能。不要任意设置过高的值！较高的写入队列值还可能增加同时发生的读取的延迟。

`vfs.read_max` sysctl 控制 VFS 预读，表示如果启发式算法确定读取是顺序发出的，则预读的块数。它由 UFS、ext2fs 和 msdosfs 文件系统使用。在默认 UFS 块大小为 32 KiB 的情况下，设置为 64 将允许推测性读取最多 2 MiB。可增加此设置以绕过磁盘 I/O 延迟，特别是在虚拟机模拟环境等延迟较大的情况下。在 I/O 负载使得预读不利影响性能或系统内存确实很低的特定情况下，可调低此值。

`vfs.ncsizefactor` sysctl 定义 VFS 名称缓存可增长到多大。名称缓存中当前分配的条目数由 `debug.numcache` sysctl 提供，并遵循 debug.numcache < kern.maxvnodes \* vfs.ncsizefactor 条件。

`vfs.ncnegfactor` sysctl 定义允许 VFS 名称缓存创建多少个负条目。当前分配的负条目数由 `debug.numneg` sysctl 提供，并遵循 vfs.ncnegfactor \* debug.numneg < debug.numcache 条件。

还有各种其他与缓冲区缓存和 VM 页面缓存相关的 sysctl。我们不建议修改这些值。

如果你正在运行网络密集型应用程序，`net.inet.tcp.sendspace` 和 `net.inet.tcp.recvspace` sysctl 特别值得关注。它们控制任何给定 TCP 连接允许的发送和接收缓冲区空间量。默认发送缓冲区为 32K；默认接收缓冲区为 64K。通过增加默认值，你可以以每个连接消耗更多内核内存为代价来提高带宽利用率。如果你正在为数百或数千个同时连接提供服务，我们不建议增加默认值，因为停滞连接的积累可能很快耗尽系统内存。但是，如果你需要在较少的连接上获得高带宽，特别是如果你有千兆以太网，增加这些默认值会产生巨大差异。你可以分别调整传入和传出数据的缓冲区大小。例如，如果你的机器主要提供 Web 服务，你可能希望减小 recvspace 以便能够增加 sendspace 而不会消耗过多内核内存。请注意，路由表（见 [route(8)](/man8/route.8.md)）可用于引入特定路由的发送和接收缓冲区大小默认值。

作为附加的管理工具，你可以在防火墙规则（见 [ipfw(8)](/man8/ipfw.8.md)）中使用管道来限制进出特定 IP 块或端口的带宽。例如，如果你有 T1 线路，你可能希望将 Web 流量限制为 T1 带宽的 70%，以便将剩余带宽留给邮件和交互式使用。通常，高负载的 Web 服务器不会在其他服务中引入显著延迟，即使网络链路已满，但强制限制可以使事情更平滑并带来长期稳定性。许多人还强制实施人为的带宽限制，以确保不会因使用过多带宽而被收费。

将发送或接收 TCP 缓冲区设置为大于 65535 的值将导致边际性能改进，除非两台主机都支持 TCP 协议的窗口缩放扩展，该扩展由 `net.inet.tcp.rfc1323` sysctl 控制。应启用这些扩展，并且 TCP 缓冲区大小应设置为大于 65536 的值，以便从某些类型的网络链路获得良好性能；特别是千兆 WAN 链路和高延迟卫星链路。RFC1323 支持默认启用。

`net.inet.tcp.always_keepalive` sysctl 确定 TCP 实现是否应尝试通过间歇性地在连接上传递“keepalives”来检测死 TCP 连接。默认情况下，对所有应用程序启用此功能；通过将此 sysctl 设置为 0，只有特别请求 keepalive 的应用程序才会使用它们。在大多数环境中，TCP keepalive 将通过使死 TCP 连接过期来改善系统状态管理，特别是对于为拨号用户提供服务的系统，这些用户在断开网络连接之前可能不会总是终止各个 TCP 连接。但是，在某些环境中，临时网络中断可能被错误地识别为死会话，导致 TCP 连接意外终止。在这种环境中，将 sysctl 设置为 0 可减少 TCP 会话断开的发生。

`net.inet.tcp.delayed_ack` TCP 功能在很大程度上被误解了。从历史上看，此功能旨在允许将传输数据的确认与响应一起返回。例如，当你在远程 shell 上键入时，对你发送的字符的确认可以与表示该字符回显的数据一起返回。关闭延迟确认后，确认可能在远程服务有机会回显刚刚接收的数据之前以自己的数据包发送。同一概念也适用于任何交互协议（例如 SMTP、WWW、POP3），可将网络中流动的小数据包数量减半。FreeBSD 延迟 ACK 实现还遵循 TCP 协议规则，即即使标准的 40ms 超时尚未过去，也至少每隔一个数据包进行一次确认。通常，延迟 ACK 最坏也只是稍微延迟连接的拆除，或稍微延迟慢启动 TCP 连接的上升。虽然我们不确定，但我们认为与 SAMBA 和 SQUID 等软件包相关的几个常见问题中建议关闭延迟确认可能是指慢启动问题。

`net.inet.ip.portrange.*` sysctl 控制自动绑定到 TCP 和 UDP 套接字的端口号范围。有三个范围：低范围、默认范围和高范围，可通过 `IP_PORTRANGE` setsockopt(2) 调用选择。大多数网络程序使用默认范围，由 `net.inet.ip.portrange.first` 和 `net.inet.ip.portrange.last` 控制，默认分别为 49152 和 65535。绑定端口范围用于传出连接，在某些情况下可能会耗尽系统端口。这最常发生在运行高负载 Web 代理时。对于运行主要处理传入连接的服务器（如普通 Web 服务器）或具有有限传出连接的服务器（如邮件中继），端口范围不是问题。对于可能耗尽端口的情况，我们建议适度减小 `net.inet.ip.portrange.first`。10000 到 30000 个端口的范围可能是合理的。更改端口范围时，还应考虑防火墙影响。某些防火墙可能阻止大范围端口（通常是低编号端口），并期望系统使用较高范围的端口进行传出连接。默认情况下，`net.inet.ip.portrange.last` 设置为最大允许端口号。

`kern.ipc.soacceptqueue` sysctl 限制用于接受新 TCP 连接的监听队列大小。默认值 128 对于在高负载 Web 服务器环境中稳健处理新连接通常太低。对于此类环境，我们建议将此值增加到 1024 或更高。服务守护程序本身可能限制监听队列大小（例如 sendmail(8)、apache），但通常在其配置文件中有一个指令可调大队列大小。较大的监听队列也能更好地抵御拒绝服务攻击。

`kern.maxfiles` sysctl 确定系统支持多少打开文件。默认值通常为几千，但如果你正在运行数据库或大型描述符密集型守护进程，可能需要将其增加到一两万。可查询只读 `kern.openfiles` sysctl 以确定系统上当前打开文件的数量。

## 加载器可调参数

系统行为的某些方面可能无法在运行时调优，因为它们执行的内存分配必须在引导过程早期进行。要更改加载器可调参数，必须在 loader.conf(5) 中设置其值并重新启动系统。

`kern.maxusers` 控制许多静态系统表的缩放，包括最大打开文件数的默认值、网络内存资源大小等。`kern.maxusers` 在引导时根据系统中可用的内存量自动调整大小，可在运行时通过检查只读 `kern.maxusers` sysctl 的值来确定。

`kern.dfldsiz` 和 `kern.dflssiz` 可调参数分别设置进程数据和堆栈大小的默认软限制。进程可通过调用 setrlimit(2) 将这些值增加到硬限制。`kern.maxdsiz`、`kern.maxssiz` 和 `kern.maxtsiz` 可调参数分别设置进程数据、堆栈和文本大小的硬限制；进程不能超过这些限制。`kern.sgrowsiz` 可调参数控制当进程需要分配更多堆栈时堆栈段增长多少。

`kern.ipc.nmbclusters` 可调整以增加系统愿意分配的网络 mbuf 数量。每个簇大约代表 2K 内存，因此值 1024 代表为网络缓冲区保留 2M 内核内存。你可以进行简单计算来确定需要多少。如果你有一台最多 1000 个同时连接的 Web 服务器，每个连接消耗 16K 接收缓冲区和 16K 发送缓冲区，你需要大约 32MB 的网络缓冲区来处理。一个很好的经验法则是乘以 2，因此 32MBx2 = 64MB/2K = 32768。所以对于这种情况，你需要将 `kern.ipc.nmbclusters` 设置为 32768。我们建议内存适中的机器使用 1024 到 4096 之间的值，内存较多的机器使用 4096 到 32768 之间的值。绝不应为此参数指定任意高的值，否则可能导致引导时崩溃。[netstat(1)](/man1/netstat.1.md) 的 `-m` 选项可用于观察网络簇使用情况。

越来越多的程序使用 sendfile(2) 系统调用通过网络传输文件。`kern.ipc.nsfbufs` sysctl 控制 sendfile(2) 允许使用来执行其工作的文件系统缓冲区数量。此参数名义上随 `kern.maxusers` 缩放，因此除极端情况外，你不需要修改此参数。详见 sendfile(2) 手册页中的 Sx TUNING 部分。

## 调度器

FreeBSD 允许在内核配置中指定多个调度器，并在引导时从中选择所需的调度器。目前的选项有：

**`SCHED_ULE`** 具有 O(1) 线程选择行为的现代调度器。

**`SCHED_4BSD`** 从 4.x BSD 继承的经典调度器。

必须至少指定一个选项。如果编译进来，默认使用 `SCHED_ULE`。

`kern.sched.available` sysctl 提供以逗号分隔的可用（已编译进来）调度器列表。`kern.sched.name` 加载器可调参数可设置为在引导时选择所需的调度器。`kern.sched.name` sysctl 报告正在使用的调度器。

## 内核配置调优

在大型系统中，有许多内核选项你可能需要调整。要更改这些选项，你需要能够从源代码编译新内核。[config(8)](/man8/config.8.md) 手册页和手册是学习如何执行此操作的良好起点。通常，创建自定义内核时你要做的第一件事是去掉所有不使用的驱动程序和服务。移除诸如 `INET6` 和你没有的驱动程序之类的东西将减小内核大小，有时可减小一兆字节或更多，为应用程序留出更多可用内存。

`SCSI_DELAY` 可用于减少系统引导时间。默认值相当高，可能导致引导过程中 5 秒以上的延迟。将 `SCSI_DELAY` 减小到 5 秒以下可能有效（特别是对于现代驱动器）。

有许多 `*_CPU` 选项可以注释掉。如果你只希望内核在 Pentium 类 CPU 上运行，可以轻松移除 `I486_CPU`，但只有在你确定 CPU 被识别为 Pentium II 或更高版本时才移除 `I586_CPU`。某些克隆可能被识别为 Pentium 甚至 486，没有这些选项可能无法引导。如果可行，那就太好了！操作系统将能够更好地使用高端 CPU 功能进行 MMU、任务切换、时基甚至设备操作。此外，高端 CPU 支持 4MB MMU 页，内核使用它将自身映射到内存中，从而在繁重的系统调用负载下提高效率。

## CPU、内存、磁盘、网络

你进行的调优类型在很大程度上取决于随着负载增加系统从哪里开始成为瓶颈。如果你的系统耗尽 CPU（空闲时间始终为 0%），则需要考虑升级 CPU，或者可能需要重新审视导致负载的程序并尝试优化它们。如果你的系统大量分页到交换空间，则需要考虑添加更多内存。如果你的系统使磁盘饱和，你通常会看到高 CPU 空闲时间和磁盘完全饱和。可使用 [systat(1)](/man1/systat.1.md) 来监视这种情况。磁盘饱和有许多解决方案：增加内存用于缓存、镜像磁盘、将操作分布到多台机器等等。

最后，你可能耗尽网络资源。尽可能优化网络路径。例如，在 [firewall(7)](/man7/firewall.7.md) 中，我们描述了一个防火墙保护内部主机的拓扑，其中外部可见的主机不通过它路由。大多数瓶颈发生在 WAN 链路上。如果扩展链路不是一个选项，可使用 [dummynet(4)](/man4/dummynet.4.md) 功能来实现峰值削峰或其他形式的流量整形，以防止过载服务（如 Web 服务）影响其他服务（如电子邮件），反之亦然。在家庭安装中，这可用于给予交互式流量（你的浏览器、[ssh(1)](/man1/ssh.1.md) 登录）优先级，高于你从机器上导出的服务（Web 服务、电子邮件）。

## 参见

[netstat(1)](/man1/netstat.1.md), [systat(1)](/man1/systat.1.md), sendfile(2), [ata(4)](/man4/ata.4.md), [dummynet(4)](/man4/dummynet.4.md), [eventtimers(4)](/man4/eventtimers.4.md), [ffs(4)](/man4/ffs.4.md), login.conf(5), [rc.conf(5)](/man5/rc.conf.5.md), [sysctl.conf(5)](/man5/sysctl.conf.5.md), [firewall(7)](/man7/firewall.7.md), [hier(7)](/man7/hier.7.md), [ports(7)](/man7/ports.7.md), [stats(7)](/man7/stats.7.md), [boot(8)](/man8/boot_i386.8.md), bsdinstall(8), ccdconfig(8), [config(8)](/man8/config.8.md), fsck(8), gjournal(8), [gpart(8)](/man8/gpart.8.md), gstripe(8), [ifconfig(8)](/man8/ifconfig.8.md), [ipfw(8)](/man8/ipfw.8.md), [loader(8)](/man8/loader.8.md), [mount(8)](/man8/mount.8.md), newfs(8), [route(8)](/man8/route.8.md), [sysctl(8)](/man8/sysctl.8.md), tunefs(8)

## 历史

`tuning` 手册页最初由 Matthew Dillon 编写，首次出现于 FreeBSD 4.3，2001 年 5 月。该手册页由 Eitan Adler <eadler@FreeBSD.org> 进行了大量修改。
