AMD EPYC (Zen 4) 向けの C++ ビルド環境を作成して OpenBLAS で行列計算を試してみた
はじめに
Intel 製ワークステーションで主に行列計算をしているプログラムがあります。このプログラムを AMD の EC2 で動かせるか、最適化できる余地がるのかサンプルプログラムを使って試して学びました。C++ のビルド環境やコンパイラ最適化について、実際に手を動かして理解を深めることが目的です。
背景と目的
第 4 世代 AMD EPYC プロセッサ(Zen 4)でのビルド環境構築と最適化オプションの理解が目的です。Intel 環境からの移行を想定し、以下の点を検証します。
- AMD EPYC(Zen 4)プロセッサの特徴確認
- AMD 固有のコンパイラ最適化オプションの効果
- マルチコアスケーリングの検証
検証内容
c7a インスタンス(第 4 世代 AMD EPYC)で、C++ と OpenBLAS による行列計算を検証しました。環境構築から性能測定、AMD 固有の最適化までの過程を記録します。
結論
AMD EPYC(Zen 4)での OpenBLAS による行列計算では以下が確認できました。
OpenBLAS はコア数に応じてほぼ理想的にスケールしました。シングルコアの実行時間は約 36.8 秒でした。2 コア使用で約 18.3 秒(2.01 倍高速化)、4 コア使用で約 9.2 秒(3.98 倍高速化)となり、ほぼ理論値です。
コンパイラ最適化の効果は、AMD 固有の最適化オプション(-march=native、-march=znver3、-march=znver4)による性能向上は 1% 未満と限定的でした。計算時間の大部分が事前コンパイル済み OpenBLAS のライブラリで消費されるためです。自前の計算ロジックを多く含むプログラムでは、コンパイラ最適化オプションが効果を発揮する可能性があります。
検証環境
| 項目 | 仕様 | 
|---|---|
| リージョン | ap-northeast-1(東京) | 
| EC2 インスタンス | c7a.xlarge(AMD EPYC 9R14 / x86_64 / 4vCPU) | 
| OS | Amazon Linux 2023 | 
| GCC | 11.5.0 | 
| Clang | 19.1.7 | 
| OpenBLAS | 0.3.18 | 
| CMake | 3.22.2 | 
c7a インスタンスは第 4 世代 AMD EPYC プロセッサ(Zen 4)を搭載しています。すべての vCPU が物理コアであり、SMT(Simultaneous Multi-Threading)は使用されていません。物理コア数を重要視をする場合はコスパが良好なインスタンスタイプで、個人的にはオススメです。
環境構築
システムアップデート
まずシステムを最新の状態に更新します。
sudo dnf update -y
開発ツールのインストール
C++ コンパイラ、ビルドツール、行列計算ライブラリをインストールします。
# Development Tools グループのインストール
sudo dnf groupinstall -y "Development Tools"
# C++ コンパイラとビルドツール
sudo dnf install -y gcc gcc-c++ cmake make git
# OpenBLAS ライブラリ
sudo dnf install -y openblas-devel lapack-devel
インストール確認
コンパイラバージョンの確認
インストールされたツールのバージョンを確認します。
gcc --version
gcc (GCC) 11.5.0 20240719 (Red Hat 11.5.0-5)
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
g++ --version
g++ (GCC) 11.5.0 20240719 (Red Hat 11.5.0-5)
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
cmake --version
cmake version 3.22.2
CMake suite maintained and supported by Kitware (kitware.com/cmake).
make --version
GNU Make 4.3
Built for x86_64-amazon-linux-gnu
Copyright (C) 1988-2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
OpenBLAS のインストール確認
OpenBLAS パッケージが正しくインストールされているか確認します。
rpm -q openblas openblas-devel
openblas-0.3.18-1.amzn2023.0.3.x86_64
openblas-devel-0.3.18-1.amzn2023.0.3.x86_6
AMD EPYC プロセッサの機能確認
CPU アーキテクチャと SIMD 拡張の確認
AMD EPYC 9R14(Zen 4)の SIMD 拡張命令セットを確認します。x86-64 では AVX、AVX2、AVX-512 のサポートしていることが重要です。
SIMD は複数のデータに同じ命令を同時実行する技術です。行列計算のような並列処理では有利です。ちなみに、ARM アーキテクチャでは SVE(Scalable Vector Extension)という異なる SIMD 技術を採用されています。
CPU フラグの確認
cat /proc/cpuinfo | grep flags | head -n 1
AMD EPYC 9R14(Zen 4)は AVX、AVX2、AVX-512 を含む SIMD 拡張命令をサポートしていました。
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid aperfmperf tsc_known_freq pni pclmulqdq monitor ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy cr8_legacy abm sse4a misalignsse 3dnowprefetch topoext perfctr_core invpcid_single ssbd perfmon_v2 ibrs ibpb stibp ibrs_enhanced vmmcall fsgsbase bmi1 avx2 smep bmi2 invpcid avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves avx512_bf16 clzero xsaveerptr rdpru wbnoinvd arat avx512vbmi pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid flush_l1d
AMD EPYC Zen 4 の AVX-512 実装について
CPU フラグから avx512f、avx512dq、avx512bw などの AVX-512 命令セットがサポートされていることが確認できます。AMD は Zen 4 ではじめて AVX-512 をサポートしました。ただし実装方式は Intel と異なり、ダブルポンプ方式が採用されています。AMD の AVX-512 は 256 ビット幅のデータパスを使用し、512 ビット命令を 2 クロックサイクルで実行します。
- Intel: 512 ビット幅(1 サイクル)
- AMD: 256 ビット幅(2 サイクル、ダブルポンプ方式)
CPU 情報
lscpu | grep -E "Architecture|Model name|CPU\(s\)|Thread|Core|Socket|NUMA"
Architecture:                            x86_64
CPU(s):                                  4
On-line CPU(s) list:                     0-3
Model name:                              AMD EPYC 9R14
Thread(s) per core:                      1
Core(s) per socket:                      4
Socket(s):                               1
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-3
OpenBLAS ビルド情報の実行時確認
OpenBLAS の x86-64 向け最適化状況を確認するため、情報を表示するプログラムを実行します。前回 Graviton の検証で利用したコードを流用しています。
#include <stdio.h>
extern "C" {
    char* openblas_get_config(void);
    char* openblas_get_corename(void);
    int openblas_get_parallel(void);
    int openblas_get_num_threads(void);
}
int main() {
    printf("=== OpenBLAS Configuration ===\n");
    printf("%s\n", openblas_get_config());
    printf("\n=== Runtime Information ===\n");
    printf("Core Name: %s\n", openblas_get_corename());
    printf("Parallel: %d\n", openblas_get_parallel());
    printf("Threads: %d\n", openblas_get_num_threads());
    return 0;
}
コンパイルして実行します。
# コンパイル
g++ openblas_info.cpp -o openblas_info -lopenblas
# 実行
./openblas_info
=== OpenBLAS Configuration ===
OpenBLAS 0.3.18 DYNAMIC_ARCH NO_AFFINITY Zen SINGLE_THREADED
=== Runtime Information ===
Core Name: Zen
Parallel: 0
Threads: 1
実行結果から、OpenBLAS は AMD EPYC プロセッサを正しく検出し、Zen アーキテクチャ向けに最適化されたコードパスを選択していることが確認できます。
DYNAMIC_ARCH について
OpenBLAS の DYNAMIC_ARCH は実行時に CPU を検出し、最適なコードパスを自動選択します。x86-64 向けビルドには Haswell、Zen、SkylakeX などの複数ターゲットが含まれており、実行環境に応じて最適なコードが使用されます。これは便利。
参考: How to compile against DYNAMIC_ARCH ? · Issue #2388 · OpenMathLib/OpenBLAS
サンプルプログラムの作成
行列計算ベンチマーク
10000×10000 の行列乗算を 3 回実行し、性能を測定します。こちらも前回の Graviton での検証で使用したコードを流用します。
#include <cblas.h>
#include <chrono>
#include <iostream>
#include <vector>
#include <unistd.h>
void run_benchmark(const char* label) {
    const int N = 10000;
    std::vector<double> A(N*N, 1.0);
    std::vector<double> B(N*N, 2.0);
    std::vector<double> C(N*N, 0.0);
    auto start = std::chrono::high_resolution_clock::now();
    cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
                N, N, N, 1.0, A.data(), N, B.data(), N, 0.0, C.data(), N);
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << label << ": " << duration.count() << " ms" << std::endl;
}
int main() {
    std::cout << "Available CPUs: " << sysconf(_SC_NPROCESSORS_ONLN) << std::endl;
    const char* num_threads = getenv("OPENBLAS_NUM_THREADS");
    std::cout << "OPENBLAS_NUM_THREADS: "
              << (num_threads ? num_threads : "not set") << std::endl;
    std::cout << "\nRunning 10000x10000 matrix multiplication...\n" << std::endl;
    run_benchmark("Test 1");
    run_benchmark("Test 2");
    run_benchmark("Test 3");
    return 0;
}
OpenBLAS ライブラリのバージョン指定
Amazon Linux 2023 では、OpenBLAS が複数のバージョンで提供されています。
| リンカオプション | ライブラリファイル | 種類 | 説明 | 
|---|---|---|---|
| -lopenblas | libopenblas.so | Serial 版 | シングルスレッド(1コアのみ使用) | 
| -lopenblaso | libopenblaso.so | OpenMP 版 | マルチスレッド(OpenMP 使用) | 
| -lopenblasp | libopenblasp.so | pthread 版 | マルチスレッド(pthread 使用) | 
今回は科学技術計算で広く使用される OpenMP 版(-lopenblaso)を使用します。
コンパイルと実行
シングルスレッド版とマルチスレッド版(OpenMP 版)の 2 つをコンパイルして性能を比較します。
# Serial 版のコンパイル
g++ -O3 performance_test.cpp -o test_serial -lopenblas
# OpenMP 版のコンパイル(マルチスレッド)
g++ -O3 performance_test.cpp -o test_parallel -lopenblaso -fopenmp
Serial 版の実行
export OPENBLAS_NUM_THREADS=1
./test_serial
Available CPUs: 4
OPENBLAS_NUM_THREADS: 1
Running 10000x10000 matrix multiplication...
Test 1: 36785 ms
Test 2: 36775 ms
Test 3: 36772 ms
OpenMP 版の実行(2コア使用)
マルチコアスケーリングを確認するため、2 コアで実行します。
export OPENBLAS_NUM_THREADS=2
./test_parallel
Available CPUs: 4
OPENBLAS_NUM_THREADS: 2
Running 10000x10000 matrix multiplication...
Test 1: 18319 ms
Test 2: 18311 ms
Test 3: 18311 ms
OpenMP 版の実行(4コア使用)
c7a.xlarge のすべてのコアを使用して実行します。
export OPENBLAS_NUM_THREADS=4
./test_parallel
Available CPUs: 4
OPENBLAS_NUM_THREADS: 4
Running 10000x10000 matrix multiplication...
Test 1: 9192 ms
Test 2: 9200 ms
Test 3: 9210 ms
AMD EPYC 最適化を試してみる
GCC のアーキテクチャ固有最適化オプション
GCC の -march と -mtune で CPU アーキテクチャに応じた最適化を指定できます。通常は両方を同じ値に設定するようです。
AMD Zen アーキテクチャのターゲット
GCC での AMD Zen アーキテクチャサポート。
| GCC バージョン | サポートターゲット | 対応アーキテクチャ | 
|---|---|---|
| GCC 9 | -march=znver2 | Zen 2(EPYC 7002 "Rome") | 
| GCC 11 | -march=znver3 | Zen 3(EPYC 7003 "Milan") | 
| GCC 13 | -march=znver4 | Zen 4(EPYC 9004 "Genoa") | 
参考: GCC x86 Options - AMD x86 Options
Amazon Linux 2023 の GCC 11.5 では znver4 がサポートされていないため、以下の 2 パターンを試してみます。
- -march=native: 実行中の CPU を自動検出して最適化
- -march=znver3: Zen 3 向け最適化(Zen 4 でも動作)
参考: https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html
GCC 11.5 での最適化コンパイル
 -march=native での最適化
