2011年11月27日日曜日

mawk は確かに速い(2倍くらい)

AWK Users JP の次の記事を読んで、手持ちのよく使う awk スクリプトを mawk で実行してみたところ、確かに速かったです。軒並み2倍速かった。

awk のパフォーマンス

試しは、Fedora 16 上で行いましたが、常用している CentOS 5 (or RHEL5) でも簡単に利用できるかどうか EPEL を見てみたら、有難いことに提供されてました。
というわけで、まず、インストールを。
[root@centos5 ~]# uname -a
Linux centos5 2.6.18-274.7.1.el5 #1 SMP Thu Oct 20 16:21:01 EDT 2011 x86_64 x86_64 x86_64 GNU/Linux
[root@centos5 ~]# yum install mawk --enablerepo=epel
Loaded plugins: downloadonly, fastestmirror
Loading mirror speeds from cached hostfile
 * base: www.ftp.ne.jp
 * epel: ftp.jaist.ac.jp
 * extras: www.ftp.ne.jp
 * updates: www.ftp.ne.jp
Setting up Install Process
Resolving Dependencies
--> Running transaction check
---> Package mawk.x86_64 0:1.3.4-5.20100625.el5 set to be updated
--> Finished Dependency Resolution

Dependencies Resolved

==========================================================================================
 Package         Arch              Version                          Repository       Size
==========================================================================================
Installing:
 mawk            x86_64            1.3.4-5.20100625.el5             epel            104 k

Transaction Summary
==========================================================================================
Install       1 Package(s)
Upgrade       0 Package(s)

Total download size: 104 k
Is this ok [y/N]: y
Downloading Packages:
mawk-1.3.4-5.20100625.el5.x86_64.rpm                               | 104 kB     00:00     
Running rpm_check_debug
Running Transaction Test
Finished Transaction Test
Transaction Test Succeeded
Running Transaction
  Installing     : mawk                                                               1/1 

Installed:
  mawk.x86_64 0:1.3.4-5.20100625.el5                                                      

Complete!
[root@centos5 ~]# 

次のテスト用の awk スクリプトは、a から zzzz までの文字列 (Linux の SCSI ディスク名の sdXX の XX 部分に使われている) を出力するものです。適度に重いかと。。
#
# Note: This awk script prints
#       a, b, ... z, aa, ab, ... zz, aaa, aab, ... zzz
#
function get_sdstr(n,   r,ret) {
        for (;;) {
                if (n <= 26) {
                        return letter[n]""ret

                } else {
                        r = n % 26 ; r = r ? r : 26
                        ret = letter[r]""ret
                        n = (n-r)/26
                }
        }
}

BEGIN {
        for (i=1; i <= 26; i++) {
                letter[i] = substr("abcdefghijklmnopqrstuvwxyz",i,1)
        }

        for (i=1; i <= (26+26*26+26*26*26+26*26*26*26); i++) {
                print get_sdstr(i)
        }
        exit(0)
}
これを、gawk と mawk でそれぞれ実行して、/usr/bin/time でパフォーマンス測定した結果が、次の通りです。
[root@centos5 ~]# md5sum test.awk 
ee228358d1513e48e0495e1e79f3956c  test.awk
[root@centos5 ~]# rpm -q gawk
gawk-3.1.5-14.el5
[root@centos5 ~]# rpm -q mawk
mawk-1.3.4-5.20100625.el5
[root@centos5 ~]# 
[root@centos5 ~]# /usr/bin/time gawk -f ./test.awk | md5sum
3.43user 0.01system 0:03.47elapsed 99%CPU (0avgtext+0avgdata 3824maxresident)k
0inputs+0outputs (0major+279minor)pagefaults 0swaps
a9de67bf77dec1b5285ee27f71ab3564  -
[root@centos5 ~]# 
[root@centos5 ~]# /usr/bin/time mawk -f ./test.awk | md5sum
1.48user 0.00system 0:01.49elapsed 99%CPU (0avgtext+0avgdata 2976maxresident)k
0inputs+0outputs (0major+220minor)pagefaults 0swaps
a9de67bf77dec1b5285ee27f71ab3564  -
このように、2倍くらい速いです。
まあ、処理内容によるでしょうけど、わたしが日頃使う手持ちスクリプトは軒並み2倍速かったので、これからは有難く常用させてもらおうと思います。作者さん&メンテナー様に感謝。

EPEL の使い方については、次の記事を参照ください。
CentOS 6 で ntfs をマウントする
なお、CentOS 6 (or RHEL6) 用の EPEL チャネルにも mawk ありました。

gawk で性能足りないということがあったら、最後の切り札に使える場合があるかも。ただし、GNU 拡張等、互換性に注意する必要がある。

2011年11月13日日曜日

Ruby からシステムコールを直接呼び出す方法

Ruby からシステムコールを直接呼び出す方法です。自分用のメモ。

perl には syscall() が用意されており、使ったことがありましたので、Ruby にもあるかな?とライブラリリファレンスを参照してみると、案の定ありました。
clock_gettime(CLOCK_MONOTONIC) を呼び出すサンプルです。
#!/usr/bin/env ruby

NR_clock_gettime = 228  # defined in asm-x86_64/unistd.h
CLOCK_MONOTONIC = 1     # CLOCK_MONOTONIC defined in linux/time.h
ts = ' ' * 16
syscall(NR_clock_gettime, CLOCK_MONOTONIC, ts)
(tv_sec, tv_nsec) = ts.unpack("l!l!")
printf "%d.%09d\n", tv_sec, tv_nsec

次が実行結果です。
# ./monotonic.rb ; cat /proc/uptime 
134772.112499324
134772.11 134476.00
なお、一般に、システムコール番号はアーキテクチャ毎に異なります。上記の 228 は、x86_64 の場合です。

■関連記事
経過時間の計測方法

2011年11月6日日曜日

ビルトインを自作して、bash で処理時間をミリ秒単位で計測

先日、bash で処理時間(経過時間)を 10 ミリ秒の粒度で計測する方法を書きました。おそらく、bash 自体やプロセス生成のオーバーヘッドを考えると、実用上はそれで十分と思います。
この記事では、いささかオーバースペック(と言うか諸々のオーバーヘッドを加味すると無意味かも? )ですが、bash のビルトイン(builtin)作成方法に興味があり、練習として、ミリ秒より細かく計測するためのインターフェースを作成してみました。

以下、CentOS 5.7 x86_64 上、root で作業しています。

まずは、bash のソース RPM (.src.rpm) を入手して、展開します。
http://vault.centos.org/5.7/os/SRPMS/
この時点で最新の bash-3.2-32.el5.src.rpm を使いました(作業環境と同じバージョン)。
# rpm -ivh bash-3.2-32.el5.src.rpm
# rpmbuild -bc /usr/src/redhat/SPEC/bash.spec
上の操作で、/usr/src/redhat/BUILD/bash-3.2/ の下にソースが展開されます。
ここで、bash ビルトインのコンパイル環境を整えるため、-bp ではなくて、-bc とします。
-bp のあとに、手動で configure という流れで作業すると、作成したビルトインの enable 時にリンクエラーになってしまいます(具体的には sh_xrealloc で引っかかりました)。

次に、ソースツリーの下の example/loadables の下へ作業ディレクトリを移動して、ひとまずサンプルをビルドして、使ってみます。
# cd /usr/src/redhat/BUILD/bash-3.2/examples/loadables/
# make -k
※ビルド過程は省略します。わたしの環境では、多少エラーが出てました。
# enable -f ./strftime strftime  ※もしリンクエラーになると、bash が落ちます
# enable | grep strftime         ※落ちなければロードされたはずですが、確認です
enable strftime
# strftime "%F %T"               ※試しに strftime を実行
2011-11-06 15:11:13

