理系学生日記

おまえはいつまで学生気分なのか

Perl における system 関数の振舞い

ほとんどの言語には system という関数が用意されていて,これはプログラムから外部プログラムを呼び出すことができます.例えば C 言語では,system に渡された引数が /bin/sh 経由で実行されたりします(ref: http://www.linux.or.jp/JM/html/LDP_man-pages/man3/system.3.html).

Perl にももちろん system が存在しますが,必ずしもシェル経由で外部プログラムを実行するわけではないようです.

use strict;
use warnings;
use Benchmark qw(cmpthese);

cmpthese(
    -10 => {
        "scalar w/  meta" => sub { system "/bin/echo *hello"    },
        "scalar w/o meta" => sub { system "/bin/echo  hello"    },
        "list   w/  meta" => sub { system "/bin/echo", "hello"  },
        "list   w/o meta" => sub { system "/bin/echo", "*hello" },
    }
);

メタキャラクタの有無で外部プログラムの呼び出し方法が変わる(メタキャラクタがあったときはシェル経由,なかったときは execvp 経由になる)ということで,どの程度パフォーマンスに差があるのか試したかったのです.

If there are no shell metacharacters in the argument, it is split into
words and passed directly to "execvp", which is more efficient.

"echo *hello" の結果が "*hello" となるディレクトリにてベンチマークを取っています.
これが結果.

                 Rate scalar w/  meta scalar w/o meta list   w/  meta list   w/o meta
scalar w/  meta 227/s              --            -57%            -59%            -59%
scalar w/o meta 528/s            132%              --             -4%             -4%
list   w/  meta 548/s            141%              4%              --             -0%
list   w/o meta 549/s            142%              4%              0%              --

スカラコンテキストで呼び出した場合,メタキャラクタを使っていると大きく性能が劣化することが確認できます.これは

  • メタキャラクタの有無を確認
  • ワードへの分割
  • (メタキャラクタ展開のための)シェルの起動

あたりで時間を食っているのだと予想されます.もちろん最も大きいのは 3 番目なんでしょうね."scalar w/o meta" の方はシェルの起動が必要ないため,"scalar w/ meta" に比べて性能が大きく改善されているようです.


一方でPerl の system 関数は,その引数の数によっても振舞いが違ってきます.perldoc -f system ではその正確な振舞いは記述されていませんが,おそらくはプログラム名と引数をそのまま exec に渡しているのではないでしょうか.メタキャラクタの有無は性能にほとんど影響していません.

If there is more than one argument in LIST, or if LIST is an array with more than one
value, starts the program given by the first element of the list with arguments given
by the rest of the list. If there is only one scalar argument, the argument is checked
for shell metacharacters, and if there are any, the entire argument is passed to the
system’s command shell for parsing (this is "/bin/sh −c" on Unix platforms, but varies
on other platforms). If there are no shell metacharacters in the argument, it is split
into words and passed directly to "execvp", which is more efficient.

上記記述を見ればわかるように,リストを引数として渡してもそのリスト内の要素が一つだった場合はスカラーと同じ形での外部呼び出しとなってしまい,パフォーマンスは大きく下がります.これを割けるための方法は perldoc -f exec 等で参照でき,indirect object などと呼ばれています.Recipe 16.2. Running Another Program なんかにも詳しく書いてあります.