# ccache 在构建 FreeBSD 的 buildworld 时的效率

* 原文：[ccache で FreeBSD を buildworld した 場合 の 効率 について](https://qiita.com/nanorkyo/items/db1374b5c821dfe5113c)
* 作者：重村法克
* 2022-11-16

## 开始

虽然现在谈论 `ccache` 可能有些晚，但它作为加速构建过程的工具非常有名。通过调查，我们发现现有的很多信息已经过时。最近 FreeBSD 中的配置方法已经有所变化，但并没有找到相关的更新信息。因此，本篇将重点介绍这一变化，并分析其效果。

※与以前的设置方法相比，现在的控制变得非常简便。

### 测试环境

* FreeBSD 13.0-RELEASE-p5
* FreeBSD 12.2-RELEASE-p11

这次不讨论 `ccache` 的安装过程（没有特别的步骤）。为了进行磁盘使用量调查，我们提前执行了以下命令，确保不会出现缓存无法生效的情况。结论是，默认的 5GB 使用量在 FreeBSD 的构建过程中是完全没有问题的。

**扩展缓存容量（5GB → 32GB）**

```sh
ccache -M 32G
```

值得注意的是，“字节”的 SI 前缀基本上是以 $10^3$ 为单位的。如果想使用 $2^{10}$ 作为单位，需要明确使用 `103103` 作为基本单位。如果要使用 $2^{10}$ 的方式表示容量，应该使用 `32Gi`（尽管显示仍不可更改）。另外，如果只是输入数字而不指定单位，默认会被解释为 `G`。

### 配置（/etc/src.conf）

**/etc/src.conf**

```ini
WITH_CCACHE_BUILD= yes
```

如果只是使用 `ccache` 来进行 `buildworld` 构建，那么只需要设置上面的配置即可。无需再手动设置环境变量 `CC="ccache cc"`（这一点非常重要！）。

对于 `buildworld` 或 `buildkernel` 构建中需要开启或关闭的组件，可以参考 [src.conf(5)](https://man.freebsd.org/src.conf/) 文档，我自己目前还没有确认遗漏的部分（因为选项实在太多了…）。

## 执行 make buildworld

我们从没有缓存的状态开始执行 `make buildworld`。为了分析缓存命中率等信息，构建完成后，我运行了 `ccache -s` 来获取统计信息。

### 第一次执行结果

如下所示，几乎所有文件都进行了编译。由于 `ccache` 的开销，首次编译的时间比正常编译时间稍长。此外，虽然发生了一些重新编译，但比例极小，不到 0.2%，可以忽略不计。

**第一次执行结果（从无缓存状态开始构建）**

```
cache directory                     /root/.ccache
primary config                      /root/.ccache/ccache.conf
secondary config      (readonly)    /usr/local/etc/ccache.conf
cache hit (direct)                     5
cache hit (preprocessed)              77
cache miss                         46123
cache hit rate                      0.18 %
called for link                      116
ccache internal error                  1
unsupported code directive             3
no input file                          1
cleanups performed                     0
files in cache                    137311
cache size                           2.7 MB
max cache size                      32.0 GB
```

此外，实际的缓存使用量并未反映出来，这一点导致了一些不准确的结果。稍后将详细说明这一点。

### 第二次执行结果

在收集上述数据后，我再次执行了 `buildworld`。如下所示，结果显示了 100% 的缓存命中率，最终得出了「50.09%」的结果。

**第二次执行结果（从缓存状态中进行构建）**

```
cache directory                     /root/.ccache
primary config                      /root/.ccache/ccache.conf
secondary config      (readonly)    /usr/local/etc/ccache.conf
cache hit (direct)                 45050
cache hit (preprocessed)            1237
cache miss                         46123
cache hit rate                     50.09 %
called for link                      232
ccache internal error                  2
unsupported code directive             6
no input file                          2
cleanups performed                     0
files in cache                    137454
cache size                           2.7 MB
max cache size                      32.0 GB
```

### 清空统计信息后的第三次执行结果

执行了 `ccache -z` 清空统计信息后，再次进行了 `buildworld`。如下所示，结果显示了 100% 的缓存命中率。这次没有进行任何编译，因此 `buildworld` 执行时间大大缩短。

**清空统计信息后的第三次执行结果（从缓存状态中进行构建）**

```sh
cache directory                     /root/.ccache
primary config                      /root/.ccache/ccache.conf
secondary config      (readonly)    /usr/local/etc/ccache.conf
cache hit (direct)                 45186
cache hit (preprocessed)            1019
cache miss                             0
cache hit rate                    100.00 %
called for link                      116
ccache internal error                  1
unsupported code directive             3
no input file                          1
cleanups performed                     0
files in cache                    137578
cache size                           2.7 MB
max cache size                      32.0 GB
```

### 执行清理操作

关于缓存大小未能即时反映的问题，我执行了 `ccache -c` 清理操作，成功将统计数据更新至最新状态。

**清理后正常化的统计信息**

```sh
cache directory                     /root/.ccache
primary config                      /root/.ccache/ccache.conf
secondary config      (readonly)    /usr/local/etc/ccache.conf
cache hit (direct)                 45186
cache hit (preprocessed)            1019
cache miss                             0
cache hit rate                    100.00 %
called for link                      116
ccache internal error                  1
unsupported code directive             3
no input file                          1
cleanups performed                     0
files in cache                    137578
cache size                           4.5 GB
max cache size                      32.0 GB
```

如你所见，即便是 5GB 的缓存，也能勉强满足需求。

## 基准测试

### buildkernel 示例

从 [karl](https://twitter.com/karl0204) 收到以下环境下的 `make buildkernel` 测量结果，并将其整理成表格。

* CPU： AMD Ryzen9 3900（3.10GHz／4.30GHz）
* 内存：32GB
* 存储：WesternDigital SN550（NVMe 连接）· ZFS 操作
* 构建目标：FreeBSD 14-CURRENT

| 构建步骤                    | 无 CCache 时间 | 有 CCache 时间 |
| ----------------------- | ----------- | ----------- |
| `make buildkernel`      | 1406 秒      | 837 秒       |
| `make -j4 buildkernel`  | 366 秒       | -           |
| `make -j8 buildkernel`  | 200 秒       | -           |
| `make -j12 buildkernel` | 155 秒       | 82 秒        |
| `make -j16 buildkernel` | 136 秒       | -           |
| `make -j20 buildkernel` | 123 秒       | -           |
| `make -j24 buildkernel` | 115 秒       | 55 秒        |

需要注意的是，CPU 资源未被完全消耗。

### buildworld 示例

* CPU：Intel Pentium N4200（1.10GHz／2.50GHz·Apollo Lake·Goldmont 架构）
* 内存：16GB
* 存储：Transcend MTS400S（SATA 连接）· ZFS 操作
* 构建目标：12.2-RELEASE-p11

| 构建步骤※        | 构建时间    |
| ------------ | ------- |
| 无 CCache     | 22317 秒 |
| 有 CCache（一轮） | 25603 秒 |
| 有 CCache（二轮） | 3841 秒  |
| 有 CCache（三轮） | 3854 秒  |

※均指定了 `make -j5 buildworld`

## 总结

* 无论构建并行度（`-j` 选项的设置）高低如何，100% 缓存命中时都会带来几倍级别的时间缩短效果（几分钟内完成构建）。
* 对于 `buildkernel` 这种几乎全由 C 语言构成的构建，效果约为两倍；而对于包含 LLVM 这类重型构建的 `buildworld`，效果可达到六倍左右。
* 引入 ccache 后的初始开销为 14%，但作为二次及后续构建效果的代价，这一开销可以忽略。
* 在开发等场景下，经常需要稍微修改并重新构建的情况，ccache 的效果非常显著。
* 本次测试还表明，在纯粹的重新构建场景下，100% 不会重新编译。这一点在执行 `make clean` 后，即使对象文件被删除，也得到了验证。
* 近年来，FreeBSD 的开发环境（如 LLVM）的构建时间显著增加，而 ccache 在缩短构建时间（无论是时间上还是内存上）方面效果极为显著。
* 然而，初次编译时所需的内存仍然是个问题，因此在内存较小的环境下进行构建仍然是一个挑战。

### ccache 的代表性命令与选项

由于 ccache 本身较为简洁，因此相关的文档较少，下面简单总结了一些常用命令与选项。

| 命令            | 含义                    | 备注             |
| ------------- | --------------------- | -------------- |
| `ccache -M ｎ` | 设置缓存容量（如果未指定，默认为 `G`） | 指定最大缓存容量※      |
| `ccache -s`   | 显示统计信息                |                |
| `ccache -z`   | 清除统计信息                |                |
| `ccache -c`   | 清理缓存目录                | 清理操作不会删除已缓存的数据 |
| `ccache -C`   | 删除缓存                  |                |

※`ｎ` 可以指定的后缀包括：“`k`”，“`M`”，“`G`”，“`T`”，“`Ki`”，“`Mi`”，“`Gi`”，“`Ti`”。

## 常见问题及解答

### 问：能不能在 `/etc/make.conf` 中设置？

答：`/etc/make.conf` 会被必定读取，因此实质上与在 `/etc/src.conf` 中设置相同。不过，若在 FreeBSD 的构建以外的其他地方使用该设置，行为无法保证。如果不介意这些差异，可以在 `/etc/make.conf` 中设置。为了与其他构建区分，建议将设置放在 `/etc/src.conf` 中。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://translated-articles.bsdcn.org/2024-nian-7-yue/ccache.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
