2011年12月24日土曜日

Fedora 16 で久々に Xen を試してみる

KVM の登場で、ほとんど触ることが無くなっていた Xen ですが、Linux 3.0 以降で Dom0 動作可能になっており、Fedora 16 なら簡単に Xen を動かせるので、試してみました。

■準備
yum install xen を実行して、Xen 関連のパッケージを導入します。grub2 のメニューは、自動で追加されるので、特に何もすることはなく、導入は簡単でした。再起動して、
Linux, with Xen 4.1 and Linux 3.1.2-1.fc16.x86_64
このように with Xen 4.1 がついたメニューを選択して起動すれば OK でした。

■実験内容
ホストには Fedora 16 x86_64 、ゲストには CentOS 6.2 x86_64 を使い、Xen 準仮想化、Xen 完全仮想化、KVM の3つの場合で、パフォーマンスがどの程度違うものかを比較。ハードは、ThinkPad X301 (Core2 Duo U9400 1.40GHz) です。
個人設備の関係上、ディスク性能が貧弱なため、ベンチマークには姫野ベンチを使いました。
なお、CentOS 6 では、DomU 用カーネルというのは無いので、カーネルの種類を切り替えるようなことはしなくて済みます。CentOS 5 の場合は、準仮想化で動くには xen カーネルを使う必要がありましたし、CentOS 4 では xenU カーネルを使う必要がありました。

■実験結果
単位MFLOPS
 84.511594  Xen 完全仮想化 ゲスト (CentOS 6.2 x86_64)
 86.336558  Xen 準仮想化 ゲスト   (CentOS 6.2 x86_64)
 96.247089  Xen Dom0   (Fedora16 x86_64、ゲストとは OS 環境が異なります)
106.986019  KVM ゲスト (CentOS 6.2 x86_64)
101.272400  KVM ホスト (Fedora16 x86_64、ゲストとは OS 環境が異なります)
姫野ベンチの結果のみですが、KVM のほうがパフォーマンス (CPU, メモリ周り) が良いという結果になりました。

■所感
ベンチを実行する前の段階で、体感上 Xen のほうがもっさりしていると感じました。普段、ネイティブ環境 (KVM ホスト側) で作業して、たまに仮想マシンを使うという私の利用形態だと、わざわざ性能劣化した Dom0 で通常作業はしたくないですし、Xen のメリットは全く無いという感想を持ちました。また個人的に Xen で気になる点として、Dom0 上で標準の性能ツール (sar 等) を採取しても、Xen ゲストの性能データを得られない点が挙げられます。KVM であれば、ホスト側の sar には、ゲストの値も計上されます。また、KVM ゲストは、KVM ホストからはプロセスに見えるため、top コマンドでも、KVM ゲスト毎の負荷状況を簡単にモニターできます。

■個人的結論
現時点で、Xen を使うメリットは全くないと感じました。Hyper-V のハイパーバイザー部分は Xen と共有しているそうですし、Oracle VM も Xen ベースだとのこと。それらがいくらチューニングしようとも、KVM よりもレイヤーが厚い分だけ、性能面で不利なのではないかと思われます。

2012-02-19追記
古い VT(CPUの仮想化支援機能)非対応のPC(P4 3.6G)を再利用しようとして、そんな時には Xen が役立つと再認識しました。Fedora 16 で (KVM無しの) QEMU を試しましたが、実用的に使うには無理がありました。QEMU では、ゲストが起動するのに 30 分かかるのが、Xen であれば許せる時間内(数分)でゲスト起動できました。

2011年12月18日日曜日

ksh で扱える整数の上限

bash と tcsh について、扱える整数の上限を調べましたが、さらに ksh についても調べてみました。
#!/bin/ksh93

function test_int_limit {
        typeset i=2147483646
        echo -e "INT_MAX-1 = "$i
        let i++
        echo -e "INT_MAX   = "$i
        let i++
        echo -e "INT_MAX+1 = "$i
echo
        i=4294967294
        echo -e "ULONG_MAX-1 = "$i
        let i++
        echo -e "ULONG_MAX   = "$i
        let i++
        echo -e "ULONG_MAX+1 = "$i
echo
        i=999999999999999998
        echo "i = "$i
        let i++
        echo "i+1 = "$i
        let i++
        echo "i+2 = "$i
echo
        i=9223372036854775806
        echo -e "LLONG_MAX-1 = "$i
        let i++
        echo -e "LLONG_MAX   = "$i
        let i++
        echo -e "LLONG_MAX+1 = "$i
echo
}

test_int_limit
ksh について、何点か補足メモ、、、

