gawk で並列処理を行う

gawk には fork() 関数が用意されているのはご存知ですか? Perl の fork() 関数に似ており、複数の CPU コアを搭載するマシンで多重ループを回すような時には並列処理することができます。

まず、普通の awk スクリプトで多重ループを書いてみます。

#! /usr/local/bin/gawk -f
# fork_1.awk

BEGIN {
    max_i = 10;
    max_j = 1000000;

    for (i = 1; i <= max_i; i++) {
        for (j = 1; j <= max_j; j++) {
            print i * j;
        }
    }
}

つまり 10 x 1,000,000 回のループを回します。

$ gawk -f fork_1.awk > /dev/null
gawk -f fork_1.awk > /dev/null  5.72s user 0.02s system 99% cpu 5.747 total

5 ~ 6 秒程度かかります。

これを fork() 関数を用いて分散処理を行ってみます。

#! /usr/local/bin/gawk -f
# fork_2.awk

@load "fork";

BEGIN {
    max_i = 10;
    max_j = 1000000;

    for (i = 1; i <= max_i; i++) {
        pid = fork();

        if (pid == 0) {
            for (j = 1; j <= max_j; j++) {
                print i * j;
            }

            exit 0;
        }
    }

    wait();
}

"fork.so" を @load で呼び出すことで fork() 関数を用いることができるようになります。 fork() 関数の戻り値が 0 の場合には「子プロセス」であり、それ以外は「親プロセス」になります。 注意する点は、子プロセスで "exit 0" をしないと指定個数以上の子プロセスが発生してしまいます。

では実行してみましょう。

$ gawk -f fork_2.awk > /dev/null
gawk -f fork_2.awk > /dev/null  0.77s user 0.01s system 98% cpu 0.793 total

1/10 まではいきませんが、0.7 ~ 0.8 秒程度となり大幅な短縮を行うことができました。

現時点の gawk の fork() 関数を用いた仕組みでは親プロセスで子プロセスの出力を直接受け取ることができません。 そのため、親プロセスで受け取る場合には子プロセスでファイルに出力してやって、親プロセスでファイルから拾う必要があります。

最新の gawk は fork() が使えて Socket 通信が使えるので、まともな httpd や Proxy が作れると喜んでいたのですが、POST の処理がうまく行えないという問題があって、Gawk (maynot) bug: Two-way IO getline stuff でも議論されているように、良い方法は現時点ではないようです。

tag_gawk.pngtag_gawk.png