ここまでで、ビルトインを自作する環境が整います。あとは、サンプルを参考にしながら monotonic というビルトインを自作してみました。strftime.c を下敷きにしています。
     1  /* monotonic - loadable builtin interface to clock_gettime(CLOCK_MONOTONIC) */
     2  
     3  /* Copyright (C) 2011-2011
     4     luna2 at http://luna2-linux.blogspot.com/
     5     If you contact luna2, email to blue3waters at gmail.com.
     6  
     7     This file is subject to the GNU General Public License.
     8     It comes with NO WARRANTY.
     9   */
    10  
    11  /* See Makefile for compilation details. */
    12  
    13  #include <config.h>
    14  
    15  #if defined (HAVE_UNISTD_H)
    16  #  include <unistd.h>
    17  #endif
    18  
    19  #include "bashtypes.h"
    20  #include "posixtime.h"
    21  
    22  #include <stdio.h>
    23  
    24  #include "builtins.h"
    25  #include "shell.h"
    26  #include "common.h"
    27  #include "bashgetopt.h"
    28  
    29  #include <time.h>       /* clock_gettime */
    30  
    31  int
    32  monotonic_builtin (list)
    33    WORD_LIST *list ;
    34  {
    35    char  *tbuf = NULL ;
    36    size_t tbsize ;
    37    char  *tv_sec = NULL ;
    38    char  *tv_nsec = NULL ;
    39    struct timespec t ;
    40  
    41    if (list == 0) {
    42      builtin_usage() ;
    43      return EX_USAGE ;
    44    }
    45  
    46    tv_sec = list->word->word ;
    47    if (tv_sec == 0 || *tv_sec == 0) {
    48      builtin_usage() ;
    49      return EX_USAGE ;
    50    }
    51  
    52    list = list->next ;
    53  
    54    if (list && list->word->word) {
    55      tv_nsec = list->word->word ;
    56    } else {
    57      builtin_usage() ;
    58      return EX_USAGE ;
    59    }
    60  
    61    clock_gettime(CLOCK_MONOTONIC, &t) ;
    62  
    63    tbsize = 32 ;         /* enough to store ULONG_MAX */
    64    tbuf = xmalloc(tbsize) ;
    65    snprintf(tbuf, tbsize, "%lu", t.tv_sec) ;
    66    bind_variable(tv_sec, tbuf, 0) ;
    67    free(tbuf) ; tbuf = NULL ;    /* erase immediately */
    68  
    69    tbsize = 32 ;
    70    tbuf = xmalloc(tbsize) ;
    71    snprintf(tbuf, tbsize, "%lu", t.tv_nsec) ;
    72    bind_variable(tv_nsec, tbuf, 0) ;
    73    free(tbuf) ; tbuf = NULL ;
    74  
    75    return EXECUTION_SUCCESS ;
    76  }
    77  
    78  /* An array of strings forming the `long' documentation for a builtin xxx,
    79     which is printed by `help xxx'.  It must end with a NULL. */
    80  char *monotonic_doc[] = {
    81          "The interface to clock_gettime(CLOCK_MONOTONIC).",
    82          "Store the results in shell variable TV_SEC and TV_NSEC",
    83          (char *)NULL
    84  } ;
    85  
    86  /* The standard structure describing a builtin command.  bash keeps an array
    87     of these structures.  The flags must include BUILTIN_ENABLED so the
    88     builtin can be used. */
    89  struct builtin monotonic_struct = {
    90          "monotonic",            /* builtin name */
    91          monotonic_builtin,      /* function implementing the builtin */
    92          BUILTIN_ENABLED,        /* initial flags for builtin */
    93          monotonic_doc,          /* array of long documentation strings. */
    94          "monotonic TV_SEC TV_NSEC",     /* usage synopsis; becomes short_doc */
    95          0                       /* reserved for internal use */
    96  } ;
これをビルドするため、Makefile を書き換えます。差分は、次の通りです。
# diff -u Makefile.org Makefile
--- Makefile.org        2011-11-06 12:35:25.000000000 +0900
+++ Makefile    2011-11-06 14:00:09.000000000 +0900
@@ -69,7 +69,7 @@
 SHOBJ_CFLAGS = -fPIC
 SHOBJ_LD = ${CC}
 SHOBJ_LDFLAGS = -shared -Wl,-soname,$@
-SHOBJ_XLDFLAGS = 
+SHOBJ_XLDFLAGS = -lrt
 SHOBJ_LIBS = 
 SHOBJ_STATUS = supported
 