(1) 関数定義する場合、function キーワードを使用して、関数名の後ろに () はつけません。
つけると構文エラーになります。
(2) ローカル変数を使うには、typeset を指定します。
bash にも typeset がありますが、あちらは、Obsolute (廃止) とされています。
(3) 関数定義は、(1) 以外にも旧来の 関数名() { ... } という記載方法もある。
しかし、その場合に typeset によるローカル変数指定が機能しません。
なので、ksh の場合は (1) の書き方を使いましょう。

テストスクリプトを実行した結果が、下記です。
# uname -a                                                                                                                                                                      
Linux my41 2.6.18-274.12.1.el5 #1 SMP Tue Nov 29 13:37:46 EST 2011 x86_64 x86_64 x86_64 GNU/Linux
# ksh93 --version                                                                                                                                                               
  version         sh (AT&T Research) 93t+ 2010-02-02
# ./test_int_limit.ksh93 
INT_MAX-1 = 2147483646
INT_MAX   = 2147483647
INT_MAX+1 = 2147483648

ULONG_MAX-1 = 4294967294
ULONG_MAX   = 4294967295
ULONG_MAX+1 = 4294967296

i = 999999999999999998
i+1 = 999999999999999999
i+2 = 1e+18

LLONG_MAX-1 = 9223372036854775806
LLONG_MAX   = 9.22337203685477581e+18
LLONG_MAX+1 = 9.22337203685477581e+18
このように、18桁までは整数、19桁以上は浮動小数点数として扱ってくれるようです。
そうです。ksh は浮動小数点数を扱えます。一方、bash では浮動小数点数を扱えません。
ごく稀に、bash でも浮動小数点数を使いたい場合があり、やむなく gawk を呼び出したりしますが、ksh ならそのようなことをしなくても済むようです。
なお、ksh の typeset には、-i オプション (整数型指定) がありますが、これを指定すると内部的に int 扱いになるようで、INT_MAX までしか格納できなくなります。

bash と tcsh の扱える整数の上限については、次の記事参照。
bash で扱える整数の上限
tcsh で扱える整数の上限

tcsh で扱える整数の上限

以前、bash で扱える整数の上限 という記事を書きましたが、tcsh についても調べてみました。
#!/bin/tcsh

@ i=2147483646
echo "INT_MAX-1 = "$i
@ i++
echo "INT_MAX   = "$i
@ i++
echo "INT_MAX+1 = "$i
echo ""
@ i=4294967294
echo "ULONG_MAX-1 = "$i
@ i++
echo "ULONG_MAX   = "$i
@ i++
echo "ULONG_MAX+1 = "$i
echo ""
@ i=9223372036854775806
echo "LLONG_MAX-1 = "$i
@ i++
echo "LLONG_MAX   = "$i
@ i++
echo "LLONG_MAX+1 = "$i
echo ""
実行結果は次の通りです。
# tcsh --version                                                                                                                                                                
tcsh 6.17.00 (Astron) 2009-07-10 (x86_64-unknown-linux) options wide,nls,dl,al,kan,rh,color,filec
# ./test_int_limit.tcsh                                                                                                                                                         
INT_MAX-1 = 2147483646
INT_MAX   = 2147483647
INT_MAX+1 = -2147483648

ULONG_MAX-1 = -2
ULONG_MAX   = -1
ULONG_MAX+1 = 0

LLONG_MAX-1 = -2
LLONG_MAX   = -1
LLONG_MAX+1 = 0
このように、現在の tcsh では INT_MAX までしか扱え無いようです (注:実験は、64bit環境で行ってます) 。もし、仕事で csh を指定されているなど、どうしても csh 系でスクリプトを書くという方は、お気をつけて。
なお、あちこちで言われているように、csh 系でスクリプトを書くのはやめたほうが良いだろうと思います。最大の難点は、関数定義できない点だと思います。

参考URL:
有害な csh プログラミング
結論: csh はプログラミングにはまったく向かないツールであり、
そのような目的に使うことは厳しく禁じられるべきである!
8. まとめ ... そもそもが欠陥品なのです。

2011年12月17日土曜日

bash で外部コマンドを使わずに sleep する方法

bash には、いくつかのビルトインコマンド (test や printf 等) があり、それらを用いた場合は、外部コマンドの起動 (fork & exec) を節約できます。一般に、fork & exec は重いので、できればもうちょっとビルトインを増やしてくれるといいのにと思うことがしばしばあります。もちろん肥大化しないため、最小限となっているものとは思うのですが・・・

この記事では、外部コマンドの sleep を呼び出すことなく、bash スクリプトを秒単位でスリープさせる方法を示します。本日、ふと思いつきました。

bash のビルトインの read には、入力待ちをタイムアウトさせるための -t オプションが備わっています。これを使えば sleep の代用にできるはずです。ただし、絶対に入力が発生しない何かを read して、かつ、ブロックさせなくてはいけません。もし、C であれば、pipe() システムコールを呼んで、作成されたパイプを read すれば良いと思いますが、bash の構文で pipe() 相当をやる方法はありません。そこで、mkfifo で名前つきパイプ (FIFO) を作成することにしました。

