xargs

オリンピックの為だけに、来年とさ来年だけ、日本国中の時計を2時間進めましょって法案を 作るとか言ってる。発案者は、某年寄り。年寄りは早起きなんで体に堪える事はなかろうけど、 付き合わされる人は大迷惑ですよ。

熱い時間に競技をやるんじゃ、きっと死人が出るから、その予防策なんだろ。だったら、競技時間だけを早めればいいんでないかいと思う。けど、それじゃ交通機関も動いていないので、勤労奉仕の人達と観客を配置につかせるのが難しいかららしい。

それで、全国一斉に時計をずらしちゃえって事みたいだ。これなら余計な金を使わんで済む。知恵者は何処にもいるね。

夏時間と言うと、慣れ親しんだJSTからJDTに変わるのかな?

そんなのzoneinfoに書いてあるでしょ。

# in May, until 0:00 on the day after the second Saturday in September.
# Rule  NAME    FROM    TO      TYPE    IN      ON      AT      SAVE    LETTER/S
Rule    Japan   1948    only    -       May     Sat>=1  24:00   1:00    D
Rule    Japan   1948    1951    -       Sep     Sun>=9   0:00   0       S
Rule    Japan   1949    only    -       Apr     Sat>=1  24:00   1:00    D
Rule    Japan   1950    1951    -       May     Sat>=1  24:00   1:00    D

# Zone  NAME            GMTOFF  RULES   FORMAT  [UNTIL]
Zone    Asia/Tokyo      9:18:59 -       LMT     1887 Dec 31 15:00u
                        9:00    Japan   J%sT

決まったら、これを元に修正して、zicでコンパイルだな。そして、 adjkerntzを忘れずに走らせておけ。(気の早い輩だ事)

ああ、オイラーの場合、真夜中もパソコンを動かしている訳じゃないんで、日本時間製造所にntpdateすれば済む話か。

きっと、サマータイム詐欺とかウィルスが沸いて来るだろうから、厳重注意。

もし実現されたら、東京大変、地方とIT従事者大迷惑だな。ああ、IT経営者はサマータイム特需でウハウハかな。

tcsh time

以前書いたshellでのファイル似てる度とgaucheのそれとかの(実行)スピード比べをやってみようと思ったんだ。(下記に出て来るaaは、copipeP.shの画面出力を省略したもの)

sakae@fb:~ % time sh aa li.c li.c
0.000u 0.033s 0:00.02 150.0%    117+182k 0+0io 0pf+0w

見慣れない形式の報告が有ったぞ。使ってるのがtcshだから?

sakae@fb:~ % sh
$ time sh aa li.c li.c
        0.03 real         0.03 user         0.00 sys

使うshellが違うと、大違いな面が有るのね。それはそうと、tcshの時の表示 150% って何よ?この際だから調べてみる。timeはtcshの内部コマンドだな。tcsh(1)

       time    If set to a number, then the time builtin (q.v.) executes
               automatically after each command which takes more than that
               many CPU seconds.  If there is a second word, it is used as a
               format string for the output of the time builtin.  (u) The
               following sequences may be used in the format string:

               %U  The time the process spent in user mode in cpu seconds.
               %S  The time the process spent in kernel mode in cpu seconds.
               %E  The elapsed (wall clock) time in seconds.
               %P  The CPU percentage computed as (%U + %S) / %E.
               %W  Number of times the process was swapped.
               %X  The average amount in (shared) text space used in Kbytes.
               %D  The average amount in (unshared) data/stack space used in
                   Kbytes.
               %K  The total space used (%X + %D) in Kbytes.
               %M  The maximum memory the process had in use at any time in
                   Kbytes.
               %F  The number of major page faults (page needed to be brought
                   from disk).
               %R  The number of minor page faults.
               %I  The number of input operations.
               %O  The number of output operations.
               %r  The number of socket messages received.
               %s  The number of socket messages sent.
               %k  The number of signals received.
               %w  The number of voluntary context switches (waits).
               %c  The number of involuntary context switches.

               Only the first four sequences are supported on systems without
               BSD resource limit functions.  The default time format is `%Uu
               %Ss %E %P %X+%Dk %I+%Oio %Fpf+%Ww' for systems that support
               resource usage reporting and `%Uu %Ss %E %P' for systems that
               do not.

(ユーザー時間 + システム時間)/ 実時間 ってのを表しているとな。CPUが複数個動いた場合、ユーザー・システム時間は、全CPUのそれを合算したものになる。と言う事は、複数の石が協力したという事かな。

xargs の使い方

数値が非常に小さい領域での事なんで、外乱で大きく変動する事があるな。こういう時は、何回も実行して、その合計で判断すれば良い。

そんな場合は、スクリプトを書いて、、、かな。いや、そんなの一行野郎じゃろう。思いつくのは、xargsかな。(某君に入れ知恵してもらいました)

$ while :
do
  echo aa li.c li.c
done | head -5 | xargs -n 3 sh

実験例。whileにコロンを与えるとtrueと等価。echoの所に、実行したいコマンドを置く。味噌はxargsのnオプション。3個の単語が流れてきたら(コマンド列が完成するんで)shを呼び出しなさい。 これで、5回コマンドが実行される。

今回は、引数の個数設定に -n を使ったけど、場合によっては、行でってのも有りだろう。-L で、行数を指定出来る。(一行に一引数分のデータがある場合は nオプションもLオプションも同義になる)

今回の場合は、実行したいコマンド列をechoで指定してるんで、-L 1 の方が、汎用性(引数の個数が変わっても変更不要)が有っていいかも。

次は、それを使って何回か実行した時の、総計実行時間を出したい。なら、頭にtimeを付ければ いいじゃんと思うけど、やってみると文法エラーになる。涙を呑んで、下記のような 一行野郎ファイルをでっちあげる。

$ cat z
while :; do echo aa li.c li.c  ; done | head -100 | xargs -n 3 sh

それを、timeに食わせる。上記一行ファイルでは、100回 shellが起動(実行)される事になる。

sakae@fb:~ % time sh z
0.790u 2.264s 0:02.64 115.5%    77+161k 0+0io 0pf+0w
sakae@fb:~ % time sh z
0.860u 2.186s 0:02.76 110.1%    78+166k 0+0io 0pf+0w
sakae@fb:~ % time sh z
0.812u 2.206s 0:02.63 114.4%    77+169k 0+0io 0pf+0w

これをみてると、100%を越えているのは疑いの無い事実。でも、おしいかな、石が2個で動いているんで、200%ぐらいまでは行って欲しいぞ。そういう場合はどうするか?

xaggsには、素敵な-Pオプションなんてのが装備されてて、指定しか数だけプロセスを同時実行出来る。石が2個なんで、それぞれにプロセスが割り当てられる事を期待して、-P 2 を追加。

sakae@fb:~ % time sh z
0.911u 1.980s 0:01.46 197.9%    75+169k 0+0io 0pf+0w
sakae@fb:~ % time sh z
0.903u 2.065s 0:01.50 197.3%    76+170k 0+0io 0pf+0w
sakae@fb:~ % time sh z
0.902u 2.033s 0:01.49 196.6%    85+171k 0+0io 0pf+0w

やったね。そんじゃ、goshのそれでやってみる。(b.scmは、やはり結果を出力しない版)

sakae@fb:~ % cat y
while :; do echo b.scm li.c li.c  ; done | head -100 | xargs -n 3 -P 2 gosh
sakae@fb:~ % time sh y
10.452u 1.001s 0:05.73 199.8%   30+5821k 0+0io 0pf+0w
sakae@fb:~ % time sh y
10.325u 1.148s 0:05.75 199.3%   30+5826k 0+0io 0pf+0w
sakae@fb:~ % time sh y
10.173u 1.072s 0:05.68 197.8%   30+5799k 0+0io 0pf+0w

石の能力を存分に使用してる。ついでにgosh独自のメモリーエリアを大量に消費してるね。 まあ、内部に仮想CPUを抱え込んで、そのエリアを確保してるからなあ。