実行中の CPU を自動検出して最適化します。
g++ -O3 -march=native -mtune=native performance_test.cpp -o test_native -lopenblaso -fopenmp
4 コアで実行します。
export OPENBLAS_NUM_THREADS=4
./test_native
Available CPUs: 4
OPENBLAS_NUM_THREADS: 4
Running 10000x10000 matrix multiplication...
Test 1: 9185 ms
Test 2: 9203 ms
Test 3: 9193 ms
 -march=znver3 での最適化
Zen 3 向けに明示的に最適化します。Zen 4 は Zen 3 の上位互換であるため、このオプションも有効なはずです。
g++ -O3 -march=znver3 -mtune=znver3 performance_test.cpp -o test_znver3 -lopenblaso -fopenmp
4 コアで実行します。
export OPENBLAS_NUM_THREADS=4
./test_znver3
Available CPUs: 4
OPENBLAS_NUM_THREADS: 4
Running 10000x10000 matrix multiplication...
Test 1: 9304 ms
Test 2: 9309 ms
Test 3: 9301 ms
Clang 19 での Zen 4 最適化
Zen 4 に最適化してみたいので現時点で最新の Clang 19 を使ってコンパイルしてみます。
Clang 19 のインストール
sudo dnf install -y clang19
バージョンを確認します。
clang++-19 --version
clang version 19.1.7 (AWS 19.1.7-13.amzn2023.0.1)
Target: x86_64-amazon-linux-gnu
Thread model: posix
InstalledDir: /usr/lib64/llvm19/bin
Configuration file: /etc/clang19/x86_64-amazon-linux-gnu-clang++.cfg
 -march=znver4 での最適化コンパイル
