OS層の評価では2004年下期において、IO高負荷時のカーネル2.4の挙動を分析し、性能のパターンとして、CPUビジー型、IOビジー型、ロック競合型のそれぞれの特徴を分類し、定量的な評価方法を確立した。2005年上期においては2004年下期に開発された手法を利用しカーネル2.6のボトルネック分析をおこなった。その結果、カーネル2.4でみられたロック競合は発見できず、スケーラビリティにおいても性質の悪い問題は発見できなかった。そこでキャッシュミスに注目し分析を行ったところキャッシュポリュージョンとして知られる問題を発見し、それを解消するカーネルパッチを開発した。
2005年下期においてはカーネル2.6にはスケーラビリティ上の性質の悪い問題はないとの前提のもと、その上のRDBMSの挙動についてDB層と連携の元、性能分析手順の確立をめざして評価作業を行った。
複数のRDBMS(PostgreSQLとMySQL)でDBT-1を実施し、性能分析手順を確立する。性能分析ツールとしてOprofileを利用し、RDBMSのボトルネックの発見、RDBMSのパラメータを変更したときのRDBMSカーネル挙動の変化等々の分析手法を確立する。
RDBMS等の性能評価方法は以下のとおりになる。
性能評価対象を決定する。(今回はRDBMS(PostgreSQL/MySQL))
プロファイリングツールや各種性能測定ツールを利用して、ベンチマークを実施する。
各種性能測定ツールから得られたデータを分析する。
今回はベンチマークとしてOSDL DBT-1を利用し、各種性能測定ツールとして、Linuxディストリビューションに標準的に実装されている、sar、vmstat、iostat、およびoprofileなどを利用した。そしてoprofileを利用したときの性能分析手法について手順化した。
2004年度下期ではoprofileを利用してLinux Kernelの性能限界を調査し、性能分析する手法を手順化し、2005年度上期ではその手順にそって性能限界を分析した。今期はその手法を踏襲しつつも、測定対象をLinux Kernelではなく一般的なOSSのRDBMSにし、RDBMSの各種設定パラメータを変更したときの挙動を中心に分析する手法を手順化した。
今回主な観点として、
HyperThreading(以下HTと記す)を有効にした場合(ON)と無効にした場合(OFF)
CPU数を増加させた場合
DBMS(MySQL)の設定パラメータを変化させた場合
についてOprofileのデータを分析し、その特徴をどのように分析するか手順化した。
具体的には、注目する観点について
それぞれの観点についてのOprofileのデータを取得する。
各Oprofileデータを比較し、性能が劣っているベンチマークで顕著に増えた関数を特定する。
その関数についてソースコード等の分析をする。
2004、2005年度の成果に基づいて、評価を行った。OprofileおよびDBT-1の実行手順に関しては各年度の成果報告書を参照されたい。具体的なコマンドの設定方法等についての記述がある。
まず始めに、ハードウェアがOProfileをサポートしているかどうかを確認するため、次のコマンドを実行する。
|
# opcontrol --list-events |
各種イベントが出力されたらOKである。
|
# opcontrol --list-events using timer interrupt |
もしも上記の出力だった場合は、CPUプロセッサが新しいためにOprofileで認識されていない可能性がある。/proc/cpuinfoでcpu model番号を確認し、それが4だとOprofileがサポートしていないので、下記のパッチにより、それをサポートするように修正する。
OprofileはサポートするCPUおよびプロセッサを下記のようにハードコードしているので、新規プロセッサ(cpu model番号が変化する場合)をサポートする場合は、いちいちソースコードを変更しなければならない。
|
diff -ur linux-2.6.9-11.19AX/arch/i386/oprofile/nmi_int.c linux-2.6.9-11.19AXcustom/arch/i386/oprofile/nmi_int.c --- linux-2.6.9-11.19AX/arch/i386/oprofile/nmi_int.c 2004-10-19 06:55:24.000000000 +0900 +++ linux-2.6.9-11.19AXcustom/arch/i386/oprofile/nmi_int.c 2005-12-22 13:54:09.000000000 +0900 @@ -313,7 +313,7 @@ { __u8 cpu_model = current_cpu_data.x86_model;
- if (cpu_model > 3) + if (cpu_model > 4) return 0;
#ifndef CONFIG_SMP
|
https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=176601
も参考にされたい。
サンプリングのイベントタイプにデフォルトのGLOBAL_POWER_EVENTSを使用するので、経過時間に比例したサンプリングデータとなる。また、カウントサイクル1は、このシステムのデフォルトの100,000を使用した(デフォルト値はシステムによって異なる)。
Oprofileによるベンチマーク分析手法は下記のようになる。
Oprofileの設定
Oprofileの起動
ベンチマークテストの実行(DBT-1)
Oprofileの停止
Oprofileレポート作成
分析
今回分析の対象をRDBMSとしたので、対象RDBMSはデバッグオプション付き(-g)でコンパイル、ビルドしておく。
今回のベンチマークでは
PostgreSQL(ユニアデックス社)
MySQL(住商情報システム)
との共同で分析をおこなった。
性能上の問題が発見された場合の分析手順は下記の通りである。
性能上の問題が発見されたOprofileデータと性能上問題がないOprofileデータを取得する。
2つのOprofileデータのトップ10程度を比較し、問題のあった方で顕著に増えている個所に注目する。
当該個所周辺のソースコードを分析する。
スケーラビリティ上の問題について
ユニアデックス(株)によるDBT-1ベンチマーク結果を分析した。その結果、CPU数を増加させていくと、CPU数が8で性能のピークをむかえ、CPU数をそれ以上増やしても性能向上に繋がらない。

表 1
|
|
|
|
|
|
|
CLIENT数 (eu) |
|
|
|
|
|
|
|
|
計測環境 |
|
|
|
800 |
1600 |
2400 |
3200 |
4000 |
4800 |
5600 |
6400 |
7200 |
8000 |
|
CPU=1 |
110.2 |
|
|
|
|
|
|
|
|
|
|||
|
CPU=2 |
110.3 |
221.0 |
283.4 |
279.4 |
281.5 |
277.4 |
277.6 |
|
|
|
|||
|
CPU=4 |
110.5 |
220.9 |
331.3 |
440.8 |
509.1 |
509.2 |
507.4 |
|
|
|
|||
|
CPU=8 |
110.5 |
221.0 |
331.2 |
442.1 |
552.1 |
651.5 |
693.0 |
681.9 |
664.8 |
666.5 |
|||
|
CPU=12 |
|
221.2 |
331.6 |
441.3 |
512.9 |
546.1 |
543.1 |
512.7 |
483.2 |
|
|||
|
CPU=16 |
|
221.2 |
332.0 |
442.3 |
507.6 |
527.2 |
516.3 |
499.3 |
487.5 |
479.6 |
|||
そこで、EU5600でCPU数8および12でOprofileデータを取得し分析した。
表 2
|
CPU数=8 MEMORYサイズ=16GB CLIENT=5600 |
|
|||
|
CPU: P4 / Xeon, speed 2801.8 MHz (estimated) |
|
|||
|
Counted MEMORY_CANCEL events (cancelled requesets in data cache address control unit) with a unit mask of 0x08 (conflicts due to 64k aliasing) count 3000 |
||||
|
|
samples |
% |
app name |
symbol name |
|
1 |
10222096 |
7.2162 |
postgres |
AllocSetAlloc |
|
2 |
5887663 |
4.1563 |
libc-2.3.4.so |
memcpy |
|
3 |
5828560 |
4.1146 |
postgres |
element_alloc |
|
4 |
4518085 |
3.1895 |
postgres |
HeapTupleSatisfiesSnapshot |
|
5 |
4054838 |
2.8625 |
postgres |
SearchCatCache |
|
6 |
3112045 |
2.1969 |
postgres |
hash_search |
|
7 |
2972878 |
2.0987 |
postgres |
LWLockAcquire |
|
8 |
2700978 |
1.9067 |
libc-2.3.4.so |
malloc |
|
9 |
2020607 |
1.4264 |
postgres |
nocachegetattr |
|
10 |
2001770 |
1.4131 |
libc-2.3.4.so |
malloc_consolidate |
表 3
|
CPU数=12 MEMORYサイズ=16GB CLIENT=5600 |
|
|||
|
CPU: P4 / Xeon, speed 2801.8 MHz (estimated) |
|
|||
|
Counted BSQ_CACHE_REFERENCE events (cache references seen by the bus unit) with a unit mask of 0x200 (read 3rd level cache miss) count 3000 |
||||
|
|
samples |
% |
app name |
symbol name |
|
1 |
1758277 |
15.0155 |
postgres |
HeapTupleSatisfiesSnapshot |
|
2 |
780948 |
6.6692 |
postgres |
s_lock |
|
3 |
656990 |
5.6106 |
postgres |
LWLockAcquire |
|
4 |
474467 |
4.0519 |
vmlinux |
LKST_ETYPE_SYSV_IPC_SEMOP_HEADER_hook |
|
5 |
349292 |
2.9829 |
postgres |
hash_search |
|
6 |
292779 |
2.5003 |
postgres |
wchareq |
|
7 |
282569 |
2.4131 |
postgres |
PinBuffer |
|
8 |
243217 |
2.077 |
postgres |
SearchCatCache |
|
9 |
230091 |
1.9649 |
postgres |
slot_deform_tuple |
|
10 |
226121 |
1.931 |
vmlinux |
kmem_cache_create |
ここで特徴的なのはCPU数12個のときs_lockが2位(約6.67%)に浮上している。CPU数8個のときはトップ10どころかトップ50にも登場していない。従ってCPU数を増加させたときs_lockが性能上のボトルネックとなっていることが推定される。
一般的に言って、スケーラビリティ上の問題として、下記の問題が知られている。
CPUビジー型
IOビジー型
ロック競合型
今回の事例ではロック競合によってスケーラビリティ上の問題が発生したと考えられる。
HTON/OFFでのデータの分析例
HT-OFFの例
|
$ head -26 */summary.*|grep -v warning|less ==> U2800-HTOFF/summary.out <== CPU: P4 / Xeon, speed 3591.64 MHz (estimated) Counted GLOBAL_POWER_EVENTS events (time during which processor is not stopped) with a unit mask of 0x01 (mandatory) count 100000 samples % app name symbol name 6673106 8.5252 mysqld btr_search_guess_on_hash 6325149 8.0806 mysqld pthread_mutex_trylock 6105048 7.7994 mysqld rec_get_offsets_func 5481538 7.0029 mysqld memcpy 4593693 5.8686 mysqld __pthread_unlock 2842017 3.6308 mysqld row_search_for_mysql 1841049 2.3520 mysqld cmp_dtuple_rec_with_match 1274417 1.6281 mysqld btr_cur_search_to_nth_level 1119738 1.4305 mysqld buf_page_get_known_nowait
|
HTONの例:
|
==> U2800-HTON/summary.out <== CPU: P4 / Xeon with 2 hyper-threads, speed 3591.94 MHz (estimated) Counted GLOBAL_POWER_EVENTS events (time during which processor is not stopped) with a unit mask of 0x01 (mandatory) count 100000 samples % app name symbol name 14939404 7.8026 mysqld rec_get_offsets_func 13518106 7.0602 mysqld pthread_mutex_trylock 11911766 6.2213 mysqld btr_search_guess_on_hash 10490744 5.4791 mysqld memcpy 8590047 4.4864 mysqld __pthread_unlock 7218939 3.7703 mysqld row_search_for_mysql 6278792 3.2793 mysqld mutex_spin_wait 5520007 2.8830 mysqld cmp_dtuple_rec_with_match 3702504 1.9337 mysqld btr_cur_search_to_nth_level 3625952 1.8938 mysqld mtr_memo_slot_release
|
このHTをONとOFFの差に注目して、その差分のところを集中的に分析することになる。上記の例でいえば、mutex_spin_wait()はHT-OFFの場合はトップ10に出てこないが、HT-ONでは7位に登場する。
Oprofileのデータによって発見された性能上のボトルネックについて、それぞれのソースコードを分析した。
まずにPostgreSQLのテストアンドセットの実装を確認した。これはs_lock()の実装が、TAS()というマクロを参照しているからである。TAS()はアトミックなテストアンドセットで下記の実装になっている。
|
#define TAS(lock) tas(lock)
static __inline__ int tas(volatile slock_t *lock) { register slock_t _res = 1;
/* * Use a non-locking test before asserting the bus lock. Note that the * extra test appears to be a small loss on some x86 platforms and a small * win on others; it's by no means clear that we should keep it. */ __asm__ __volatile__( " cmpb $0,%1 \n" " jne 1f \n" " lock \n" " xchgb %0,%1 \n" "1: \n" : "+q"(_res), "+m"(*lock) : : "memory", "cc"); return (int) _res; } |
cmpbでlockと0を比較し、等しくなかったら(ロックが既に誰かに取得されているので、_resすなわち1を返す。lockが0だったら、まだ誰にもロックを取得されていないので、lock xchgb _res, lockによりアトミックに_resとlockを交換する。その時点でlockの値が0ならば_resの値が0になりlockの値が1になる。そしてreturn _resで0が返る。一方、アトミックに_resとlockを交換する直前にlockの値が1になっていると、(誰かにロックを取得されてしまった場合)、_resの値は1になるので、1が返る。これはロックが取得できなかったことを示す。
まとめると、TAS()ではロックが取得できると0を取得できないと1を返すという実装になっている。
s_lock()の当該部分は以下の通りである。単純なスピンループである。毎回spins_per_delay回分スピンループを行い、それでも、ロックを取得できなければ、pg_usleep()で自発的にスリープするというアルゴリズムである。
ロック競合によって8CPUより多いCPUにおいてスケーラビリティが出ていないので、s_lockに問題があると考え、その部分を改良するパッチを考案した。
while (TAS(lock))
{
/* CPU-specific delay each time through the loop */
SPIN_DELAY();
/* Block the process every spins_per_delay tries */
if (++spins >= spins_per_delay)
{
if (++delays > NUM_DELAYS)
s_lock_stuck(lock, file, line);
if (cur_delay == 0) /* first time to delay? */
cur_delay = MIN_DELAY_MSEC;
pg_usleep(cur_delay * 1000L);
/* increase delay by a random fraction between 1X and 2X */
cur_delay += (int) (cur_delay *
(((double) random()) / ((double) MAX_RANDOM_VALUE)) + 0.5);
/* wrap back to minimum delay when max is exceeded */
if (cur_delay > MAX_DELAY_MSEC)
cur_delay = MIN_DELAY_MSEC;
spins = 0;
}
}
単純なTAS()によるスピンロックではなく、内側にlock変数を確認するという2重ループにしてみた。結論から言うとこのパッチではスケーラビリティ上の問題は解決しなかった。
下記のパッチの意味は、TAS()がアトミックなテストアンドセットを実現するために、バスをロックしてメモリをアクセスしているためにコストの高いオペレーションとなっている。そこで2重ループにして、lock変数が1である間だけループを繰り返すようにした。この内側のループでは、lock変数の値はCPUキャッシュに存在していると仮定できるので、メインメモリまでアクセスしないし、メモリバスを占有しないので、コストの安いオペレーションとなる。その結果スケーラビリティを向上させるのではないかと考えたのである。
PostgreSQLのスケーラビリティ上の問題
|
$ diff -up s_lock.c.orig s_lock.c --- s_lock.c.orig 2005-11-23 03:23:19.000000000 +0900 +++ s_lock.c 2006-02-27 19:00:39.626059832 +0900 @@ -99,6 +99,12 @@ s_lock(volatile slock_t *lock, const cha
while (TAS(lock)) { + /* This double while-loop does not lock the memory bus. */ + /* The CPU will access the CPU cache of *lock variable */ + /* instead of the main memory. */ + /* So this implementation scales well. */ + while (*lock) + { /* CPU-specific delay each time through the loop */ SPIN_DELAY();
@@ -127,6 +133,7 @@ s_lock(volatile slock_t *lock, const cha
spins = 0; } + } }
/*
|
しかしこのパッチでは効果がなかった。s_lockがどこから呼ばれているのか、そのロックを取る必要があるのか、減らすことはできないかなどの検討が必要である。ロックの粒度を変更すること、ロックのアルゴリズムを変更することなどは、実装のアーキテクチャを抜本的に見直す必要があり、今回の実験では残念ながらそこまでの検討はできなかった。今後の課題としたい。
MySQLについてもHTON時に顕著に実行時間が増加したmutex_spin_waitの周辺をソースコードを元に検討した。
この実装はロックが取得できるまで、ループするもので、ループ内部でut_delay()により自発的に待っている。ただし、ut_delay()の実装は単なるループでの待ちなので非常にコストが高く、HTの場合、物理CPU上の他の論理CPUへの影響が非常に大きい実装である。
|
while (mutex_get_lock_word(mutex) != 0 && i < SYNC_SPIN_ROUNDS) { if (srv_spin_wait_delay) { ut_delay(ut_rnd_interval(0, srv_spin_wait_delay)); }
i++; }
|
MySQLのボトルネック
|
$ diff -pu ut0ut.c.orig ut0ut.c --- ut0ut.c.orig 2005-10-17 10:27:43.000000000 +0900 +++ ut0ut.c 2006-02-28 11:59:16.777840496 +0900 @@ -290,6 +290,13 @@ ut_delay( j = 0;
for (i = 0; i < delay * 50; i++) { + /* When executing a spin-wait loop on the Hyper-Threading + processor, the processor can suffer a severe performance + penalty. The pause instruction provides a hint to the + processor. Please refer IA-32 Intel Architecture + Software Developers Manual, Vol 3. */ + __asm__ __volatile__( + "pause; \n"); j += i; }
|
そこで、Intelのマニュアルに従ってループの内側にpause命令を入れHTがONの時の影響を少なくすることを試みた。しかし、DBT-1での効果は得られなかった。
今回、下記の方法で性能評価を行い評価手順をまとめた。
性能上の問題が発見されたOprofileデータと性能上問題がないOprofileデータを取得する。
2つのOprofileデータのトップ10程度を比較し、問題のあった方で顕著に増えている個所に注目する。
当該個所周辺のソースコードを分析する。
今後の課題としては、PostgreSQLおよびMySQLで発見されたスケーラビリティ上の問題およびHTをONにした時の性能上の問題についてより詳細な分析を行い、問題点の解決をはかる点がある。
1何回イベントが発生したらサンプリングを行うかの割合