なぜベースラインを取るのか#
デスクトップ環境を一通り整え終えたタイミングで、「今のこのマシンがどれくらいの性能で動いているか」を数値で残しておきたくなった。理由は単純で、将来「なんか遅くなった?」と感じたときに比較する基準がないと、気のせいか本当に劣化しているかを判断できないからだ。
カーネル更新、BIOS アップデート、新しいメモリへの換装、Curve Optimizer の適用——どれも体感では数%の差しかわからない。ベンチマーク値があれば、あとから「この変更でどれだけ変わったか」を客観的に見返せる。
この記事は、そのための記録だ。
環境#
| 項目 | 値 |
|---|---|
| CPU | AMD Ryzen 7 7800X3D (8C/16T, Zen4 X3D, 96MB L3) |
| Board | MSI MAG X670E TOMAHAWK WIFI (MS-7E12) |
| BIOS | 1.KB (2026-03-17) |
| RAM | Corsair CMK32GX5M2B5200C40 (DDR5-5200 CL40, 2×16GB) |
| GPU | NVIDIA RTX 4090 + AMD Raphael iGPU |
| OS | CachyOS (Arch 系) |
| Kernel | linux-cachyos 7.0.0-1 (znver4 build, EEVDF + LTO + AutoFDO) |
BIOS 側は以下を有効化済み。
- EXPO (DDR5-5200 CL40 プロファイル) →
5200 MT/sで動作確認 - Re-Size BAR (RTX 4090 の BAR1 が 32GB で認識)
- IOMMU (AMD-Vi)
- SVM / Virtualization
- Curve Optimizer: 未適用 (Auto)
- PBO Limits: Auto / Scalar: 1X
Curve Optimizer はあえて入れていない。理由は後述。
測定するもの#
ベースラインとして残す価値があるのは、時間経過や設定変更で変わりうる値で、かつ単発の測定で再現性のある値だ。以下の3つに絞った。
- メモリ帯域 — EXPO 設定と FCLK の効きを見る (STREAM)
- メモリレイテンシ — キャッシュ階層と DRAM のレイテンシを見る (pointer-chase)
- CPU スループット — メモリと CPU の複合負荷を見る (7z benchmark)
STREAM: メモリ帯域#
STREAM ↗ は John McCalpin による定番のメモリ帯域ベンチマーク。Copy / Scale / Add / Triad の 4 種類の単純なループで、持続的なメモリ帯域を測る。
Arch のリポジトリに stream パッケージはあるが、名前が ImageMagick の stream コマンドと衝突するので、ソースから小さく書き起こした。-march=znver4 で AVX-512 まで使わせて、OpenMP で 16 スレッド並列化している。
// 抜粋。完全版は記事末尾の再現コマンド参照
#pragma omp parallel for
for (long i=0; i<N; i++) a[i] = b[i] + scalar * c[i]; // Triadc結果#
| Kernel | MB/s |
|---|---|
| Copy | 35,071 |
| Scale | 35,150 |
| Add | 38,304 |
| Triad | 38,800 |
DDR5-5200 デュアルチャネルの理論ピークは 5200 × 8 × 2 = 83.2 GB/s。実測 Triad はその 46.6% にあたる。STREAM は書き込みを伴うのでピークの 40〜50% が典型値、今回の数字はスペック相応だ。
もし DDR5-6000 CL30 に換装すれば、経験的に Triad は 55〜60 GB/s 付近まで伸びる。5200 CL40 構成の実力はこのあたりが天井。
Pointer-chase: メモリレイテンシ#
帯域と並んでメモリ性能を決めるもう一つの軸がレイテンシ。ベンチマークとしてはリンクリストを辿るだけの単純なループで、各アクセスが前のアクセスの結果に依存するため キャッシュやメモリコントローラのレイテンシが直接そのまま見える。
配列サイズを L1 / L2 / L3 / DRAM それぞれに収まる大きさに変えながら測定することで、キャッシュ階層ごとのアクセスコストがわかる。
// 単一コアで、ランダムな順序に繋がったチェーンを辿る
for (size_t i=0; i<iters; i++) p = chain[p];c結果#
| Region | Latency | 階層 |
|---|---|---|
| 32 KB | 1.0 ns | L1D (各コア 32KB) |
| 256 KB | 3.1 ns | L2 境界 |
| 1 MB | 5.8 ns | L2 (1MB/core) |
| 8 MB | 10.9 ns | L3 (V-cache内) |
| 32 MB | 11.7 ns | L3 (V-cache内) |
| 96 MB | 32.1 ns | L3 境界 |
| 256 MB | 70.9 ns | DRAM |
| 1 GB | 87.1 ns | DRAM + TLB miss |
32MB アクセス時のレイテンシが 11.7 ns で粘っているのが 7800X3D の最大の特徴だ。通常の Zen4 (例: 7700X) だと L3 は 32MB で、32MB アクセス時にはすでに DRAM に近い 30 ns 超まで伸びてしまう。
7800X3D は 3D V-Cache によって 96MB の L3 を持っており、今回のベンチでもそれが忠実に可視化された。96MB を超えたところで急に 32 ns → 71 ns にジャンプするのが L3 → DRAM の境界だ。
DRAM レイテンシ 71 ns は DDR5-5200 としては標準的な値。EXPO が効いていないと JEDEC 4800 MT/s で動作し、レイテンシは 80 ns 近くまで悪化する。
7z benchmark: CPU + メモリ複合#
7z b は CPU とメモリの両方を使う LZMA 圧縮ベンチで、Linux 環境で手軽に実行できる複合指標として便利だ。
7z b -mmt16 | tail -5fish結果#
| 項目 | 値 |
|---|---|
| Compression | 112,687 MIPS |
| Decompression | 129,503 MIPS |
| Total | 121,095 MIPS |
| 1T Freq | 4,795〜5,036 MHz |
| 16T Freq | 4,395〜4,555 MHz |
7800X3D ストック設定の典型値。Curve Optimizer -20 を全コアに入れると、この値は +5〜8% 程度伸びる余地がある (16T 実効クロックが 4,700 MHz 付近まで上がるため)。
温度と Preferred Core#
あわせて記録しておいた。
Idle 温度#
Tctl: 47.5°C
Tccd1: 35.8°CplaintextCCD 温度 35°C は 7800X3D としては相当涼しい部類。負荷時も 80°C に届かない構成になっている。
Preferred Core ランキング#
amd-pstate は acpi_cppc/highest_perf の値を見て、シリコン品質が良いコアにシングルスレッド負荷を寄せる。値を見ればチップの個体差がわかる。
| Core | highest_perf |
|---|---|
| cpu1 | 196 |
| cpu5 | 196 |
| cpu0 | 191 |
| cpu3 | 186 |
| cpu2 | 181 |
| cpu4 | 176 |
| cpu7 | 171 |
| cpu6 | 166 |
最大値と最小値の差が 30 (= 5.05 GHz 換算で約 150 MHz) 程度。平均的な個体で、特に「ハズレ」でも「当たり」でもない。シングルスレッドは cpu1 / cpu5 に寄るのでここが一番伸びるコアだ。
なぜ Curve Optimizer を入れないか#
Zen4 + X3D の定番チューニングとして Curve Optimizer (CO) がある。各コアの電圧曲線を負方向にオフセットすることで、同クロックで温度が下がり、結果的にブースト時間が伸びる——という仕組みで、上手くいけば 5〜8% 伸びる。
今回入れなかったのは以下の理由。
- 温度に余裕がありすぎる: Tctl 47°C は熱で全く律速されていない状態。CO の主目的 (温度↓ → クロック↑) の効果が薄い
- X3D の不安定性は検出が厄介: 非X3D と違って、X3D の CO 失敗は「数週間に一度クラッシュ」という形で出やすく、CO のせいかどうか永遠に疑い続けることになる
- 体感差が小さい: devcontainer ビルドやコンパイルで +5% は人間には感じ取れない領域
「ベンチマーク趣味」としてやる価値はあるが、道具としてのマシンの安定性を優先するなら今回はスキップが正解と判断した。
将来メモリ換装 (DDR5-6000 CL30) と同時に CO もテストしたくなるかもしれない。そのときにこのベースラインが比較対象として効いてくる。
再現コマンド#
以降、同じ条件で測定するための最小コマンド。
# === STREAM ===
cat > /tmp/stream_bench.c <<'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <omp.h>
#define N (100L * 1024 * 1024)
#define NTIMES 10
static double a[N], b[N], c[N];
double wtime(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return t.tv_sec + t.tv_nsec * 1e-9;
}
int main() {
#pragma omp parallel for
for (long i = 0; i < N; i++) { a[i] = 1.0; b[i] = 2.0; c[i] = 0.0; }
double scalar = 3.0;
double best_copy=1e30, best_scale=1e30, best_add=1e30, best_triad=1e30;
for (int k = 0; k < NTIMES; k++) {
double t;
t = wtime(); _Pragma("omp parallel for") for (long i=0; i<N; i++) c[i] = a[i];
t = wtime()-t; if (t<best_copy) best_copy=t;
t = wtime(); _Pragma("omp parallel for") for (long i=0; i<N; i++) b[i] = scalar*c[i];
t = wtime()-t; if (t<best_scale) best_scale=t;
t = wtime(); _Pragma("omp parallel for") for (long i=0; i<N; i++) c[i] = a[i]+b[i];
t = wtime()-t; if (t<best_add) best_add=t;
t = wtime(); _Pragma("omp parallel for") for (long i=0; i<N; i++) a[i] = b[i]+scalar*c[i];
t = wtime()-t; if (t<best_triad) best_triad=t;
}
double bytes = 2.0 * sizeof(double) * N;
printf("Copy: %8.1f MB/s\n", bytes/best_copy/1e6);
printf("Scale: %8.1f MB/s\n", bytes/best_scale/1e6);
printf("Add: %8.1f MB/s\n", 3.0*sizeof(double)*N/best_add/1e6);
printf("Triad: %8.1f MB/s\n", 3.0*sizeof(double)*N/best_triad/1e6);
return 0;
}
EOF
gcc -O3 -march=znver4 -fopenmp /tmp/stream_bench.c -o /tmp/stream_bench
OMP_NUM_THREADS=16 /tmp/stream_bench
# === 7z ===
7z b -mmt16 | tail -10
# === 温度 ===
sensors | grep -E "Tctl|Tccd"
# === Prefcore ===
for c in (seq 0 7)
echo "cpu$c: "(cat /sys/devices/system/cpu/cpu$c/acpi_cppc/highest_perf)
endfishおわりに#
ベンチマークは「マシンを褒める道具」として使うより、「将来の自分への差分情報」として残すほうが実用価値が高い。
CO を入れるかどうか、メモリを換装するかどうか、カーネルを更新するかどうか——どの判断も、この数字と比較できることで「本当に効いたのか」がはっきりする。
次に同じコマンドを叩くのは、たぶん DDR5-6000 CL30 に換装したときか、CachyOS のカーネルが 7.x 後半に上がったときだろう。そのときこの記事を開き直して、どれだけ変わったかを確認する予定だ。