Perl Hackers Hub

第68回 他言語のライブラリをPerlに移植する(2)

この記事を読むのに必要な時間:およそ 3.5 分

前回の(1)こちらから。

移植時に活用すべき機能

移植方針の目処が立ったら,いよいよ実装です。Perlの言語機能やライブラリをフル活用することで,Perlらしさと実行速度を両立したコードを書けます。

(2)では,ライブラリを移植するにあたって活用すべきPerlの機能を解説します。

正規表現

Perlでは,文字列に対してメソッドを呼び出すのではなく,文字列を操作する関数を呼び出したり正規表現を使ったりして文字列を操作します。移植元の言語で文字列オブジェクトのメソッドを呼び出して文字列操作をしているときは,移植元の言語のドキュメントを読んで,どのような操作が行われているのかを把握しましょう。

たとえば,文字列の先頭から1文字ずつ読んで何か処理を行いたいとします。Rubyでは,Stringクラスのeach_charインスタンスメソッドを呼び出すことで実現できます。

text.each_char do |ch|
    # chを使って処理する
end

Perlでは,同様の処理を次のような正規表現マッチを使って実現します。

for my $ch ($text =~ /./g) {
    # $chを使って処理する
}

gフラグを付けた正規表現マッチは,リストコンテキストでは正規表現にマッチする部分文字列の配列を返します。したがって,正規表現マッチと配列を操作する関数を組み合わせることで,複雑な文字列操作を実現できます。

my @ch = 'abcd' =~ /./g;
# => ('a', 'b', 'c', 'd')

配列を処理する関数・ライブラリ

ある配列の各要素を加工した配列を作ったり,条件に応じて要素を取り除いた配列を作ったりしたいとします。Rubyなど多くのプログラミング言語では,配列を表すクラスのインスタンスメソッドを呼んで配列操作を行います。

[1, 2, 3].map { |x| x * 2 } # => [2, 4, 6]

Perlの配列は,Rubyなどの言語のようにインスタンスメソッドを呼び出すことができません。Perlで同様の配列操作を実現するには,forループで要素をpushしていく方法と,mapgrepといった配列を操作する組込み関数を使う方法が考えられます。

単純な処理であれば,後者のほうが処理速度の面で有利です。配列の各要素を2倍にする関数twiceを,forループで各要素を2倍にしてpushする方法と,mapを使う方法とで実装した場合の速度を比較してみましょう。以下に,ベンチマークスクリプトtwice.plと筆者の環境(MacBook Air 2020,1.2GHz Intel Core i7,16GBRAM)でのベンチマーク結果を示します。

twice.pl
use Benchmark qw(:all);

sub twice_map {
    return map { $_ * 2 } @_;
}

sub twice_push {
    my @ret;
    push @ret, $_ * 2 for @_;
    return @ret;
}

my $count = 100000;
my @input = (1..10000);

cmpthese($count, {
    twice_map => sub {
        twice_map(@input);
    },
    twice_push => sub {
        twice_push(@input);
    },
});
% perl twice.pl
             Rate twice_push  twice_map
twice_push 1371/s         --       -48%
twice_map  2630/s         92%        --

mapを使うほうが,forループでpushしていくよりも2倍近く高速に処理できていることがわかります。

mapgrepといった組込み関数よりも高度な処理を行いたい場合は,List::UtilList::MoreUtilsList::UtilsByといった配列操作のためのライブラリを活用してください。ライブラリの実装をシンプルに保ちつつ配列を加工できます。

著者プロフィール

うたがわきき

1996年生まれ,福岡出身。株式会社はてな所属。はてなブログMediaの開発に携わる。

お酒と旅行が好き。

Twitter:utgwkk

URL:https://utgw.net/