OProfile による PostgreSQL 8.1 と MySQL 5.0 の性能分析

1.1概要

1.1.1はじめに

OS層の評価では2004年下期において、IO高負荷時のカーネル2.4の挙動を分析し、性能のパターンとして、CPUビジー型、IOビジー型、ロック競合型のそれぞれの特徴を分類し、定量的な評価方法を確立した。2005年上期においては2004年下期に開発された手法を利用しカーネル2.6のボトルネック分析をおこなった。その結果、カーネル2.4でみられたロック競合は発見できず、スケーラビリティにおいても性質の悪い問題は発見できなかった。そこでキャッシュミスに注目し分析を行ったところキャッシュポリュージョンとして知られる問題を発見し、それを解消するカーネルパッチを開発した。

2005年下期においてはカーネル2.6にはスケーラビリティ上の性質の悪い問題はないとの前提のもと、その上のRDBMSの挙動についてDB層と連携の元、性能分析手順の確立をめざして評価作業を行った。

1.1.2評価目的の概要

複数のRDBMS(PostgreSQLとMySQL)でDBT-1を実施し、性能分析手順を確立する。性能分析ツールとしてOprofileを利用し、RDBMSのボトルネックの発見、RDBMSのパラメータを変更したときのRDBMSカーネル挙動の変化等々の分析手法を確立する。

1.1.3評価方法の概要

RDBMS等の性能評価方法は以下のとおりになる。

  1. 性能評価対象を決定する。(今回はRDBMS(PostgreSQL/MySQL))

  2. プロファイリングツールや各種性能測定ツールを利用して、ベンチマークを実施する。

  3. 各種性能測定ツールから得られたデータを分析する。


今回はベンチマークとしてOSDL DBT-1を利用し、各種性能測定ツールとして、Linuxディストリビューションに標準的に実装されている、sar、vmstat、iostat、およびoprofileなどを利用した。そしてoprofileを利用したときの性能分析手法について手順化した。

2004年度下期ではoprofileを利用してLinux Kernelの性能限界を調査し、性能分析する手法を手順化し、2005年度上期ではその手順にそって性能限界を分析した。今期はその手法を踏襲しつつも、測定対象をLinux Kernelではなく一般的なOSSのRDBMSにし、RDBMSの各種設定パラメータを変更したときの挙動を中心に分析する手法を手順化した。

1.1.4分析・考察の概要

今回主な観点として、

  1. HyperThreading(以下HTと記す)を有効にした場合(ON)と無効にした場合(OFF)

  2. CPU数を増加させた場合

  3. DBMS(MySQL)の設定パラメータを変化させた場合

についてOprofileのデータを分析し、その特徴をどのように分析するか手順化した。

具体的には、注目する観点について

  1. それぞれの観点についてのOprofileのデータを取得する。

  2. 各Oprofileデータを比較し、性能が劣っているベンチマークで顕著に増えた関数を特定する。

  3. その関数についてソースコード等の分析をする。

1.1.5評価技法のまとめ

1.1.6今後の課題


1.2評価手順

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によるベンチマーク分析手法は下記のようになる。

  1. Oprofileの設定

  2. Oprofileの起動

  3. ベンチマークテストの実行(DBT-1)

  4. Oprofileの停止

  5. Oprofileレポート作成

  6. 分析


今回分析の対象をRDBMSとしたので、対象RDBMSはデバッグオプション付き(-g)でコンパイル、ビルドしておく。

今回のベンチマークでは

  1. PostgreSQL(ユニアデックス社)

  2. MySQL(住商情報システム)

との共同で分析をおこなった。


1.3性能・信頼性評価結果と分析・考察

1.3.1Oprofileデータの分析

性能上の問題が発見された場合の分析手順は下記の通りである。


  1. 性能上の問題が発見されたOprofileデータと性能上問題がないOprofileデータを取得する。

  2. 2つのOprofileデータのトップ10程度を比較し、問題のあった方で顕著に増えている個所に注目する。

  3. 当該個所周辺のソースコードを分析する。


1.3.1.1PostgreSQL

スケーラビリティ上の問題について

ユニアデックス(株)による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が性能上のボトルネックとなっていることが推定される。

一般的に言って、スケーラビリティ上の問題として、下記の問題が知られている。

    1. CPUビジー型

    2. IOビジー型

    3. ロック競合型

今回の事例ではロック競合によってスケーラビリティ上の問題が発生したと考えられる。

1.3.1.2MySQL

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位に登場する。

1.3.2考察

Oprofileのデータによって発見された性能上のボトルネックについて、それぞれのソースコードを分析した。


1.3.2.1PostgreSQL

まずに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がどこから呼ばれているのか、そのロックを取る必要があるのか、減らすことはできないかなどの検討が必要である。ロックの粒度を変更すること、ロックのアルゴリズムを変更することなどは、実装のアーキテクチャを抜本的に見直す必要があり、今回の実験では残念ながらそこまでの検討はできなかった。今後の課題としたい。


1.3.2.2MySQL

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での効果は得られなかった。


1.3.3評価手法のまとめと今後の課題

今回、下記の方法で性能評価を行い評価手順をまとめた。

  1. 性能上の問題が発見されたOprofileデータと性能上問題がないOprofileデータを取得する。

  2. 2つのOprofileデータのトップ10程度を比較し、問題のあった方で顕著に増えている個所に注目する。

  3. 当該個所周辺のソースコードを分析する。

今後の課題としては、PostgreSQLおよびMySQLで発見されたスケーラビリティ上の問題およびHTをONにした時の性能上の問題についてより詳細な分析を行い、問題点の解決をはかる点がある。

1何回イベントが発生したらサンプリングを行うかの割合

このデータの性能データ

  • 関連する性能データは登録されていません。

関連する考察データ

  • 関連する考察データは登録されていません。

コメント表示 コメント登録