Home > Perl > モジュールをuseした際のメモリ使用量(増加量)を調べてみる

モジュールをuseした際のメモリ使用量(増加量)を調べてみる

  • 2006-07-04 (火)
  • Perl
  • このエントリーを含むはてなブックマーク

CPANモジュール: GTop

GTop - Perl interface to libgtop

perl.apache.org にある mod_perl パフォーマンスチューニングのドキュメントにて解説されていた、ある特定のモジュールを use した際のメモリ増加量を調べる方法。GTop っていうCPANモジュールを、こんな感じに使うことでお手軽に調べることができるようです:

gtop.plx

#!/usr/bin/perl
use strict;
use warnings;
use GTop;
my $gtop = GTop->new;
my $before = $gtop->proc_mem($$)->size;
eval $ARGV[0];
die $@ if $@;
my $after = $gtop->proc_mem($$)->size;
my $diff = GTop::size_string($after - $before);
print "$diff : $ARGV[0]\n";

例) CGIモジュールのメモリ増加量は...

$ ./gtop.plx 'use CGI'
704k : use CGI

あら面白い♪って事で、思いつくCPANモジュールを片っ端から調べてみました。

目次

わんさか調べてみた結果

※調査用に用意したスクリプトはこんな感じです。

観点

mod_perl パフォーマンスチューニングで重要な「shared memory を有効に使う」の観点から、普通に use する場合に加えて、startup.pl 内での呼び出し記述 - import 無しの 'use CGI ()' - も一緒に調べてみました。さらには、気になったので、実際にインスタンスを作ってみた場合とか、メインのメソッドを利用してみた場合のメモリ増加量も調べてみた次第。

CGI系

704k : use CGI
704k : use CGI ()
788k : use CGI (); CGI->compile(qw(param header))
788k : use CGI (); CGI->compile(qw(param header)); CGI->new
2.1M : use CGI (); CGI->compile(":all")
2.1M : use CGI qw(-compile :all)
264k : use CGI::Carp
264k : use CGI::Carp ()
264k : use CGI::Cookie
264k : use CGI::Cookie ()
264k : use CGI::Cookie (); CGI::Cookie->new(-name=>"hoge")
264k : use CGI::Lite
132k : use CGI::Lite ()
264k : use base "CGI::Application"
264k : use base "CGI::Application"; __PACKAGE__->new
1.0M : use base "CGI::Application"; __PACKAGE__->new->run

CGI モジュールは例によって shared memory 用の呼び出し方とか色々あるので、主要と思える記述を試してみました。

▲RETURN TO TOP

Template系

940k : use Template
940k : use Template ()
1.6M : use Template (); Template->new
3.1M : use Template (); Template->new->process("x.tmpl",{})
2.9M : use Template (); Template::Config->preload
808k : use HTML::Template
808k : use HTML::Template ()
808k : use HTML::Template (); HTML::Template->new(filename=>"x.tmpl")->output

TTの方はデフォルトのTemplate::Stash:XSを使った場合の結果。 別途、空ファイル tt.tmpl を用意しておく必要あり。

▲RETURN TO TOP

Encode, encoding

428k : use Encode
432k : use Encode ()
584k : use Encode::Guess 1.9M : use Encode::Guess qw(euc-jp)
1.9M : use Encode::Guess qw(euc-jp shiftjis iso-2022-jp)
460k : use encoding "utf8"
476k : use encoding "utf8", Filter=>1

▲RETURN TO TOP

DBI系

1.0M : use DBI
1.0M : use DBI ()
1.7M : use DBI (); DBI->connect($dsn,$id,$pass)
1.7M : use DBI (); DBI->install_driver("mysql")
2.2M : use base "Class::DBI"
2.2M : use base "Class::DBI"; __PACKAGE__->connection($dsn,$id,$pass)

DBD::mysql を使った場合の結果。

▲RETURN TO TOP

日時操作系

1.9M : use DateTime
1.9M : use DateTime ()
2.1M : use DateTime (); DateTime->now( time_zone=>"local" ) 132k : use HTTP::Date
132k : use HTTP::Date ()
132k : use HTTP::Date (); HTTP::Date::time2str()

▲RETURN TO TOP

Class::Accessor

132k : use base "Class::Accessor"
132k : use base "Class::Accessor"; __PACKAGE__->mk_accessors("hoge")
132k : use base "Class::Accessor::Fast"
132k : use base "Class::Accessor::Fast"; __PACKAGE__->mk_accessors("hoge")

▲RETURN TO TOP

LWP系

1.0M : use LWP::UserAgent
1.0M : use LWP::UserAgent ()
1.0M : use LWP::UserAgent (); LWP::UserAgent->new
2.8M : use LWP::UserAgent (); LWP::UserAgent->new->get("http://example.com")
264k : use LWP::Simple
132k : use LWP::Simple ()
1.3M : use LWP::Simple qw($ua)
1.2M : use LWP::Simple (); LWP::Simple::get("http://example.com")

▲RETURN TO TOP

XML系

628k : use XML::Parser
628k : use XML::Parser ()
628k : use XML::Parser (); XML::Parser->new
912k : use XML::Parser (); XML::Parser->new(Style=>"Objects")->parse("<x>fuga</x>")
3.5M : use XML::DOM
3.5M : use XML::DOM ()
3.6M : use XML::DOM (); XML::DOM::Parser->new
2.3M : use XML::LibXML
2.3M : use XML::LibXML ()
2.3M : use XML::LibXML (); XML::LibXML->new->parse_string("<x>fuga</x>")
524k : use XML::Simple
396k : use XML::Simple ()
396k : use XML::Simple (); XML::Simple->new
4.3M : use XML::Simple (); XML::Simple->new->XMLin("<x>fuga</x>")
1.4M : use XML::Simple (); $XML::Simple::PREFERRED_PARSER="XML::Parser"; XML::Simple->new->XMLin("<x>fuga</x>")
264k : use XML::TreePP
264k : use XML::TreePP ()
264k : use XML::TreePP (); XML::TreePP->new
264k : use XML::TreePP (); XML::TreePP->new->parse("<x>hoge</x>")

▲RETURN TO TOP

File操作系

544k : use File::Find
544k : use File::Find ()
132k : use File::Basename
132k : use File::Basename ()
280k : use File::Path
280k : use File::Path ()
132k : use File::Spec
132k : use File::Spec ()
156k : use File::Glob
156k : use File::Glob ()
564k : use FileHandle
564k : use FileHandle ()
564k : use FileHandle (); FileHandle->new
432k : use IO::File
432k : use IO::File ()
432k : use IO::File (); IO::File->new
432k : use IO::File (); IO::File->new->open(">hoge")

▲RETURN TO TOP

Net系

1.8M : use Net::FTP
1.8M : use Net::FTP ()
1.5M : use Net::SMTP
1.5M : use Net::SMTP ()

▲RETURN TO TOP

Apache系

636k : use Apache::Session::File
764k : use Apache::Session::File; tie my %x,"Apache::Session::File",undef
132k : use base "Apache::Singleton"
132k : use Apache ()
396k : use Apache::Registry ()
264k : use Apache::PerlRun ()

▲RETURN TO TOP

Socket通信系

972k : use IO::Socket
132k : use IPC::Open2
132k : use IPC::Open3 292k : use Socket

▲RETURN TO TOP

HTML::Parser

308k : use HTML::Parser
308k : use HTML::Parser ()
308k : use HTML::Parser (); HTML::Parser->new
308k : use HTML::Parser (); HTML::Parser->new->parse("<a>fuga</a>")

▲RETURN TO TOP

JSON

396k : use JSON
396k : use JSON ()
396k : use JSON (); JSON::objToJson({ x=>1 }) 2.3M : use JSON::Syck
2.3M : use JSON::Syck ()
2.3M : use JSON::Syck (); JSON::Syck::Dump({ x=>1 })

▲RETURN TO TOP

*::Util系

132k : use Hash::Util
0k : use List::Util
0k : use Scalar::Util

※0K = GTopにて使用されているモジュールゆえ判定不能

▲RETURN TO TOP

その他

132k : use Attribute::Handlers
132k : use Attribute::Handlers ()
152k : use Digest::MD5
152k : use Digest::MD5 ()
264k : use Dumpvalue
132k : use English
132k : use English ()
148k : use Fcntl
148k : use Fcntl ()
280k : use Fcntl qw(:DEFAULT)
412k : use FindBin
132k : use Getopt::Std
528k : use Getopt::Long
660k : use Math::Trig
660k : use Math::Trig ()
776k : use POSIX
248k : use POSIX ()
352k : use Storable
352k : use Storable ()
132k : use Unicode::RecursiveDowngrade
132k : use Unicode::RecursiveDowngrade; Unicode::RecursiveDowngrade->new->downgrade({})

▲RETURN TO TOP

メモリを有効利用するために

mod_perl の場合

mod_perl では出来るだけ多くのメモリを共有させることで、より効率的なサーバ資源活用が実現できます (※詳しい説明は長くなるので省略。perl.apache.org にある mod_perl ドキュメント > performance tuning > sharing memory を参照のこと)。なので、各種モジュール (特にメモリ使用量の多いモジュール) を mod_perl 下で使用する場合は、なるべく apache 親プロセス起動時に use しておくとGOODです。具体的には httpd.conf と startup.pl を以下のように設定する感じになります:

httpd.conf

PerlRequire startup.pl

startup.pl

use Encode ();
use DateTime ();
...
1;

※余談 - startup.pl って、どう見ても startup.pm なんじゃないかと思うのは俺だけ?%::INCにも追加されるし。

関連情報
Typemiss.net 「WebプログラマのためのCopy On Write解説:mod_perl/FastCGIでメモリを節約する方法」 - メモリ共有について、図解つきで丁寧に説明してくれています。素敵。