次が実験に使ったスクリプトです。
#!/bin/bash
#
# Name: test_my_builtin_sleep.bash
#

setup_my_sleep() {
        local    sleep_fifo=/tmp/.sleep_fifo.$$.$RANDOM$RANDOM
        mkfifo  $sleep_fifo
        exec 9<>$sleep_fifo
        rm -f   $sleep_fifo
}

setup_my_sleep

my_sleep() {
        read -t $1 0<&9
}

time {
for ((i=0;i<20;i++))
do
        sleep 1
        read < /proc/uptime
        echo $REPLY
done
}

echo

time {
for ((i=0;i<20;i++))
do
        my_sleep 1
        read < /proc/uptime
        echo $REPLY
done
}
exec 9<>$sleep_fifo という書き方で、fd 9 を使って FIFO を rw モードでオープンしています。また、直後に FIFO のパスを rm していますが、オープン中なのでスクリプトが終わるまでは、FIFO を利用できます。

次のような実行結果になりました。
ThinkPad X301 Core2 1.40GHz 上の CentOS 5 x86_64 で実行しています。
# dmidecode | grep Version                                                                                                                                                      
        Version: 6EET54WW (3.14 )
        Version: ThinkPad X301
        Version: Not Available
        Version: Not Available
        Version: Intel(R) Core(TM)2 Duo CPU     U9400  @ 1.40GHz
        SBDS Version: 03.01
# cat /etc/redhat-release                                                                                                                                                       
CentOS release 5.7 (Final)
# uname -a
Linux my41 2.6.18-274.12.1.el5xen #1 SMP Tue Nov 29 14:18:21 EST 2011 x86_64 x86_64 x86_64 GNU/Linux
# ./test_my_builtin_sleep.bash 
4066.52 3937.55
4067.53 3938.46
4068.54 3939.47
4069.55 3940.45
4070.55 3941.45
4071.56 3942.46
4072.57 3943.47
4073.58 3944.47
4074.59 3945.47
4075.59 3946.48
4076.60 3947.48
4077.61 3948.49
4078.62 3949.49
4079.63 3950.50
4080.63 3951.50
4081.64 3952.49
4082.65 3953.50
4083.66 3954.50
4084.67 3955.51
4085.68 3956.51

real    0m20.161s
user    0m0.012s
sys     0m0.040s

4086.68 3957.52
4087.68 3958.52
4088.69 3959.53
4089.69 3960.53
4090.69 3961.53
4091.70 3962.53
4092.70 3963.54
4093.71 3964.54
4094.71 3965.55
4095.72 3966.55
4096.72 3967.55
4097.72 3968.56
4098.73 3969.56
4099.73 3970.57
4100.74 3971.57
4101.74 3972.58
4102.74 3973.58
4103.75 3974.58
4104.75 3975.59
4105.76 3976.59

real    0m20.079s
user    0m0.000s
sys     0m0.000s
前半は外部コマンドの sleep を使っており、後半は read -t & FIFO 方式です。なお、時間の経過が読み取り易いように、/proc/uptime を表示しています。
外部コマンドを用いた場合は、user+sys で 52 ミリ秒のオーバーヘッドが見えているのに対して、read -t & FIFO 方式では、ゼロ (オーバーヘッドが1ミリ秒未満) に見えています。期待通りです。

システムの負荷が高いと、fork & exec が遅延する場合があるので、実験で用いたスクリプトのように小刻みに sleep する処理は、影響を受け易いです。そんな場面に遭遇したら、read -t & FIFO 方式を試してみては。
それだったら、無理に bash で書かなくてもいいんじゃないのという声もありそうですが。
そりゃその通りで、適材適所で perl に Ruby に Python に・・・豊かな時代ですね。

2014-05-11追記
RHEL7.0 RC を試していたのですが、最近は read -t で少数(0.5秒とか)を指定できるようです。sleep も、いつの間にか。
[root@hoge ~]# uname -a
Linux hoge 3.10.0-121.el7.x86_64 #1 SMP Tue Apr 8 10:48:19 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux
[root@hoge ~]# rpm -q bash
bash-4.2.45-5.el7.x86_64
[root@hoge ~]# time sleep 0.5

real    0m0.501s
user    0m0.001s
sys     0m0.000s
[root@hoge ~]# time read -t 0.5

real    0m0.500s
user    0m0.000s
sys     0m0.000s
というわけで、read -t + FIFO でミリ秒単位の sleep も出来そうです。ご入用の方は、どうぞ。
なお、mkfifo を使わないことが可能なのかどうかは、未だに分からずです。bash のビルトインを自作すれば可能でしょうけども。
人気ブログランキングへ にほんブログ村 IT技術ブログへ