2011年11月23日水曜日

Linux Control Group

リソースの割り当てを柔軟に管理するための、Linuxカーネルの機能のこと(プロセスをグループ化したものに対してもリソースを割り当てることができる)。省略して cgroup と呼ばれる。

RedHatのマニュアル:リソース管理ガイド に細かく書いてあるので、手元にある CentOS-6.0 でざっと試してみた。

1. インストール
CentOSならあらかじめインストールされているらしい。無ければ yum install libcgroup を実行。そうすると/etc/cgconfig.conf, /etc/init.d/cgconfig などができる。
2. /etc/cgconfig.conf を編集
とりあえずメモリの使用量を制限するため、/etc/cgconfig.conf に数行追加。
group test {
  memory {
    memory.limit_in_bytes = 50K;
  }
}
これでハードリミット50KBの制限を与えたことになる。ソフトリミット "memory.soft_limit_n_bytes" もあるらしい。
3. cgconfigサービスを起動
# service cgconfig start
起動すると /cgroup/memory/test というディレクトリが生成される。その中にある疑似ファイルを利用して cgroup を制御したり、情報を読み取ったりすることができる。たとえば memory.limits_in_bytesを開いてみると、上記の cgconfig.conf に記載した50KBの制限がバイト単位で指定されているのがわかる(設定ファイルで指定した値と正確には一致しない)。
# cat /cgroup/memory/test/memory.limits_in_bytes
53248
4. /etc/cgrules.conf を編集

メモリの使用量を制限したいユーザー、プロセス名(またはコマンドパス)等を指定する。ここではとりあえず /rootディレクトリに置いてある a.out と b.out というコマンドを対象にすることにして、次の2行を追加。

root:/root/a.out memory test/
root:/root/b.out memory test/
5. cgredサービスを起動
# service cgred start
6. テスト
これで準備ができたので、とりあえず "hello, world"を出力して無限ループするプログラム(hello_and_stop.c)を書く。
/* hello_and_stop.c */
#include <stdio.h>
int main(void){
    printf("hello, world\n");
    while(1){}
    return 0;
}
コンパイルしたあと、cgroup の制限を超えるまで地道に a.out と b.out をバックグラウンド実行する。
# gcc hello_and_stop.c
# cp a.out b.out
# ./a.out &
[1] 5690
hello, world
# ./b.out &
[2] 5691
hello, world
# ./a.out &
[3] 5692
hello, world
# ./b.out &
[4] 5693
Memory cgroup out of memory: kill process 5690 (a.out) score 59 or a child
Killed process 5690 (a.out) vsz:3832kB, anon-rss:16kB, file-rss:300kB
hello, world
[1] Killed             ./a.out
#

4回目の実行で制限を超えて、一番最初に実行したプロセス(PID=5690)が kill された。b.out を実行したにもかかわらず a.out が kill されたので、グループ化してリソース管理が行われているに違いない。

7. ログを確認
制限を超えたときのログは、とりあえず/var/log/messagesに記録されていた。意外と親切でたくさんの情報が残っている。
Nov 23 20:05:58 localhost kernel: b.out invoked oom-killer: gfp_mask=0xd0, order=0, oom_adj=0
Nov 23 20:05:58 localhost kernel: b.out cpuset=/ mems_allowed=0
Nov 23 20:05:58 localhost kernel: Pid: 5693, comm: b.out Not tainted 2.6.32-71.29.1.el6.x86_64 #1
Nov 23 20:05:58 localhost kernel: Call Trace:
Nov 23 20:05:58 localhost kernel: [] ? cpuset_print_task_mems_allowed+0x91/0xb0
Nov 23 20:05:58 localhost kernel: [] oom_kill_process+0xcb/0x2e0
Nov 23 20:05:58 localhost kernel: [] ? select_bad_process+0xd0/0x110
Nov 23 20:05:58 localhost kernel: [] mem_cgroup_out_of_memory+0x73/0x90
Nov 23 20:05:58 localhost kernel: [] mem_cgroup_handle_oom+0x147/0x170
Nov 23 20:05:58 localhost kernel: [] ? autoremove_wake_function+0x0/0x40
Nov 23 20:05:58 localhost kernel: [] __mem_cgroup_try_charge+0x20d/0x240
Nov 23 20:05:58 localhost kernel: [] mem_cgroup_charge_common+0x7b/0xc0
Nov 23 20:05:58 localhost kernel: [] mem_cgroup_newpage_charge+0x48/0x50
Nov 23 20:05:58 localhost kernel: [] handle_pte_fault+0x7a0/0xad0
Nov 23 20:05:58 localhost kernel: [] ? tty_ioctl+0xf7/0x950
Nov 23 20:05:58 localhost kernel: [] handle_mm_fault+0x1ed/0x2b0
Nov 23 20:05:58 localhost kernel: [] do_page_fault+0x123/0x3a0
Nov 23 20:05:58 localhost kernel: [] page_fault+0x25/0x30
Nov 23 20:05:58 localhost kernel: Task in /test killed as a result of limit of /test
Nov 23 20:05:58 localhost kernel: memory: usage 52kB, limit 52kB, failcnt 48
Nov 23 20:05:58 localhost kernel: memory+swap: usage 264kB, limit 9007199254740991kB, failcnt 0
Nov 23 20:05:58 localhost kernel: Mem-Info:
Nov 23 20:05:58 localhost kernel: Node 0 DMA per-cpu:
Nov 23 20:05:58 localhost kernel: CPU    0: hi:    0, btch:   1 usd:   0
Nov 23 20:05:58 localhost kernel: CPU    1: hi:    0, btch:   1 usd:   0
Nov 23 20:05:58 localhost kernel: Node 0 DMA32 per-cpu:
Nov 23 20:05:58 localhost kernel: CPU    0: hi:  186, btch:  31 usd:  75
Nov 23 20:05:58 localhost kernel: CPU    1: hi:  186, btch:  31 usd: 127
Nov 23 20:05:58 localhost kernel: active_anon:88921 inactive_anon:97754 isolated_anon:0
Nov 23 20:05:58 localhost kernel: active_file:11086 inactive_file:12590 isolated_file:0
Nov 23 20:05:58 localhost kernel: unevictable:0 dirty:0 writeback:0 unstable:0
Nov 23 20:05:58 localhost kernel: free:21668 slab_reclaimable:5484 slab_unreclaimable:10278
Nov 23 20:05:58 localhost kernel: mapped:2668 shmem:0 pagetables:1732 bounce:0
Nov 23 20:05:58 localhost kernel: Node 0 DMA free:4776kB min:664kB low:828kB high:996kB active_anon:3944kB inactive_anon:6172kB active_file:132kB inactive_file:596kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15308kB mlocked:0kB dirty:0kB writeback:0kB mapped:36kB shmem:0kB slab_reclaimable:16kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:64kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no
Nov 23 20:05:58 localhost kernel: lowmem_reserve[]: 0 994 994 994
Nov 23 20:05:58 localhost kernel: Node 0 DMA32 free:81896kB min:44388kB low:55484kB high:66580kB active_anon:351740kB inactive_anon:384844kB active_file:44212kB inactive_file:49764kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:1017952kB mlocked:0kB dirty:0kB writeback:0kB mapped:10636kB shmem:0kB slab_reclaimable:21920kB slab_unreclaimable:41112kB kernel_stack:1576kB pagetables:6864kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no
Nov 23 20:05:58 localhost kernel: lowmem_reserve[]: 0 0 0 0
Nov 23 20:05:58 localhost kernel: Node 0 DMA: 24*4kB 13*8kB 6*16kB 4*32kB 2*64kB 3*128kB 1*256kB 1*512kB 1*1024kB 1*2048kB 0*4096kB = 4776kB
Nov 23 20:05:58 localhost kernel: Node 0 DMA32: 210*4kB 419*8kB 474*16kB 365*32kB 163*64kB 41*128kB 23*256kB 24*512kB 6*1024kB 1*2048kB 4*4096kB = 81888kB
Nov 23 20:05:58 localhost kernel: 25202 total pagecache pages
Nov 23 20:05:58 localhost kernel: 1523 pages in swap cache
Nov 23 20:05:58 localhost kernel: Swap cache stats: add 238961, delete 237438, find 53952/75027
Nov 23 20:05:58 localhost kernel: Free swap  = 1904848kB
Nov 23 20:05:58 localhost kernel: Total swap = 2064376kB
Nov 23 20:05:58 localhost kernel: 262128 pages RAM
Nov 23 20:05:58 localhost kernel: 7828 pages reserved
Nov 23 20:05:58 localhost kernel: 19543 pages shared
Nov 23 20:05:58 localhost kernel: 211774 pages non-shared
Nov 23 20:05:58 localhost kernel: Memory cgroup out of memory: kill process 5690 (a.out) score 59 or a child
Nov 23 20:05:58 localhost kernel: Killed process 5690 (a.out) vsz:3832kB, anon-rss:16kB, file-rss:300kB

ちなみに /etc/cgconfig.conf, /etc/cgrules.conf を変更したときには、それぞれ # service cgconfig restart, # service cgred restart を行うと変更が反映される。

2011年11月22日火曜日

GCC最適化オプション(-O0, -O1, -O2, -O3, -funroll-loops)

「An Introduction to GCC」に載っていた例を実際に試してみた。

環境は CentOS-6.0 で、

  • 2.6.32-71.29.1.el6.x86_64 GNU/Linux
  • gcc (GCC) 4.4.4 20100726 (Red Hat 4.4.4-13)

やったこと

本に載っていた"test.c"をそのまま使い、-O0, -O1, -O2, -O3, -O3 -funroll-loops の5種類について実行ファイルのサイズとtimeコマンドの結果を求めた。

コンパイルと実行に使ったPerlスクリプトは以下のとおり。

#!/usr/bin/perl
#
# test.pl
#
@a = ('-Wall -O0', '-Wall -O1', '-Wall -O2', '-Wall -O3', '-Wall -O3 -funroll-loops');
for($i=0; $i <= $#a; $i++){
  print("\[$a[$i]\]\n");
  system("gcc $a[$i] test.c && size -B ./a.out && time ./a.out");
  print("\n\n");
}

ここで、実行ファイルのサイズを求めるにあたっては、ls -lではなくGNU Binutilssizeコマンドを使うようにした。sizeコマンドはELFファイルの中のセクションごとにサイズを調べてくれるので、.textセクションのサイズも分かる。すなわち最適化による命令の量の変化がより明らかになる。

結果

# ./test.pl > test.log 2>&1
# cat test.log
[-Wall -O0]
   text    data     bss     dec     hex filename
   1387     492      16    1895     767 ./a.out
sum = 4e+38

real 0m1.677s
user 0m1.263s
sys 0m0.002s


[-Wall -O1]
   text    data     bss     dec     hex filename
   1329     492      16    1837     72d ./a.out
sum = 4e+38

real 0m0.793s
user 0m0.735s
sys 0m0.003s


[-Wall -O2]
   text    data     bss     dec     hex filename
   1369     492      16    1877     755 ./a.out
sum = 4e+38

real 0m0.457s
user 0m0.442s
sys 0m0.005s


[-Wall -O3]
   text    data     bss     dec     hex filename
   1369     492      16    1877     755 ./a.out
sum = 4e+38

real 0m0.456s
user 0m0.444s
sys 0m0.003s


[-Wall -O3 -funroll-loops]
   text    data     bss     dec     hex filename
   1665     492      16    2173     87d ./a.out
sum = 4e+38

real 0m0.503s
user 0m0.493s
sys 0m0.002s

性能がよいのは-O2-O3で、たぶん同じコード。funroll-loopsの最適化は今回の例では逆効果だった。

サイズを最も小さくできたのは-O1

補足:-Osオプション

-Osオプションを使うとサイズの最適化を行うことができる。これも試した結果、確かにサイズが最も小さくなった。

[-Wall -Os]
   text    data     bss     dec     hex filename
   1289     492      16    1797     705 ./a.out
sum = 4e+38

real 0m0.575s
user 0m0.544s
sys 0m0.003s

-Wall -O1の場合と比較すると、.textセクションのサイズは40バイト減り、実行時間も0.2sくらい減っている。

2011年11月20日日曜日

食物連鎖(Union-FInd木を用いる問題)

プログラミングコンテストチャレンジブックに載っていた、「食物連鎖」という問題(POJ 1182)。Union-Find木を応用して解けるらしいが、文字の説明だけではすんなり理解できなかったので概念を図にした。すっきりした。

2011年11月14日月曜日

GCC でコンパイルの中間ファイルを残す

-save-temps というオプションを使う。

例: プリプロセッサの出力を調べる

マクロ "NUM" を含むソース dtestval.c
#include <stdio.h>
int main(int argc, char *argv[])
{
  printf("Value of NUM is %d\n", NUM);
  return 0;
}
-save-tempsオプションとマクロ定義を与えてコンパイルしてみる
$ gcc -save-temps -DNUM="1 + 1" dtestval.c -o dtestval
$ ls -1 dtestval*
dtestval
dtestval.c
dtestval.i
dtestval.o
dtestval.s
プリプロセスの出力ファイル dtestval.i
# 1 "dtestval.c"
# 1 ""
(省略)
# 938 "/usr/include/stdio.h" 3 4

# 2 "dtestval.c" 2

int main(int argc, char *argv[])
{
  printf("Value of NUM is %d\n", 1 + 1);
  return 0;
}
"NUM" の定義が反映されて 1 + 1 になっているのが分かる。

2011年11月13日日曜日

GDB で core を作りつつプロセスを止める

生きているプロセスのプロセスIDを調べて、GDB をそのIDに attach してから kill(GDBの kill コマンド)すると core が出てくれる(もちろんプロセスの executable file はGCCの"-g"オプション付きでコンパイルされている必要がある)。

別の言葉で言うと、シェルから kill -QUIT pid する以外にも方法がある、ということ。