次は、上記のb.scmをコンパイルしたアプリでやってみる。 引数が2個揃えば、実行アプリbを起動出来るんで、-n 2

sakae@fb:~ % cat x
while :; do echo li.c li.c  ; done | head -100 | xargs -n 2 -P 2 ./b
sakae@fb:~ % time sh x
8.607u 0.606s 0:04.61 199.5%    5966+12234k 0+0io 0pf+0w
sakae@fb:~ % time sh x
8.833u 0.638s 0:04.74 199.5%    5957+12246k 0+0io 0pf+0w
sakae@fb:~ % time sh x
8.833u 0.558s 0:04.70 199.5%    5968+12257k 0+0io 0pf+0w

今度は共有メモリエリアもバーンと増えましたねぇ。そして非共用のエリアもインタープリタ時代に比べて更に増えています。

余りtcshなんてのを使っているとリナ陣営から受けが宜しくないと思うので、ウブでやってみる。

sakae@usvr:/tmp$ time sh x

real    0m2.750s
user    0m4.663s
sys     0m0.745s
sakae@usvr:/tmp$ /usr/bin/time sh x
4.66user 0.77system 0:02.74elapsed 198%CPU (0avgtext+0avgdata 18712maxresident)k
0inputs+0outputs (0major+269630minor)pagefaults 0swaps

ははは、bash備え付けのtimeじゃなくて、システム備え付けのtimeを使うと、それなりにtcshっぽく表示してくれるのね。

sakae@usvr:/tmp$ /usr/bin/time -f "\t%E real,\t%U user,\t%S sys" sh x
        0:02.81 real,   4.81 user,      0.74 sys

time(1)から拾ってきた例。timeコマンドって、時間表示だけじゃなく、結構色々なデータを収集できるのね。知らんかったわい。

参考までに、OpenBSDでも走らせてみた。

ob6$ time sh z
    0m04.00s real     0m00.16s user     0m06.76s system
ob6$ time sh y
    0m05.78s real     0m04.20s user     0m07.05s system
ob6$ time sh x
    0m04.16s real     0m04.23s user     0m03.62s system

それぞれに個性が有って、面白い。

それはそうと、このxargsってコマンドが何となく、schemeのapplyに似てると思うのは、オイラーだけ? それとパイプの使い方も関数型言語の使い方を継承してると思う。

yes | head -100 | xargs -n 4 hoge