cgi の場合

cgi の場合、メモリ共有とかは関係無くなってしまいます。とすると、あんまり語るべきことは思いつかないなあ。メモリ使用量が多いモジュールは使うのを避けよう・・・って話では無いですし。あたりまえか。

先駆者が作ってくれたこれらモジュールは、車輪の再開発をさける意味でも、積極的に利用していくべし、だと思います (たまに駄作もあるかも、だけど、その辺は己のセンスで嗅ぎ取るべし)。で、メモリ使用量が増えた事で起動時間が遅くなって困るとかいう問題が出てくるのであれば、それこそ mod_perl に移行させることをお勧めします。

▲RETURN TO TOP

調査結果から見えてくる疑問点

startup.pl で use しておく、といっても、前述の調査結果をよく見てみると、

例えば LWP::UserAgent の場合

1.0M : use LWP::UserAgent ()
2.8M : use LWP::UserAgent (); LWP::UserAgent->new->get("http://example.com")

のように、単純に use した場合と、実際に利用してみた (getメソッドをコールした) 場合とで、メモリ増加量が大幅に異なっています。真のメモリ共有化・効率化を目指すのであれば startup.pl の中で 2.8M 分きっちりメモリにロードしておいた方が良いのでは?

なぜこんなに差が出ているの?

実際のソースを見るとすぐわかるのですが、例えば前述の LWP::UserAgent の get メソッド付近のコードはこんな感じになっています:

sub get {
    ...
    require HTTP::Request::Common;
    ....
}

require 宣言 = get メソッドが実行されないと、HTTP::Request::Common モジュールがメモリ上にロードされない仕組みになっています(require についての解説はエントリ「Perlのuseとrequireの違い」 を参照) 。似たような require 行が HTTP::Request::Common の中にもあって・・・みたいな連鎖が、メモリ使用量の差を生み出しているようです。

こういった require を使ったモジュール動的ロードの仕組みは、通常の cgi で使う分には「必要な時に必要なモジュールだけをロードする」といった観点から、無駄なメモリ消費を抑えられてGOODなのですが、 mod_perl + メモリ共有を考えた場合だと、逆に作用しちゃってますね。

解決方法

最初は別エントリでぐちゃぐちゃ書いてたんですが、ふと気になって Google 検索してみたら先駆者がすでに語ってくれていました (検索してみてよかった。ホッ)。naoyaのはてなダイアリー「startup.pl で require?」エントリ経由 → Practical mod_perl > Chapter 10. Improving Performance with Shared Memory and Proper Forking

10.1.5. Module Initialization at Server Startup

It's important to preload modules and scripts at server startup. But for some modules this isn't enough, and you have to prerun their initialization code to get more memory pages shared. Usually you will find information about specific modules in their respective manpages.

要約すると 「それぞれのモジュールのドキュメント読んでね。mod_perl 専用の初期化方法について触れてある。はず。」みたいな。mod_perl 専門書にてそう書かれているんだから、きっと万能な対策 (silver bullet)は無いのかなぁ。※mod_perl2だとどうなんだろう?未調査。

というわけで、地道に各モジュールごとの対策を調べていくべし。このエントリ前半の調査結果一覧が、対策が必要なモジュールの特定に役立てば幸いです。

TIPS

しょぼいですけど、各モジュールごとの対策を効率よく調べる手順はこんな感じになるかと思います:

  1. perldoc で文字列 'mod_perl' を検索してみる
    例) CGIモジュールの perldoc だと、すぐに CGI->compile を使うべし、みたいな記述が見つかります。
  2. モジュールのソースコードから、文字列 'MOD_PERL' を検索してみる
    例) Template モジュールだと 'Template::Config->preload() if $ENV{ MOD_PERL }' 記述が見つかり、なにもせずとも勝手に mod_perl 最適化されることがわかります。
  3. それでも見つからないなら、startup.pl の中でメソッドを実行しておいちゃう
    →諦めて get メソッドなりなんなりを実行しといちゃえ /w
    →多少は無駄があるだろうけど、LWP::UserAgentなんかだと有効な気がします /w

▲RETURN TO TOP

おまけ - GTop モジュールのインストールは面倒かも

GTop モジュールは、 libgtopなるGNOME系のライブラリの Perlインターフェースです。なので事前にこのlibgtopを導入しておく必要があります。未知の世界だったので結構ハマりました。glib-config が無いよ、gnome-config も無いよ、 pkg-config も必要だよ・・・って、もー面倒くさかったです。僕の環境 (Debian Sarge) では、apt-install にて libgtop2-2 の他に、libgnome-dev と pkg-config パッケージを導入する事で無事動くようになりました。ご注意あれ。

▲RETURN TO TOP

Comments:0

Comment Form

コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。

Remember personal info

Home > Perl > モジュールをuseした際のメモリ使用量(増加量)を調べてみる

Search
Feeds

Page Top