@@ -83,7 +83,7 @@
 
 ALLPROG = print truefalse sleep pushd finfo logname basename dirname \
          tty pathchk tee head mkdir rmdir printenv id whoami \
-         uname sync push ln unlink cut realpath getconf strftime
+         uname sync push ln unlink cut realpath getconf strftime monotonic
 OTHERPROG = necho hello cat
 
 all:   $(SHOBJ_STATUS)
@@ -186,6 +186,9 @@
 strftime:      strftime.o
        $(SHOBJ_LD) $(SHOBJ_LDFLAGS) $(SHOBJ_XLDFLAGS) -o $@ strftime.o $(SHOBJ_LIBS)
 
+monotonic:     monotonic.o
+       $(SHOBJ_LD) $(SHOBJ_LDFLAGS) $(SHOBJ_XLDFLAGS) -o $@ monotonic.o $(SHOBJ_LIBS)
+
 # pushd is a special case.  We use the same source that the builtin version
 # uses, with special compilation options.
 #
@@ -236,3 +239,4 @@
 mkdir.o: mkdir.c
 realpath.o: realpath.c
 strftime.o: strftime.c
+monotonic.o: monotonic.c
特に、librt のリンクが必要なので、SHOBJ_XLDFLAGS に -lrt を設定しています。

もう一度 make -k を実行すれば、monotonic が出来上がります。
# make -k
# ls -l monotonic*
-rwxr-xr-x 1 root root 11463 Nov  6 15:29 monotonic
-rw-r--r-- 1 root root  2379 Nov  6 15:29 monotonic.c
-rw-r--r-- 1 root root  9992 Nov  6 15:29 monotonic.o

試しに動かしてみた結果です。
# enable -f ./monotonic monotonic
# enable | grep monotonic
enable monotonic
# help monotonic
monotonic: monotonic TV_SEC TV_NSEC
    The interface to clock_gettime(CLOCK_MONOTONIC).
    Store the results in shell variable TV_SEC and TV_NSEC
# monotonic TV_SEC TV_NSEC
# echo $TV_SEC $TV_NSEC
12115 474565954
# cat /proc/uptime ; monotonic TV_SEC TV_NSEC ; echo $TV_SEC $TV_NSEC
12148.86 11976.46
12148 861371954
どうやら、うまく動きました。
monotonic は、指定されたシェル変数(上では TV_SEC および TV_NSEC ですが、他の名前でも可)に、clock_gettime(CLOCK_MONOTONIC) で得られた値を設定します。

経過時間を計測する場合、得られた値の差を求めれば良いので、実際にやってみます。
#!/bin/bash
#
# Name: elaps_ns.bash
#

enable -f ./monotonic monotonic

set_START() {
        monotonic START START_NSEC
}

get_ELAPS() {
        monotonic END END_NSEC
        if [ $END_NSEC -ge $START_NSEC ] ; then
                let ELAPS=END-START
                let ELAPS_NS=END_NSEC-START_NSEC
        else
                let ELAPS=END-START-1
                let ELAPS_NS=1000000000+END_NSEC-START_NSEC
        fi
}

echo SECONDS=$SECONDS   #bash's builtin
time {
        set_START
}
echo START=$START NSEC=$START_NSEC

echo -e "\n### sleep (2500 x 1000) [us]  using usleep"
time {
        usleep 2500000
}

time {
        get_ELAPS
}
echo END=$END NSEC=$END_NSEC
printf "ELAPS=START-END=$ELAPS.%09d\n" $ELAPS_NS
echo
echo SECONDS=$SECONDS   #bash's builtin
# ./elaps_ns.bash 
SECONDS=0

real    0m0.000s
user    0m0.000s
sys     0m0.000s
START=14665 NSEC=852954954

### sleep (2500 x 1000) [us]  using usleep

real    0m2.503s
user    0m0.000s
sys     0m0.000s

real    0m0.000s
user    0m0.000s
sys     0m0.000s
END=14668 NSEC=356643954
ELAPS=START-END=2.503689000

SECONDS=2
bash 組み込みの time で計った経過時間 2.503 と一致することが確認できます。

利用したシステムコール clock_gettime(CLOCK_MONOTONIC) については、次の記事を参照してください。

経過時間の計測方法

2011年11月5日土曜日

経過時間の計測方法

最近、スクリプト言語の恩恵により、C で書く機会というのは滅多にないのですが、ふと経過時間測定のための bash 用 builtin を書きたいと思い、その基礎として、現在の Linux で利用できるインタフェースについて調べました。

わたしの過去の C プログラミング経験 (某UNIX,10年くらい前) では、経過時間の計測には gettimeofday() を使っていました。たぶんそれしか無かった (?) ので。
しかし、その場合、システム時間が遡ってしまう可能性 (システム管理者が日時設定する時など) を考慮する必要があります。
2011-11-06追記、省みると、その当時,その UNIX でも times() はあったでしょうから、わたしが無知だったということですね。wrap への考慮は必要としても。

調べてみると、現在の Linux であれば、clock_gettime(CLOCK_MONOTONIC,) を使えば良さそうだと知りました。
このシステムコールを使えば、システム起動からの経過時間を高精度で得られるようです。
つまり、(カーネルソースまで調べてないですが、おそらくは) /proc/uptime の左側数字の情報源を高精度で得られます。
これなら、システム時刻が遡るタイミングであっても、経過時間を高精度で計測できます。
よく、times() システムコールの戻り値を、経過時間の計測に使ってバグっている (wrap のタイミングで) のを見かけますが、こちらを使えば、そんなバグも作らずに済みそうです。(Solaris やら HP-UX も対応しようとして、共通に使えるものを探した結果そうなるのでしょうけど … )

以下が、サンプルプログラムです。コンパイル時には、librt をリンクするため、リンクオプション -lrt を付加する必要があります。
/*
    gcc -o monotonic monotonic.c -lrt
*/
#include <time.h>
#include <stdio.h>

main()
{
        struct timespec tp ;
        clock_gettime(CLOCK_MONOTONIC, &tp) ;
        printf("%llu %llu\n", tp.tv_sec, tp.tv_nsec) ;
}
実行結果は次の通りです。
# cat /etc/redhat-release 
CentOS release 5.7 (Final)
# uname -a
Linux xxx 2.6.18-274.3.1.el5 #1 SMP Tue Sep 6 20:13:52 EDT 2011 x86_64 x86_64 x86_64 GNU/Linux
# gcc -o monotonic monotonic.c -lrt
# ./monotonic ; cat /proc/uptime 
10714 102582089
10714.10 10559.35
このように、/proc/uptime の値が高精度で得られている様子が見えます。

2011-11-06追記
実際に、clock_gettime(CLOCK_MONOTONIC) を呼び出す bash ビルトインを自作してみました。次の記事を参照ください。

ビルトインを自作して、bash で処理時間をミリ秒単位で計測

2011-11-12追記
ソースから、/proc/uptime と clock_gettime(CLOCK_MONOTONIC) の情報源が同じであることを確かめました。参照したのは、kernel-2.6.18-274.el5 です。
まず、/proc/uptime は次のようになっています。
     97 static int uptime_read_proc(char *page, char **start, off_t off,
     98                                  int count, int *eof, void *data)
     99 {
    100         struct timespec uptime;
    101         struct timespec idle;
    102         int len;
    103         cputime_t idletime = cputime_add(init_task.utime, init_task.stime);
    104 
*   105         do_posix_clock_monotonic_gettime(&uptime);
    106         cputime_to_timespec(idletime, &idle);
    107         len = sprintf(page,"%lu.%02lu %lu.%02lu\n",
    108                         (unsigned long) uptime.tv_sec,
    109                         (uptime.tv_nsec / (NSEC_PER_SEC / 100)),
    110                         (unsigned long) idle.tv_sec,
    111                         (idle.tv_nsec / (NSEC_PER_SEC / 100)));
    112 
    113         return proc_calc_metrics(page, start, off, count, eof, len);
    114 }
"fs/proc/proc_misc.c"
105行目の do_posix_clock_monotonic_gettime() は、もう名前からして、clock_gettime(CLOCK_MONOTONIC) に対応していそうですが、その通りです。
    114 #define do_posix_clock_monotonic_gettime(ts) ktime_get_ts(ts)
"include/linux/time.h"
    157 /*
    158  * Call the k_clock hook function if non-null, or the default function.
    159  */
*   160 #define CLOCK_DISPATCH(clock, call, arglist) \
    161         ((clock) < 0 ? posix_cpu_##call arglist : \
    162          (posix_clocks[clock].call != NULL \
    163           ? (*posix_clocks[clock].call) arglist : common_##call arglist))
...
    218 /*
    219  * Get monotonic time for posix timers
    220  */
    221 static int posix_ktime_get_ts(clockid_t which_clock, struct timespec *tp)
    222 {
*   223         ktime_get_ts(tp);
    224         return 0;
    225 }
    226 
    227 /*
    228  * Initialize everything, well, just everything in Posix clocks/timers ;)
    229  */
    230 static __init int init_posix_timers(void)
    231 {
    232         struct k_clock clock_realtime = {
    233                 .clock_getres = hrtimer_get_res,
    234         };
    235         struct k_clock clock_monotonic = {
    236                 .clock_getres = hrtimer_get_res,
*   237                 .clock_get = posix_ktime_get_ts,
    238                 .clock_set = do_posix_clock_nosettime,
    239         };
    240 
    241         register_posix_clock(CLOCK_REALTIME, &clock_realtime);
*   242         register_posix_clock(CLOCK_MONOTONIC, &clock_monotonic);
    243 
    244         posix_timers_cache = kmem_cache_create("posix_timers_cache",
    245                                         sizeof (struct k_itimer), 0, 0, NULL, NULL);
    246         idr_init(&posix_timers_id);
    247         return 0;
    248 }
...
    920 asmlinkage long
    921 sys_clock_gettime(const clockid_t which_clock, struct timespec __user *tp)
    922 {
    923         struct timespec kernel_tp;
    924         int error;
    925 
    926         if (invalid_clockid(which_clock))
    927                 return -EINVAL;
*   928         error = CLOCK_DISPATCH(which_clock, clock_get,
    929                                (which_clock, &kernel_tp));
    930         if (!error && copy_to_user(tp, &kernel_tp, sizeof (kernel_tp)))
    931                 error = -EFAULT;
    932 
    933         return error;
    934 
    935 }
"kernel/posix-timers.c"
モノトニック時刻の実装詳細は、ktime_get_ts() の先にありますが、この記事では情報源の確認までにします。jiffies_64 に帰着するのかと思ってましたが、そうでは無いようでした。興味のある方は、先を追ってみては。
以上から、10ミリ秒精度で十分であれば、モノトニック時刻の取得は、/proc/uptime の読み出しで代用できるということが確かめられました。各種スクリプト言語から、簡単に利用できて便利と思います。
なお、主要スクリプト言語について、clock_gettime() を呼び出すインターフェースがあるか調べてみました。以下。

perl
http://perldoc.perl.org/Time/HiRes.html

Ruby
見つけられませんでした。2011-11-13追記、Ruby にも perl と同様な syscall() が用意されていますので、強引に呼び出すことはできました。

Python
http://stackoverflow.com/questions/1205722/how-do-i-get-monotonic-time-durations-in-python
同様にして、ほとんど何でも C ライブラリを呼び出せそうです。すごいな Python は。
perl でも、システムコールであれば、syscall() を使って無理すれば呼べますが、こんなにキレイにはできません。2011-11-13追記、実際動かしてみると、CentOS 5 の python 2.4 では動かず、CentOS 6 の python 2.6 なら動きました。CentOS 5 では ctypes が足りませんでした。
2012-10-14追記、CentOS 5.8 で python-ctypes が追加されていたようです。

Ruby については、システムコール直呼び出しはやったことないので不明です。今度、調べてみよう。


■関連記事
bash で処理時間を 10 ミリ秒単位で計測する方法
ビルトインを自作して、bash で処理時間をミリ秒単位で計測
perlでサブルーチンを動的に定義しようとしてハマりました
人気ブログランキングへ にほんブログ村 IT技術ブログへ