Zen 4(EPYC 9R14)専用の最適化オプションを使ってコンパイルします。
clang++-19 -O3 -march=znver4 -mtune=znver4 performance_test.cpp -o test_znver4 -lopenblaso -fopenmp
4 コアで実行します。
export OPENBLAS_NUM_THREADS=4
./test_znver4
Available CPUs: 4
OPENBLAS_NUM_THREADS: 4
Running 10000x10000 matrix multiplication...
Test 1: 9226 ms
Test 2: 9216 ms
Test 3: 9213 ms
測定結果のまとめ
実行結果一覧
| 設定 | コンパイラ | オプション | コア数 | 実行時間(ms) | 速度比 | 
|---|---|---|---|---|---|
| Serial 版 | GCC 11.5 | -O3 -lopenblas | 1 | 36,777 | 1.00x | 
| OpenMP 版 | GCC 11.5 | -O3 -lopenblaso -fopenmp | 2 | 18,286 | 2.01x | 
| OpenMP 版 | GCC 11.5 | -O3 -lopenblaso -fopenmp | 4 | 9,240 | 3.98x | 
| -march=native | GCC 11.5 | -O3 -march=native -mtune=native | 4 | 9,166 | 4.01x | 
| -march=znver3 | GCC 11.5 | -O3 -march=znver3 -mtune=znver3 | 4 | 9,168 | 4.01x | 
| -march=znver4 | Clang 19 | -O3 -march=znver4 -mtune=znver4 | 4 | 9,212 | 3.99x | 
考察
マルチコアスケーリング
OpenBLAS はコア数に応じてほぼ理想的にスケールしました。ここは想定通りです。
- 1 コア → 2 コア: 2.01 倍の性能向上
- 1 コア → 4 コア: 3.98 倍の性能向上
理論値(2 倍、4 倍)にほぼ到達しており、OpenBLAS のマルチスレッド実装は効率的に動作しています。行列計算のような並列性の高いワークロードでは、コア数を増やすことで直線的に性能が向上することを確認できました。
コンパイラ最適化の効果は限定的
AMD 固有の最適化オプション(-march=native、-march=znver3、-march=znver4)による性能向上はほとんど見られませんでした。
- 標準の -O3: 9,240 ms
- -march=native: 9,166 ms(0.8% 改善)
- -march=znver3: 9,168 ms(0.8% 改善)
- -march=znver4: 9,212 ms(0.3% 改善)
今回のプログラムでは計算時間の大部分が事前コンパイル済みの OpenBLAS ライブラリで消費されます。 アプリケーションコードのコンパイラ最適化は、計算以外のわずかな部分にしか影響しません。
そのため、アプリケーション側のコンパイラ最適化による性能向上は限定的です。自前の計算ロジックを多く含むプログラムでは、最適化の効果が期待できます。
GCC 11.5(-march=znver3)と Clang 19(-march=znver4)の性能差もほとんどありません。これも OpenBLAS が計算の主体であったためです。Clang 19 は Zen 4 用の -march=znver4 をサポートしていますが、今回の検証では GCC 11.5 の -march=znver3 と同等の性能でした。
まとめ
AMD EPYC Zen 4 でのビルド環境構築と行列計算の性能検証を通じて、以下を学びました。
ビルド環境について
AMD でもビルドするだけなら比較的容易な印象です。ただし最大限のパフォーマンスを引き出すには AMD 固有の最適化オプションの理解が必要です。自前の計算ロジックを多く含むプログラムでは、AMD 固有の最適化オプションがより大きな効果を発揮する可能性があります。
学んだこと
今回の検証を通じて、以下の理解が深まりました。
- コンパイラ最適化オプション(-march、-mtune)の役割
- SIMD 拡張命令セット(AVX、AVX2、AVX-512)
- 事前コンパイル済み外部ライブラリによる依存すること
おわりに
Intel ワークステーションで運用している行列計算プログラムを AWS の AMD EPYC 環境で動かすための検証をしました。
C++ のビルド環境について実際に手を動かすことで理解が深まりました。とくにコンパイラ最適化オプション(-march、-mtune)の使い方や、SIMD 拡張命令セット(AVX、AVX2、AVX-512)の確認方法について学ぶことができました。
OpenBLAS を使った行列計算では、コンパイラ最適化の効果が限定的であることも分かりました。Graviton で試したときも同じだったのでわかってはいたのですが改めて試してみました。









