CPUIDでCPUスペックを確認する
超高性能プログラミング技術のメモ(14)
2020/7/22 動作周波数追記。コア数を修正
ここまで書き残してきた超高性能プログラミングのメモは、アセンブラ命令を多用しているため、CPUのスペックに思いっきり依存しています。しかし、CPUは、パッケージやマイクロ・アーキテクチャによって、使用できる拡張命令セットや、キャッシュメモリのサイズなどが変わります。そのため、同じプログラムでも同じ性能は出ないことになります。
これの対策としては、CPUに合わせたプログラムを複数用意しておき、プログラム内でCPUの種類を判定してプログラムを切り替える、といった方法が考えられます。CPUの種類は膨大な数になるため、実際にはマイクロ・アーキテクチャ単位で切り替えることになると思います。
CPUの種類やスペックは、アセンブラ命令であるCPUIDで取得することができます。Linuxの場合、内容を確認するだけであれば、下記コマンドで確認することができます。
$ less /proc/cpuinfo
しかし、手持ちのMacBookにはcpuinfoが見当たらなかったため、intelのマニュアル(下記)を参考にしてcpuinfoを自作しました。
CPUIDを取得する関数
CPUIDはアセンブラ命令なので、C言語から簡単に呼び出せるように、下記のラッパー関数を用意しました。
void cpuid( int eax, int ecx, cpuid_t *reg )
{
__asm__ __volatile__(
"cpuid\n\t"
: "=a"(reg->eax),"=b"(reg->ebx),"=c"(reg->ecx),"=d"(reg->edx)
: "a"(eax),"c"(ecx) );
}
CPUIDでは、レジスタeaxとecxに与えられた数字(eaxはリーフを、ecxはサブリーフを指定します)で、取得するデータが変わります。そのため、入力用のeaxとecxは直接の引数とし、出力用のレジスタeax, ebx, ecx, edxは構造体cpuid_tにまとめています。構造体cpuid_tは、次のように定義しています。
typedef struct _cpuid_t {
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
} cpuid_t;
CPUID命令では熱電源モニタリングなどの情報なども取得できますが、今回欲しいのは命令セットやキャッシュメモリの情報です。今回、eaxに与えるリーフの番号は、00H, 01H, 02H, 03H, 04H, 07H, 0BH, 1DH, 1EH, 80000001H, 80000007Hを使用しています。
(※各リーフのデータの読み取り方は、上述のマニュアルとご覧ください。)
cpuid.hには、CPUIDで取得したデータを格納する構造体と、上述のcpuid関数と合わせて、次の3つの関数を用意しています。
void cpuid( int eax, int ecx, cpuid_t *reg );
void read_cpuid_info( cpuid_info_t* out );
void write_cpuid_info( const cpuid_info_t* out );
read_cpuid_info()関数は、CPU情報を取得して、cupid_info_t構造体に格納します。write_cpuid_info()関数は、cpuid_info_t構造体をダンプする関数です。
CPU情報を確認する
上述のソースコードをmakeコマンドでビルドすると、cpuinfoプログラムが作成されます。このcpuinfoを実行すると、LInuxの/proc/cpuinfoとよく似た情報が出力されます。
$ make
$ ./cpuinfo
自分が使用しているMacBookは、次のように出力されました。
Maximum Input Value : 16H
vender id : GenuineIntel
proccessor type : Original OEM Processor
family : 06H
model : 0eH
stepping : 3
brand index : 0
clflush line size : 64B
Max num. of addr. : 16
Initial APIC ID : 0
Features : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse-36 clfsh ds acpi mmx fxsr sse sse2 ss htt tm pbe sse3 pclmulqdq dtes64 monitor ds-cpl vmx eist tm2 ssse3 sdbg fma cmpxchg16b xtpr pdcm pcid sse4.1 sse4.2 x2apic movbe popcnt tsc-deadline aesni xsave osxsave avx f16c rdrand
Cache or TLB : Data TLB: 1 GByte pages, 4-way set associative, 4 entries
Cache or TLB : Data TLB: 4 KByte pages, 4-way set associative, 64 entries
Cache or TLB : Instruction TLB: 2M/4M pages, fully associative, 8 entries
Cache or TLB : Instruction TLB: 4KByte pages, 8-way set associative, 64 entries
Cache or TLB : 64-Byte prefetching
Cache or TLB : Shared 2nd-Level TLB: 4 KByte /2 MByte pages, 6-way associative, 1536 entries. Also 1GBbyte pages, 4-way, 16 entries.
Extended Processor Signature : 00H
Extended Processor Information : lahf/sahf lzcnt prefetchw syscall/sysret exe_disable 1gb_page rdtscp intel64 invaliant_tscwbnoinvd
Processor Brand String : Intel(R) Core(TM) i5-6267U CPU @ 2.90GHz
Cache Line size (B) : 0
L2 Associativity field : Disabled
Cache size (KB) : 0
Physical Address Bits : 39
Linear Address Bits : 48
Processor Base Frequency (MHz) : 2900
Maximum Frequency (MHz) : 3300
Bus (Reference) Frequency (MHz) : 100
Extended Features : fsgsbase ia32_tsc_adjust sgx bmi1 avx2 smep bmi2 movsb/stosb invpcid fpucs/fpuds impx rdseed adx smap clflushopt proctrace md_clear iprs/ibpb stibp enum_ssbd
Shift size from x2APIC to Topology : 1
Number of logical processors : 2
Level number : 0
Level type : SMT
x2APIC ID : 0
上から見ていくと、まずfamilyとmodelから、モデル・シグネチャが06_0EHということが分かります。これを、Software Developer's ManualのVolume 4 Chapter 2で探してみると、”Intel Core Duo, Intel Core Solo processors”となっています。
次にキャッシュラインサイズ(clflush line size)が64Bということが分かります。以前、説明したように、キャッシュラインサイズはロード命令のによって自動的にキャッシュに転送されるデータサイズです。効率的にするには、この自動転送されるサイズを無駄にしないようにしなければなりません。
FeaturesとExtended Featuresを見ると、拡張命令セットの対応状況が分かります。MMX, SSE, SSE2, SSE3, SSE4.1, SSE4.2, AVX, AVX2, FMAという出力が見られるので、AVX2/FMAまでが使用可能です。残念ながら、少し古いチップなので、AVX512は未対応でした。
TLB(Translation Lookaside Buffer)は命令やデータのアドレスを格納しておくキャッシュメモリです。メモリの物理アドレスとCPUが使う論理アドレスの変換情報などが格納されています。
動作周波数は、通常時で2.9GHz、最大で3.3GHzでした。CPU使用量が高いときに、Tarbo-Boostテクノロジーで、動作周波数は3.3GHzまで自動的に上昇していくと考えられます。
最後に、論理プロセッサ数(Number of logical processors)は、そのままコア数と解釈して良いようで、プロセッサは2コアになります。また、Level typeがSMTとなっているので、ハイパー・スレッディング・テクノロジーが有効になっています。
キャッシュメモリ情報を確認する
自作cpuinfoでは、キャッシュメモリの情報も出力しています。
Cache No. : 0
Cache Type Field : Data Cache
Cache Level : 1
Self Init. Cache Lv. : 1
Fully Aassociative : No
Max Shared Logical Processors : 2
Max Packed Processor Cores : 8
System Coherency Line Size (B) : 64
Physical Line partitions : 1
Ways of associativity : 8
Number of Sets : 64
Write-Back Invalidate/Invalidate : No. WBINVD/INVD from threads sharing this cache acts upon lower level caches for threads sharing this cache.
Cache Inclusiveness : No. Cache is not inclusive of lower cache levels.
Complex Cache Indexing : No. Direct mapped cache.
Cache size (B) : 32768
Cache size (KB) : 32
Cache size (MB) : 0
こちらは、L1データキャッシュです。Max Shared Logical Processorsから、論理プロセッサ2個で共有されていることが分かります。Line Sizeは前述の通り64Bで、連想数(ways of associativity)が8、セット数(Number of sets)64なので、64B×8ways×64entries=32768B=32KBとなっています。これは、64B×64entries=4KB離れたデータが同じwayに8個まで格納されることを意味しています。例えば、4KBのストライドアクセスをすると、ずっと同じwayを使用してしまい、キャッシュメモリを使いきれないことになります。また、Write Backはしない(つまり、Write Through)ということも分かります。
Cache No. : 1
Cache Type Field : Instruction Cache
Cache Level : 1
Self Init. Cache Lv. : 1
Fully Aassociative : No
Max Shared Logical Processors : 2
Max Packed Processor Cores : 8
System Coherency Line Size (B) : 64
Physical Line partitions : 1
Ways of associativity : 8
Number of Sets : 64
Write-Back Invalidate/Invalidate : No. WBINVD/INVD from threads sharing this cache acts upon lower level caches for threads sharing this cache.
Cache Inclusiveness : No. Cache is not inclusive of lower cache levels.
Complex Cache Indexing : No. Direct mapped cache.
Cache size (B) : 32768
Cache size (KB) : 32
Cache size (MB) : 0
こちらは、L1命令キャッシュです。使用したアセンブラ命令が格納されています。スペックは、L1データキャッシュと同じですね。
Cache No. : 2
Cache Type Field : Unified Cache
Cache Level : 2
Self Init. Cache Lv. : 1
Fully Aassociative : No
Max Shared Logical Processors : 2
Max Packed Processor Cores : 8
System Coherency Line Size (B) : 64
Physical Line partitions : 1
Ways of associativity : 4
Number of Sets : 1024
Write-Back Invalidate/Invalidate : No. WBINVD/INVD from threads sharing this cache acts upon lower level caches for threads sharing this cache.
Cache Inclusiveness : No. Cache is not inclusive of lower cache levels.
Complex Cache Indexing : No. Direct mapped cache.
Cache size (B) : 262144
Cache size (KB) : 256
Cache size (MB) : 0
こちらは、L2データキャッシュです。Unified Cacheなので、共有されるキャッシュですが、最大共有論理プロセッサ数が2なので、コア間の共有というよりもスレッド間共有が主だと思います。ハイパースレッディングを停止すれば、2コアで共有できるかもしれません。また、ラインサイズ64B、連想数8個、セット数1024個なので、64B×8×1024=262144B=256KBとなっています。
Cache No. : 3
Cache Type Field : Unified Cache
Cache Level : 3
Self Init. Cache Lv. : 1
Fully Aassociative : No
Max Shared Logical Processors : 16
Max Packed Processor Cores : 8
System Coherency Line Size (B) : 64
Physical Line partitions : 1
Ways of associativity : 16
Number of Sets : 4096
Write-Back Invalidate/Invalidate : No. WBINVD/INVD from threads sharing this cache acts upon lower level caches for threads sharing this cache.
Cache Inclusiveness : Yes. Cache is inclusive of lower cache levels.
Complex Cache Indexing : Yes. A complex function is used to index the cache, potentially using all address bits.
Cache size (B) : 4194304
Cache size (KB) : 4096
Cache size (MB) : 4
こちらは、L3データキャッシュ(あるいはLLCデータキャッシュ)です。L2キャッシュと同じく共有キャッシュですが、こちらは最大共有論理プロセッサ数が16、パッケージング可能な最大コア数が8なので、8コア×2スレッド=16論理プロセッサの全てで共有されることが推測できます。キャッシュサイズは、ラインサイズ64B×連想数16×セット数4096=4MBとなっています。
プログラムで利用できるようにする
以上のような情報をcpuid関数で取得して、プログラム内で条件分岐すれば、CPUに応じて動的に切り替えを行うことができます。
しかし、動的切り替えまでは不要で、上記のような情報をプログラム内で静的に使用したい場合もあるので、自作プログラムにはC言語のプリプロセッサのマクロ定義として出力するプログラムも同梱してあります。下記のようなコマンドを実行してください。
$ ./make_cpu_header
手元のMacBookでの出力は次のとおりです。
#ifndef CPU_H_HEADER_GUIRD
#define CPU_H_HEADER_GUIRD
/**********************************/
/** Instruction Set Architecture **/
/**********************************/
#define HAVE_F16C 1
#define HAVE_FPU 1
#define HAVE_MMX 1
#define HAVE_FPU 1
#define HAVE_MMX 1
#define HAVE_SSE 1
#define HAVE_SSE2 1
#define HAVE_SSE3 1
#define HAVE_SSSE3 1
#define HAVE_SSE4_1 1
#define HAVE_SSE4_2 1
#define HAVE_AVX 1
#define HAVE_AVX2 1
#define HAVE_AVX512F 0
#define HAVE_AVX512DQ 0
#define HAVE_AVX512PF 0
#define HAVE_AVX512ER 0
#define HAVE_AVX512CD 0
#define HAVE_AVX512BW 0
#define HAVE_AVX512VL 0
#define HAVE_AVX512_IFMA 0
#define HAVE_AVX512_VBMI 0
#define HAVE_AVX512_VBMI2 0
#define HAVE_AVX512_VNNI 0
#define HAVE_AVX512_BITALG 0
#define HAVE_AVX512_4VNNIW 0
#define HAVE_AVX512_4FMDPS 0
#define HAVE_AVX512_VPOPCNTDQ 0
#define HAVE_AVX512_VP2INTERSECT 0
#define HAVE_AVX512_BF16 0
#define HAVE_AMX_BF16 0
#define HAVE_AMX_TILE 0
#define HAVE_AMX_INT8 0
/**********************************/
/** Instructions **/
/**********************************/
#define HAVE_FMA 1
#define HAVE_MOVBE 1
#define HAVE_POPCNT 1
#define HAVE_RDRAND 1
#define HAVE_PCLMULQDQ 1
#define HAVE_CMPXCHG16B 1
#define HAVE_RDMSR 1
#define HAVE_WRMSR 1
#define HAVE_CMPXCHG8B 1
#define HAVE_SYSENTER 1
#define HAVE_SYSEXIT 1
#define HAVE_CMOV 1
#define HAVE_FCOMI 1
#define HAVE_FCMOV 1
#define HAVE_CLFLUSH 1
#define HAVE_FXSAVE 1
#define HAVE_FXRSTOR 1
#define HAVE_RDSEED 0
#define HAVE_PREFETCHWT1 1
#define HAVE_LAHF 1
#define HAVE_SAHF 1
#define HAVE_LZCNT 1
#define HAVE_PREFETCHW 1
#define HAVE_SYSCALL 1
#define HAVE_SYSRET 1
#define HAVE_RDTSCP 1
#define HAVE_VPCLMULQDQ 0
/**********************************/
/** System Configuration **/
/**********************************/
#define HAVE_HTT 1
#define HAVE_X86_64 1
/**********************************/
/** Cache Configuration **/
/**********************************/
#define L1D_SIZE_B 32768
#define L1D_SIZE_KB 32
#define L1D_SIZE_MB 0
#define L1D_LINESIZE_B 64
#define L1D_PARTITION 1
#define L1D_ASSOC_WAYS 8
#define L1D_ASSOC_SETS 64
#define L1I_SIZE_B 32768
#define L1I_SIZE_KB 32
#define L1I_SIZE_MB 0
#define L1I_LINESIZE_B 64
#define L1I_PARTITION 1
#define L1I_ASSOC_WAYS 8
#define L1I_ASSOC_SETS 64
#define L2_SIZE_B 262144
#define L2_SIZE_KB 256
#define L2_SIZE_MB 0
#define L2_LINESIZE_B 64
#define L2_PARTITION 1
#define L2_ASSOC_WAYS 4
#define L2_ASSOC_SETS 1024
#define L3_SIZE_B 4194304
#define L3_SIZE_KB 4096
#define L3_SIZE_MB 4
#define L3_LINESIZE_B 64
#define L3_PARTITION 1
#define L3_ASSOC_WAYS 16
#define L3_ASSOC_SETS 4096
/**********************************/
#endif//CPU_H_HEADER_GUIRD
拡張命令セットとしては、AVX2までしか利用できませんね。インテルのマニュアルには、新しい拡張命令セットであるAMX(Advanced Matrix eXtensions)の情報も記載されていたので、AMXにも対応しています。ただし、AMX対応のCPUは2021年に発売が予定されています。
まとめ
CPUの情報を確認するためのプログラムを自作し、その出力を見ながらHPCプログラミングに必要そうな情報を読み取りました。それらをプログラムから利用できるように、マクロ定義するヘッダファイルを生成するプログラムをご紹介しました。
Intel製CPUにしか対応していませんが、もし良かったら試してみてください。とはいえ、Linuxなら/proc/cpuinfoで十分ですから必要ないですが・・・。
この記事が気に入ったらサポートをしてみませんか?