仮想化環境における MMU
仮想化環境下で扱うメモリアドレスには以下の種類がある。
- ゲスト仮想アドレス: ゲスト OS から見える仮想アドレス
- ゲスト物理アドレス: ゲスト OS から見える物理アドレス。ハイパーバイザーがエミュレートした擬似物理アドレス
- ホスト仮想アドレス: ホスト OS から見える仮想アドレス
- ホスト物理アドレス: ホスト OS から見える物理アドレス
上記の通り、ゲスト物理アドレスはハイパーバイザーがエミュレートした擬似的なものとなる。このゲスト物理アドレスでは実際の物理メモリにアクセスできないため、ホスト物理アドレスに変換する必要がある。
Xen や KVM ではハイパーバイザーがシャドウページングという方法で MMU のエミュレーションを行う。
シャドウページングでは、ハイパーバイザーがゲスト OS のページテーブルの設定処理を検出して、ゲスト OS のページテーブル設定処理をエミュレートする。ハイパーバイザーはゲスト仮想アドレスをホスト物理アドレスに変換するテーブル (シャドウページテーブル) を設定する。
EPT とは
EPT (Extended Page Talbes) は Intel の CPU に実装されているハードウェアの仮想化支援機構。MMU をハードウェア側で仮想化することにより、ハイパーバイザーのオーバーヘッドを軽減し、ゲスト OS の処理を高速化することができる。AMD の CPU にも NPT (Nested Page Tables) という同様の機能がある。
EPT ではハイパーバイザーがゲスト物理アドレスをホスト物理アドレスに変換するためのテーブルを用意する。EPT を有効にした場合は、ゲスト OS 稼働時は CPU がこのテーブルを利用してゲスト仮想アドレスをホスト物理アドレスに自動的に変換する。
CPU に EPT が実装されている場合、/proc/cpuinfo の flags に ept が表示される。
$ cat /proc/cpuinfo | head -25
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 79
model name : Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
stepping : 1
microcode : 0xb000022
cpu MHz : 2986.322
cache size : 46080 KB
physical id : 0
siblings : 36
core id : 0
cpu cores : 18
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 20
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
bogomips : 4599.88
clflush size : 64
cache_alignment : 64
address sizes : 46 bits physical, 48 bits virtual
power management:
Linux の最新ディストリビューションでは基本的に EPT は有効になっている。
KVM を利用した場合は、/sys/module/kvm_intel/parameters/ept が Y であれば EPT が有効になっている。
$ cat /sys/module/kvm_intel/parameters/ept
Y
EPT を感じる
以下のようなコードを用意して、EPT の効力を肌で感じてみましょう。
#include <unistd.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#define BUFFER_SIZE (1024*1024*1024)
#define PAGE_SIZE 4096
int main(void) {
char *p;
time_t t;
char *s;
t = time(NULL);
s = ctime(&t);
p = malloc(BUFFER_SIZE);
if (p == NULL)
err(EXIT_FAILURE, "malloc() failed.");
printf("%.*s: allocated %d MB.\n", (int)(strlen(s) - 1), s, BUFFER_SIZE/(1024*1024));
int i = 0;
int j = 0;
for (i = 0; i < BUFFER_SIZE; i += PAGE_SIZE) {
p[i] = 0;
if (j % 4096== 0)
printf("%.*s: touched %d MB.\n", (int)(strlen(s) - 1), s, i / (1024*1024));
j++;
}
printf("%.*s: touched %d MB.\n", (int)(strlen(s) - 1), s, BUFFER_SIZE / (1024*1024));
exit(EXIT_SUCCESS);
}
基本的には、メモリを確保して順番にアクセスしているだけです。
それではまず EPT 無しの状態で測ってみましょう。
まずは running なゲスト OS を shutdown します。
$ sudo virsh list --all
Id Name State
----------------------------------------------------
9 CentOS7 running
$ sudo virsh shutdown CentOS7
次に EPT を無効化します。
$ sudo rmmod kvm_intel
$ sudo modprobe kvm_intel ept=0
$ cat /sys/module/kvm_intel/parameters/ept
N
それでは実際にログインしてコードを実行しましょう。
$ sudo virsh start CentOS7 --console
$ gcc -o EPT-Test EPT-Test.c
$ time ./EPT-Test
Tue Jun 19 03:47:26 2018: allocated 1024 MB.
Tue Jun 19 03:47:26 2018: touched 0 MB.
Tue Jun 19 03:47:26 2018: touched 16 MB.
Tue Jun 19 03:47:26 2018: touched 32 MB.
Tue Jun 19 03:47:26 2018: touched 48 MB.
Tue Jun 19 03:47:26 2018: touched 64 MB.
...
Tue Jun 19 03:47:26 2018: touched 960 MB.
Tue Jun 19 03:47:26 2018: touched 976 MB.
Tue Jun 19 03:47:26 2018: touched 992 MB.
Tue Jun 19 03:47:26 2018: touched 1008 MB.
Tue Jun 19 03:47:26 2018: touched 1024 MB.
real 0m1.195s
user 0m0.420s
sys 0m0.769s
real が 1.195s でした。
次に EPT を有効化した状態で測定します。
一旦、再度ゲスト OS を停止し、EPT を有効化します。
$ sudo virsh shutdown CentOS7
$ sudo rmmod kvm_intel
$ sudo modprobe kvm_intel ept=1
$ cat /sys/module/kvm_intel/parameters/ept
Y
ゲスト OS を起動し、コードを実行します。
$ sudo virsh start CentOS7 --console
$ time ./EPT-Test
Tue Jun 19 03:49:55 2018: allocated 1024 MB.
Tue Jun 19 03:49:55 2018: touched 0 MB.
Tue Jun 19 03:49:55 2018: touched 16 MB.
Tue Jun 19 03:49:55 2018: touched 32 MB.
Tue Jun 19 03:49:55 2018: touched 48 MB.
Tue Jun 19 03:49:55 2018: touched 64 MB.
...
Tue Jun 19 03:49:55 2018: touched 960 MB.
Tue Jun 19 03:49:55 2018: touched 976 MB.
Tue Jun 19 03:49:55 2018: touched 992 MB.
Tue Jun 19 03:49:55 2018: touched 1008 MB.
Tue Jun 19 03:49:55 2018: touched 1024 MB.
real 0m0.337s
user 0m0.005s
sys 0m0.330s
real が 0.337s でした。
測定は 1 度で、本当は統計を取るべきですが、これは驚異的ですね。
UnixBench もやってみます。UnixBench に関してはここが詳しいです。
まずは EPT 無しで。
$ yum -y install perl perl-Time-HiRes make gcc git
$ git clone https://github.com/kdlucas/byte-unixbench
$ cd byte-unixbench/UnixBench
$ ./Run
結果は以下。
Benchmark Run: Tue Jun 19 2018 04:42:43 - 05:11:43
1 CPU in system; running 1 parallel copy of tests
Dhrystone 2 using register variables 35990073.6 lps (10.0 s, 7 samples)
Double-Precision Whetstone 3462.4 MWIPS (15.3 s, 7 samples)
Execl Throughput 407.1 lps (29.6 s, 2 samples)
File Copy 1024 bufsize 2000 maxblocks 116965.5 KBps (30.0 s, 2 samples)
File Copy 256 bufsize 500 maxblocks 29485.7 KBps (30.0 s, 2 samples)
File Copy 4096 bufsize 8000 maxblocks 449854.3 KBps (30.0 s, 2 samples)
Pipe Throughput 128515.3 lps (10.0 s, 7 samples)
Pipe-based Context Switching 44579.4 lps (10.0 s, 7 samples)
Process Creation 1300.7 lps (30.0 s, 2 samples)
Shell Scripts (1 concurrent) 787.3 lpm (60.1 s, 2 samples)
Shell Scripts (8 concurrent) 107.9 lpm (60.3 s, 2 samples)
System Call Overhead 71169.6 lps (10.0 s, 7 samples)
System Benchmarks Index Values BASELINE RESULT INDEX
Dhrystone 2 using register variables 116700.0 35990073.6 3084.0
Double-Precision Whetstone 55.0 3462.4 629.5
Execl Throughput 43.0 407.1 94.7
File Copy 1024 bufsize 2000 maxblocks 3960.0 116965.5 295.4
File Copy 256 bufsize 500 maxblocks 1655.0 29485.7 178.2
File Copy 4096 bufsize 8000 maxblocks 5800.0 449854.3 775.6
Pipe Throughput 12440.0 128515.3 103.3
Pipe-based Context Switching 4000.0 44579.4 111.4
Process Creation 126.0 1300.7 103.2
Shell Scripts (1 concurrent) 42.4 787.3 185.7
Shell Scripts (8 concurrent) 6.0 107.9 179.8
System Call Overhead 15000.0 71169.6 47.4
========
System Benchmarks Index Score 221.7
次に EPT を有効にして測定した結果が以下です。
Benchmark Run: Tue Jun 19 2018 04:08:03 - 04:36:57
1 CPU in system; running 1 parallel copy of tests
Dhrystone 2 using register variables 36249477.4 lps (10.0 s, 7 samples)
Double-Precision Whetstone 3447.0 MWIPS (15.4 s, 7 samples)
Execl Throughput 3686.7 lps (30.0 s, 2 samples)
File Copy 1024 bufsize 2000 maxblocks 694441.0 KBps (30.0 s, 2 samples)
File Copy 256 bufsize 500 maxblocks 179576.5 KBps (30.0 s, 2 samples)
File Copy 4096 bufsize 8000 maxblocks 2358772.8 KBps (30.0 s, 2 samples)
Pipe Throughput 937300.5 lps (10.0 s, 7 samples)
Pipe-based Context Switching 182325.2 lps (10.0 s, 7 samples)
Process Creation 13425.7 lps (30.0 s, 2 samples)
Shell Scripts (1 concurrent) 5592.4 lpm (60.0 s, 2 samples)
Shell Scripts (8 concurrent) 767.2 lpm (60.0 s, 2 samples)
System Call Overhead 968740.3 lps (10.0 s, 7 samples)
System Benchmarks Index Values BASELINE RESULT INDEX
Dhrystone 2 using register variables 116700.0 36249477.4 3106.2
Double-Precision Whetstone 55.0 3447.0 626.7
Execl Throughput 43.0 3686.7 857.4
File Copy 1024 bufsize 2000 maxblocks 3960.0 694441.0 1753.6
File Copy 256 bufsize 500 maxblocks 1655.0 179576.5 1085.1
File Copy 4096 bufsize 8000 maxblocks 5800.0 2358772.8 4066.8
Pipe Throughput 12440.0 937300.5 753.5
Pipe-based Context Switching 4000.0 182325.2 455.8
Process Creation 126.0 13425.7 1065.5
Shell Scripts (1 concurrent) 42.4 5592.4 1319.0
Shell Scripts (8 concurrent) 6.0 767.2 1278.7
System Call Overhead 15000.0 968740.3 645.8
========
System Benchmarks Index Score 1146.3
非常に大きな違いです。