yesが信号源と言うかデータの発生器、headが超簡単なフィルター、xargsがapplyに相当して、hogeが関数。パラメータはパイプからやって来る。

 -- Function: apply proc arg1 ... args
     [R7RS base] Calls a procedure PROC with a list of arguments,
     `(ARG1 ... . ARGS)'.  The last argument ARGS must be a proper list.
     Returns (a) value(s) PROC returns.
          (apply list 'a 'b '(c d e)) => (a b c d e)

          (apply + 1 2 '(3 4 5))      => 15

xargsの中身拝見

例によって、xargsの中身を紐解いていきます。OpenBSD上です。

xargsに食わせる(普通はパイプから流れてくる)データを簡単に作ります。シーケンス番号でいいでしょう。リナとかだと、seqなんてコマンドが用意されてるようですが、OpenBSDには有りません。だめじゃん!

そんな時は、man -k seq して、それらしいのを探します。jotという、4.2BSD時代からのコマンドが見つかりました。なんとなくAPLにも同名のやつが有ったような。。

ob6$ jot 10 >IN.txt
ob6$ cat IN.txt
1
:
10

一応、試験しておきます。xargsにコマンドを与えないと、デフォルトでechoが使われると言う、考え抜かれた挙動になっています。

ob6$ ./a.out -n 4 < IN.txt
1 2 3 4
5 6 7 8
9 10

pオプションを与えると、実行するかどうか、聞いてきますね。ちゃんとデフォのechoも隠す事なく、表示されてました。

ob6$ ./a.out -n 4 -p < IN.txt
/bin/echo 1 2 3 4?...y
1 2 3 4
/bin/echo 5 6 7 8?...n
/bin/echo 9 10?...n

このデータを下記のようにgdbに喰わせる。

(gdb) b main
Breakpoint 1 at 0x7f9: file xargs.c, line 84.
(gdb) r -n 4 < IN.txt
Starting program: /tmp/xargs/a.out -n 4 < IN.txt

ステップ実行していくと

        /*
         * POSIX.2 limits the exec line length to ARG_MAX - 2K.  Running that
         * caused some E2BIG errors, so it was changed to ARG_MAX - 4K.  Given
         * that the smallest argument is 2 bytes in length, this means that
         * the number of arguments is limited to:
         *
         *       (ARG_MAX - 4K - LENGTH(utility + arguments)) / 2.
         *
         * We arbitrarily limit the number of arguments to 5000.  This is
         * allowed by POSIX.2 as long as the resulting minimum exec line is
         * at least LINE_MAX.  Realloc'ing as necessary is possible, but
         * probably not worthwhile.
         */
        nargs = 5000;
        if ((arg_max = sysconf(_SC_ARG_MAX)) == -1)
                errx(1, "sysconf(_SC_ARG_MAX) failed");

こんなのが出て来た。コマンドに与える引数は配列に確保される。配列の入るエリアサイズは、一応決まってるけど、問題が有ったんで、少な目に設定してるとな。

昔々、シス管男子してる時、news(って言っても、若い人は知らないだろうね。-- 掲示板みたいなシステム)に溜まった大量の記事を削除しようとして、rm * したら、引数が多すぎますとお叱りを受けた。これに引っかかっていたんだな。

(gdb) p arg_max
$1 = 262144

こんな値になってた。大雑把に見積もって、一引数あたり50文字ぐらいとして、楽々と5000個(デフォルト値)の引数を処理出来ますとな。

/usr/src/lib/libc/gen/sysconf.c

 61│ sysconf(int name)
 62│ {
 63│         struct rlimit rl;
 64│         size_t len;
 65│         int mib[3], value, namelen, sverrno;
 66│
 67├───────> len = sizeof(value);
 68│         namelen = 2;
 69│
 70│         switch (name) {
 71│ /* 1003.1 */
 72│         case _SC_ARG_MAX:
 73│                 mib[0] = CTL_KERN;
 74│                 mib[1] = KERN_ARGMAX;
 75│                 break;

こういう大事なデータは、カスタマイズ出来るはずと思って、sysconfに潜ってみた。そしたら、上のコードが見えたので、勘を働かせて、

ob6$ sysctl -a | grep arg
kern.argmax=262144

これ、勝手にいじっちゃまずいだろうね。

道草しちゃったな。流れをざっと追うと、mainの最後の方に、永久ループに包まれた、parse_inputってのが出て来る。この中で、xargsの入力(通常はパイプからやって来る。)をスキャンし、コマンド行が完成すると、prerunが呼ばれる。

このprerunの中で、組み立てられたコマンドが実行(vfork and execve)される。以後、入力が尽きるまで同じ事を繰り返し。EOFになったら、最後に実行したコマンドの終了を待ち合わせて、完了したら、xargsを終了。

複数プロセスの同時実行制御は、waitchildren()の中で行われている。

static void
waitchildren(const char *name, int waitall)
{
        pid_t pid;
        int status;

        while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ?
            WNOHANG : 0)) > 0) {
                curprocs--;
          :

waitpidの第三引数が3項演算子になってて分かりずらいけど、今動作中のプロセス数が最大数に達しているかどうかで、WNOHANGか0の値かが決まる。

比較師の前にあるwaitallは通常0が渡って来るんで、それが反転されてtrueになる。よって、比較結果でtrueかfalseが決まる。

xargsとしての最後の呼び出し時には、waitallが1として渡ってくるので、その反転されてfalseになるんで、次の比較を行うまでもなく、論理式全体の結果はfalseとなる。(短絡演算と言うlisp由来の技法が使われている)

wait(2)族に属すwaitpidを見ると

     WNOHANG     Indicates that the call should not block if there are no
                 processes that wish to report status.

待たないで次行くよってお願いだ。待ってて、子供が終了すると、そのpidが得られるので、whileの評価はtrueとなる。まあ、色々な事を詰め込んだものだな。

ob6$ cat ck.sh
#!/bin/sh
echo 'No=' $1 ' pid=' $$
sleep 10

待ちの状況確認のため、上記を用意。

ob6$ ./a.out -t -n 1 -P 3 ./ck.sh < IN5.txt
./ck.sh 1
./ck.sh 2
./ck.sh 3
No= 1  pid= 51888
No= 2  pid= 29458
No= 3  pid= 78252
   ----------------< この間で10秒待たされる
./ck.sh 4
./ck.sh 5
No= 4  pid= 61151
No= 5  pid= 13120

一気に3個のプロセスを起動。それが終了するまで、次を実行出来ない。なお、 -t は、実行されるコマンド内容を表示するオプションだ。

pidがインクリメントされないでランダムになるのは、悪い人への防衛策です。細かい所まで気が回るな。> OpenBSD

power of xargs

xargsの威力は何か? 上で試したようにCPUをフルにこき使う為に使う(使える)。でも、こういう場面って意外に少ないと思うぞ。

それより常日頃お世話になってる使い方で、有難みを実感しよう。 よくあるのは、ソースツリーからの検索。まずはOpenBSDのカーネルソースの個数。

ob6$ find /usr/src/sys -name '*.[ch]' | wc
    5522    5522  202162

これでも、他のOSに比べて少ないんだろうね(NetBSDだと2万個以上ある)。で、この群から上で出て来た数値の設定元を探してみる。今回は結果が欲しいんじゃないんで、棄ててるけど。

ob6$ time find /usr/src/sys -name '*.[ch]' | xargs grep 262144 >/dev/null
    0m00.46s real     0m00.06s user     0m00.41s system

0.5秒で検索完了。それじゃ、対象になったファイルを1個づつgrepするように、改悪してみる。

ob6$ time find /usr/src/sys -name '*.[ch]' | xargs -n 1 grep 262144 >/dev/null
    0m15.47s real     0m00.07s user     0m13.83s system

歴然とした差が出て来た。30倍も遅い。いかにアプリ(grep)の起動に時間がかかるか、身を持って感じられたね。

上記には、多少の嘘がまじっている。初回の実行時は、下記のようになった。

ob6$ time find /usr/src/sys -name '*.[ch]' | xargs grep 262144 >/dev/null
    0m04.77s real     0m00.02s user     0m03.26s system

この4秒余り余計に時間がかかるのは、SSDからメモリエリアにデータを読み込むからだ。2回目からは、メモリー上にあるデータで(多分)賄えるので、スピードが上がる。

Memory: Real: 43M/93M act/tot Free: 1879M Cache: 19M Swap: 0K/0K
Memory: Real: 45M/342M act/tot Free: 1630M Cache: 166M Swap: 0K/0K

上のデータは、topした時に出て来るメモリー関係の表示。上段は、起動直後。下段はfindを実行した後の結果。キャッシュが19Mから166Mと、約140M程増加している。これがSSDからメモリーに転送されたソースファイルって事だ。

xargs on FreeBSD -P 0

FreeBSDのxargsには、起動出来るプロセス数として、

     -P maxprocs
             Parallel mode: run at most maxprocs invocations of utility at
             once.  If maxprocs is set to 0, xargs will run as many processes
             as possible.

0を指定すると可能な限り沢山だよと書いてある。子供の使いみたいな物言だな。一体幾つまでいいの? ソースに当たってみる。

                case 'P':
                        maxprocs = strtonum(optarg, 0, INT_MAX, &errstr);
                        if (errstr)
                                errx(1, "-P %s: %s", optarg, errstr);
                        if (getrlimit(RLIMIT_NPROC, &rl) != 0)
                                errx(1, "getrlimit failed");
                        if (maxprocs == 0 || maxprocs > rl.rlim_cur)
                                maxprocs = rl.rlim_cur;
                        break;

リソース制限数までOKって事だな。現在のリソース使用許可状況。

sakae@fb:~ % ulimit -a
cpu time               (seconds, -t)  unlimited
file size           (512-blocks, -f)  unlimited
data seg size           (kbytes, -d)  33554432
stack size              (kbytes, -s)  524288
core file size      (512-blocks, -c)  unlimited
max memory size         (kbytes, -m)  unlimited
locked memory           (kbytes, -l)  64
max user processes              (-u)  6656
open files                      (-n)  57987
virtual mem size        (kbytes, -v)  unlimited
swap limit              (kbytes, -w)  unlimited
socket buffer size       (bytes, -b)  unlimited
pseudo-terminals                (-p)  unlimited
kqueues                         (-k)  unlimited
umtx shared locks               (-o)  unlimited

制限が緩いなあ。きつくしてみるか。

sakae@fb:~ % limit maxproc 50
sakae@fb:~ % ulimit -u
50

limit -hとすると、リソース名が表示されるんで、それをリミットに設定。そして、実験用には、先に作ったものを使った。

走れよメロスじゃなかった。そう言えば、24Hrだか走って、お涙頂戴、寄付お願いの恒例番組の季節到来するなあ。

sakae@fb:/tmp % seq 100 | xargs -n 1 -P 0 sh ck.sh
No= 1  pid= 9168
No= 2  pid= 9169
 :
No= 20  pid= 9204
No= 23  pid= 9208
No= 21  pid= 9205
ck.sh: Cannot fork: Resource temporarily unavailable
ck.sh: Cannot fork: Resource temporarily unavailable
xargs: vfork: Resource temporarily unavailable
ck.sh: Cannot fork: Resource temporarily unavailable
ck.sh: Cannot fork: Resource temporarily unavailable

既にオイラー名義のプロセスが活躍中なんで、それを差し引いた所で、リミッターがかかった。

jot

FreeBSDにはリナに忖度してseqが用意されてる。けど、本命はjotだ。機能豊富。

sakae@fb:/tmp % jot -r 100 0 10000 | rs 3 5
5182  8052  517   7530  8723
2917  7943  6573  3836  6668
5581  1065  4623  4580  98

こんな事が簡単に出来る。0から10000の間の乱数を100個生成。それをパイプに流して、3行5列に整形しなさい。 pythonのnumpy当たりに実装されてる機能が、パイレスでも実現出来てる。

sakae@fb:/tmp % jot -w QR%c 26 A
QRA
QRB
 :
QRZ

簡易Q符号発生器。

sakae@fb:~ % jot -b 'hoge fuga' 3
hoge fuga
hoge fuga
hoge fuga

こんな事も出来るので、上で出て来た

while :; do echo li.c li.c  ; done | head -100 | xargs -n 2 -P 2 ./b

を、すっきりと、下記のように書き換えられる。

jot -b 'li.c li.c' 100 | xargs -n 2 -P 2 ./b

無線屋さん風に解釈すれば、jotの部分が信号発生器、xargsがドライバー部分、終段はbの2パラ送信機だな。電源(この場合はパソコン)に余裕があれば、幾らでもパラレル数を増やせるぞ。

でも、この糞熱い時期、807なんて球の4パラ終段なんて、地獄そのものだわな。と、時代がかった事を申しております。現代は、ソリッドステートの時代で、暑苦しさは払拭されてるのさ。

こんな便利なものがリナには無いと嘆くなかれ。 athena-jot を入れれば済む話さ。よかったね。

sakae@usvr:~$ ls -l /usr/bin/*jot
-rwxr-xr-x 1 root root 22536 Nov  7  2017 /usr/bin/athena-jot*
lrwxrwxrwx 1 root root    10 Nov  7  2017 /usr/bin/jot -> athena-jot*

jotは、かりそめな名前で、本名は出目を冠した、athena-jotなんで、そこんところをご理解の上、酷使してやってください。

Project Athenaって、jotだけじゃなくて、もっと大物のX11とかも生み出している。それにたてついて、新しいのを生み出そうとした、うぶとかいうのもあるな。結局皆さまの支持を得られなくて、尻すぼみになっちゃったけど。