Perlのスタックを使った引数や戻り値の操作
(3)
my ($min, $max, $avg) = get_score_info(160, 230, 120, 210, 300);
print "min: $min, max: $max, avg: $avg\n";
sub get_score_info {
my @args = @_; ―(1)
my $n = @args; ―(2)
my $min = 0;
my $max = 0;
my $sum = 0;
for (my $i = 0; $i < $n; $i++) {
$sum += $args[$i]; ―(3)
if ($i == 0) {
$min = $args[$i];
$max = $args[$i];
} else {
if ($min > $args[$i]) {
$min = $args[$i];
}
if ($max < $args[$i]) {
$max = $args[$i];
}
}
}
my $avg = $sum / $n;
return ($min, $max, $avg); ―(4)
}
実行結果は次のとおりです。
min: 120, max: 300, avg: 204
上記のコードで注目すべきポイントとして、
(1) 可変長の引数を受け取ることができる(2) 受け取った引数の数を取得する(3) 引数の値を1つずつ取得して計算を行う(4) 複数値を返す
Perlのサブルーチンは可変長の引数を受け取ることができ、
CでPerlの拡張コードを記述する場合、
可変長の引数を受け取る
Inline::C上では可変長の引数であることを、
それらのマクロを用いて可変長の引数を操作するソースコードは、
use Inline C;
get_score_info(160, 230, 120, 210, 300);
__DATA__
__C__
void get_score_info(SV* num, ...) {
int i, n;
int min, max;
double sum, avg;
Inline_Stack_Vars; ―(1)
n = Inline_Stack_Items; ―(2)
for (i = 0; i < n; i++) {
SV* sv = Inline_Stack_Item(i); ―(3)
int tmp = (int)SvIV(sv); ―(4)
sum += (double)tmp;
if (i == 0) {
min = tmp;
max = tmp;
} else {
if (min > tmp) {
min = tmp;
}
if (max < tmp) {
max = tmp;
}
}
}
avg = sum / (double)n;
printf("min: %d, max: %d, avg: %.0f\n", min, max, avg);
}
ポイントを見ていきましょう。
(1) CでPerlのサブルーチンと同様に可変長の引数を受け取るためにスタックを操作する必要があり、このマクロではその準備を行う。ほかの Inline_
関連のマクロよりも必ず先に宣言しなければならないStack (2) 可変長の引数の数を返すマクロ。ここでは引数を5つ渡しているので5が返ってくる(3) $_[i]
に相当するマクロ。このマクロを用いることで引数へ渡した値に該当するインデックスを指定して値を取り出す(4) (3) で取り出したSV*からIVを取り出し、intへキャストしてtmpへ代入する
最後にmin
、max
、avg
の値をprintf
関数で出力できます。
複数の値を返す
先ほどの関数をベースに、
CはPerlと違い、Inline_
関連のマクロを使用し、
use Inline C;
my ($min, $max, $avg) = get_score_info(160, 230, 120, 210, 300);
print "min: $min, max: $max, avg: $avg\n";
__DATA__
__C__
void get_score_info(SV* num, ...) {
int i, n;
int min, max;
double sum, avg;
Inline_Stack_Vars;
n = Inline_Stack_Items;
for (i = 0; i < n; i++) {
SV* sv = Inline_Stack_Item(i);
int tmp = (int)SvIV(sv);
sum += (double)tmp;
if (i == 0) {
min = tmp;
max = tmp;
} else {
if (min > tmp) {
min = tmp;
}
if (max < tmp) {
max = tmp;
}
}
}
avg = sum / (double)n;
Inline_Stack_Reset; ―(1)
Inline_Stack_Push(newSViv((IV)min)); ―(2)
Inline_Stack_Push(newSViv((IV)max));
Inline_Stack_Push(newSVuv((UV)avg));
Inline_Stack_Done; ―(3)
}
ポイントを見ていきましょう。
(1) 宣言することで引数のために使用していたスタックが、戻り値の操作のために使用可能になる (2) 戻り値のスタックへ値をpush
する(3) 複数戻り値を確定させるとき、つまりこれ以上スタックへ push
を行わないことを示すために宣言する必要がある
このコードを実行すると次の結果が得られます。
min: 120, max: 300, avg: 204
今回定義したPerlのサブルーチンと同じ実行結果を、
CでPerlの処理性能を補う
Cと比べたPerlの数値計算
PerlとInline::Cそれぞれで記述したadd
の処理速度にどれだけ差があるかを計測するために、
計測に用いたPerlのバージョンは5.
use Inline C;
use Benchmark 'cmpthese';
my $n = 1;
cmpthese 0, {
'PP' => sub {
pp_add($n, $n);
},
'Inline::C' => sub {
c_add($n, $n);
},
};
sub pp_add {
return $_[0] + $_[1];
}
__DATA__
__C__
int c_add(int x, int y) {
return x + y;
}
計測するために、cmpthese
を使います。cmpthese
を使うと計測の結果がチャート形式で出力されます。
実行結果は次のとおりになりました。
Rate PP Inline::C
PP 5434403/s -- -63%
Inline::C 14530606/s 167% --
Rateの列は実行速度を表しています。値が大きくなればなるほど1秒で実行できる回数が多くなります。PP
Cと比べたPerlの文字列連結
では、
use Inline C;
use Benchmark 'cmpthese';
my $a = "Hello";
my $b = ", World";
cmpthese 0, {
'PP' => sub {
pp_cat($a, $b);
},
'Inline::C' => sub {
c_cat($a, $b);
},
};
sub pp_cat {
return $_[0] . $_[1];
}
__DATA__
__C__
char* c_cat(char* x, char* y) {
/* 誌面の都合上エラー処理を省略します */
char* tmp;
size_t x_len, y_len;
x_len = strlen(x);
y_len = strlen(y);
tmp = (char*)malloc(x_len + y_len); ―(1)
memcpy(tmp, x, x_len); ―(2)
memcpy(tmp + x_len, y, y_len); ―(3)
return tmp;
}
記述したCのコードは次の挙動をします。
(1) malloc
関数で文字列連結した結果を保存するために、メモリ領域を確保する (2) 確保した領域へ変数x
に格納された文字列をセットする(3) セットした文字列の最後から続けて、変数 y
に格納された文字列をセットする
Cでは文字列連結をするためにメモリを操作するコードを記述する必要があります。Perlの場合は内部で複雑なメモリ操作を代わりに行ってくれるので、
計測結果は次のとおりになりました。
Rate Inline::C PP
PP 5596158/s 9% --
Inline::C 5134979/s -- -8%
ほとんど差はありませんが、
Perlはたった1行のコードでCとほぼ同じ速度で処理ができました。Perlが古くから文字列を処理するための言語だと言われ続けたことに納得できる結果です。
Cで書くべき勘どころ
これらの結果から、
PerlとCを比較すると、
簡単にホットスポットを改善したい場合、
Rate PP Inline::C
PP 335081/s -- -92%
Inline::C 4186717/s 1149% --
XSで書かれたPerlモジュール
CPANをより使いやすくしたWebサービスであるMetaCPANでは、
Time::Moment──XSによる高速化
Time::Momentは、now()
は同様の機能を持つ組込み関数のlocaltime()
より高速に呼び出せます。
Encode──Perlが苦手とするバイト操作のサポート
Encodeは、
JSON::XS──Perlの型へ高速変換
JSON::XSは、
autobox──PerlのAPIを使った構文操作
autoboxは、
参考ドキュメント
もっと深掘りしたい方のために、
perlguts──Perl APIへの入り口
perlgutsは、
perlxstut──XSのチュートリアル
perlxstutは、
perlclib──PerlでC標準ライブラリをどう扱うか
perlclibは、
illguts──Perl内部の説明書
illgutsは、
CによるPerl拡張入門(α)──日本語によるXSの解説サイト
「CによるPerl拡張入門(α)」
まとめ
今回は、
さて、
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT