<?xml version="1.0" encoding="utf-8" ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Script-Type" content="text/javascript" />
  <link rel="stylesheet" type="text/css" href="//blog.8-p.info/2009/wp-content/themes/b8i/ALL.css" />

  <link rel="alternate" type="application/rss+xml"
             href="//blog.8-p.info/2009/feed" title="Japanese + English" />
  <link rel="alternate" type="application/atom+xml"
             href="//blog.8-p.info/2009/tag/lang-en/feed/atom" title="English" />

  <link rel="icon" href="//blog.8-p.info/favicon.ico" type="image/vnd.microsoft.icon" />
  <title>Devel::NYTProf を読む - blog.8-p.info</title>

<link rel="EditURI" type="application/rsd+xml" title="RSD" href="//blog.8-p.info/2009/xmlrpc.php?rsd" />
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="//blog.8-p.info/2009/wp-includes/wlwmanifest.xml" /> 


</head>

<body>

<div id="header">
  <div class="main">
    <h1><a href="/">blog.8-p.info</a></h1>
    <p>加藤和良 (1984年うまれ) の個人的なブログです。</p>
  </div>
  <div class="sidebar">
    <form method="get" action="//blog.8-p.info/2009/">
      <div id="search">
        <input type="text" class="text" id="searchKeyword"  name="s"
               value="" />
      </div>
    </form>
  </div>
</div>

<div id="body">
  <div class="entry" id="entry1224">
  <h2 class="entry-title"><a href="//blog.8-p.info/2009/09/devel-nytprof">Devel::NYTProf を読む</a>
      </h2>
  <div class="yui-g meta">
    <div class="date yui-u first">
      2009-09-05 05:27    </div>
    <div class="tags yui-u">
          </div>
  </div>
  <div class="body"><p>Devel::NYTProf は Perl むけのプロファイラだ。機能が豊富で、HTML による結果出力がきれいで、プロファイラ自身のオーバーヘッドが少ない、ということで最近の Perl 界隈では事実上の標準プロファイラになっていると思う。</p>
<div class="eyecatch"><img src="//blog.8-p.info/2009/wp-content/uploads/2009/09/nytprof.png" alt="Devel::NYTProf" title="Devel::NYTProf" width="600" height="160"/></div>
<p>Perl にはもともとデバッガ用のインターフェースがある。perldoc perldebguts で低レベルな詳細が perldoc DB で (暫定的なものとされているけど) 高レベルなインターフェースが確認できる。後者からすこし引用しておこう。</p>
<blockquote><p>Perl debug information is frequently required not just by debuggers, but also by modules that need some &#8220;special&#8221; information to do their job properly, like profilers.</p></blockquote>
<p>Devel::NYTProf もこれを使用している、だけではなかった。</p>
<h3>ソースツリー</h3>
<ul class="hier">
<li>Changes</li>
<li>HACKING</li>
<li>INSTALL</li>
<li>MANIFEST</li>
<li>META.yml</li>
<li>Makefile.PL</li>
<li><strong>NYTProf.xs</strong></li>
<li>README</li>
<li>benchmark.pl</li>
<li class="dir">bin/
<ul>
<li>nytprofcg</li>
<li>nytprofcsv</li>
<li><strong>nytprofhtml</strong></li>
</ul>
</li>
<li class="dir">demo/</li>
<li class="dir">lib/
<ul>
<li class="dir">Devel/
<ul>
<li class="dir">NYTProf/</li>
<li><strong>NYTProf.pm</strong></li>
</ul>
</li>
</ul>
</li>
<li>perftest.pl</li>
<li>ppport.h</li>
<li class="dir">t/</li>
<li>typemap</li>
<li class="dir">xt/</li>
</ul>
<p>Devel::NYTProf のうちの計測部分は C で書かれた NYTProf.xs にある。HTML 出力部分は lib/ 以下ではなく bin/nytprofhtml にあり Template::Toolkit や HTML::Template にたよらない男らしいコードだった。</p>
<p>use すると読み込まれる lib/Devel/NYTProf.pm にコードはほとんど無い。</p>
<pre>
package Devel::NYTProf;

our $VERSION = '2.10';

package    # hide the package from the PAUSE indexer
    DB;

# Enable specific perl debugger flags.
# Set the flags that influence compilation ASAP so we get full details
# (sub line ranges etc) of modules loaded as a side effect of loading
# Devel::NYTProf::Core (ie XSLoader, strict, Exporter etc.)
# See "perldoc perlvar" for details of the $^P ($PERLDB) flags

$^P = 0x010     # record line range of sub definition
    | 0x100     # informative "file" names for evals
    | 0x200;    # informative names for anonymous subroutines

# XXX hack, need better option handling
my $use_db_sub = ($ENV{NYTPROF} &amp;&amp; $ENV{NYTPROF} =~ m/\buse_db_sub=1\b/);

$^P |= 0x002    # line-by-line profiling via DB::DB (if $DB::single true)
    |  0x020    # start (after BEGINs) with single-step on
    if $use_db_sub;

require Devel::NYTProf::Core;    # loads XS

if ($use_db_sub) {               # install DB::DB sub
    *DB = ($] &lt; 5.008008)
        ? sub { goto &#038;DB_profiler }    # workaround bug in old perl versions (slow)
        : \&#038;DB_profiler;
}

init_profiler();                       # provides true return value for module
</pre>
<p>perldbguts に登場する DB::DB は、環境変数 NYTPROF に設定がないと使われないのがわかる。では、NYTProf はどこで時間を測定してるんだろうか。</p>
<h3>フック</h3>
<p>NYTProf.pm にあった DB::init_profiler は NYTProf.xs の末尾のほうにある</p>
<pre>
MODULE = Devel::NYTProf     PACKAGE = DB

...

int
init_profiler()
    C_ARGS:
    aTHX
</pre>
<p>から </p>
<pre>
/* Initial setup */
static int
init_profiler(pTHX)
{

...

    /* redirect opcodes for statement profiling */
    Newxc(PL_ppaddr_orig, OP_max, void *, orig_ppaddr_t);
    Copy(PL_ppaddr, PL_ppaddr_orig, OP_max, void *);
    if (profile_stmts &amp;&amp; !use_db_sub) {
        PL_ppaddr[OP_NEXTSTATE]  = pp_stmt_profiler;
        PL_ppaddr[OP_DBSTATE]    = pp_stmt_profiler;
#ifdef OP_SETSTATE
        PL_ppaddr[OP_SETSTATE]   = pp_stmt_profiler;
#endif
        if (profile_leave) {
            PL_ppaddr[OP_LEAVESUB]   = pp_leaving_profiler;
            PL_ppaddr[OP_LEAVESUBLV] = pp_leaving_profiler;
            PL_ppaddr[OP_LEAVE]      = pp_leaving_profiler;
            PL_ppaddr[OP_LEAVELOOP]  = pp_leaving_profiler;
            PL_ppaddr[OP_LEAVEWRITE] = pp_leaving_profiler;
            PL_ppaddr[OP_LEAVEEVAL]  = pp_leaving_profiler;
            PL_ppaddr[OP_LEAVETRY]   = pp_leaving_profiler;
            PL_ppaddr[OP_DUMP]       = pp_leaving_profiler;
            PL_ppaddr[OP_RETURN]     = pp_leaving_profiler;
            /* natural end of simple loop */
            PL_ppaddr[OP_UNSTACK]    = pp_leaving_profiler;
            /* OP_NEXT is missing because that jumps to OP_UNSTACK */
            /* OP_EXIT and OP_EXEC need special handling */
            PL_ppaddr[OP_EXIT]       = pp_exit_profiler;
            PL_ppaddr[OP_EXEC]       = pp_exit_profiler;
        }
    }

    /* redirect opcodes for caller tracking */
    if (!sub_callers_hv)
        sub_callers_hv = newHV();
    if (!pkg_fids_hv)
        pkg_fids_hv = newHV();
    PL_ppaddr[OP_ENTERSUB] = pp_entersub_profiler;
</pre>
<p>につながっている。そう、コメントにある通り、Devel::NYTProf は Perl VM のディスパッチテーブルを書き換え、計測用の関数をあいだにはさんでいる。</p>
<p>複数種の命令に同じ関数をあてて大丈夫なのか心配なので pp_stmt_profiler, pp_leaving_profiler, pp_exit_profiler まで追ってみると</p>
<pre>
static OP *
pp_stmt_profiler(pTHX)                            /* handles OP_DBSTATE, OP_SETSTATE, etc */
{
    OP *op = run_original_op(PL_op-&gt;op_type);
    DB_stmt(aTHX_ NULL, op);
    return op;
}

static OP *
pp_leaving_profiler(pTHX)                         /* handles OP_LEAVESUB, OP_LEAVEEVAL, etc */
{
    OP *op = run_original_op(PL_op-&gt;op_type);
    DB_leave(aTHX_ op);
    return op;
}

static OP *
pp_exit_profiler(pTHX)                            /* handles OP_EXIT, OP_EXEC, etc */
{
    DB_leave(aTHX_ NULL);                         /* call DB_leave *before* run_original_op() */
    if (PL_op-&gt;op_type == OP_EXEC)
        finish_profile(aTHX);                     /* this is the last chance we'll get */
    return run_original_op(PL_op-&gt;op_type);
}
</pre>
<p>現在実行中の命令はグローバル変数 PL_op で参照できるので問題ないようだ。pp_entersub_profiler はすこし入り組んでいるので省略。</p>
<p>DB_stmt, DB_leave なんて名前の関数が内部でしか使われていないのは、VM に手を入れる手法が開発のごく初期には無かった (DB を使う手法が最初にあった) せいかもしれない。Devel::NYTProf の HISTORY からすこし引用しておく。</p>
<blockquote><p>Then he went a little crazy and added a slew of new features, in addition to per‐sub and per‐block timing and subroutine caller tracking. These included the ’opcode interception’ method of profiling, ultra‐fast and robust inclusive subroutine timing, doubling performance, &#8230;</p></blockquote>
<h3>計測</h3>
<p>フックがしかけられれば、そこから計測をすればいい。pp_leaving_profiler, pp_exit_profiler から DB_leave にいくと</p>
<pre>
    /* measure and output end time of previous statement
     * (earlier than it would have been done)
     * and switch back to measuring the 'calling' statement
     */
    DB_stmt(aTHX_ NULL, op);
</pre>
<p>pp_stmt_profiler とおなじかたちで DB_stmt を呼び出していた。</p>
<p>DB_stmt は以下のことをやっている。</p>
<ul>
<li>現在の時刻 - 前の時刻を計算</li>
<li>それを書き出し</li>
<li>現在の時刻を保存</li>
</ul>
<p>コードのうち、該当する部分だけを抜き出すとこうなる。</p>
<pre>
DB_stmt(pTHX_ COP *cop, OP *op)
{
...
    if (usecputime) {
...
    }
    else {
        get_time_of_day(end_time);
        get_ticks_between(start_time, end_time, elapsed, overflow);
    }

    if (last_executed_fid) {

        output_tag_int((unsigned char)((profile_blocks)
                        ? NYTP_TAG_TIME_BLOCK : NYTP_TAG_TIME_LINE), elapsed);
        output_int(last_executed_fid);
        output_int(last_executed_line);
...
    }

    if (usecputime) {
...
    }
    else {
        get_time_of_day(start_time);
    }
...
}
</pre>
<p>get_time_of_day は第1引数、get_ticks_between は第3, 4引数を破壊するマクロなのがわかりづらい。fid は file id なので、これで各ファイルの行について、前の命令をぬけてから (run_original_op の後に DB_stmt を呼んでいるので) 今回の命令を実行し終わるまでにかかった時間がわかる。</p>
<h3>プロファイル結果のファイルサイズ</h3>
<p>ちょっと余談。NYTProf.xs には zlib による圧縮が組み込まれている。さらにいうと圧縮前のファイルもバイナリ形式だし、整数は Procotol Buffers や SWF のように桁数に応じてバイト長が変わる。fread でさくっと読めるような形式じゃない。</p>
<p>圧縮の理由については <a href="http://blog.timbunce.org/2008/10/03/nytprof-204-gives-you-90-smaller-data-files/">NYTProf 2.04 gives you 90% smaller data files</a> に説明がある。</p>
<blockquote><p>One of the major drawbacks of statement-level profiling is the volume of data it generates while profiling your code. For every statement executed the profiler streams out the file id, the line number, and the time spent. For every statement! When trying to profile a full application doing real work the volume of data generated quickly becomes impractical to deal with. Multi-gigabyte files are common.</p></blockquote>
<h3>まとめと関連</h3>
<p>Devel::NYTProf は Perl むけのプロファイラで、VM をフックすることでプロファイルのオーバーヘッドをちいさくしている。ちなみに、プロファイラの結果も圧縮されるのでちいさい。</p>
<p>個人的には VM のディスパッチテーブルなんて static でファイルスコープにしておきたいけど、公開されてるってことは変更されることも、たぶん設計の意図のうちなんだろう。</p>
<p>なお、Perl の VM については <a href="http://d.hatena.ne.jp/tokuhirom/20090331/1238514697">Perl5 フレキシビリティ</a>、プロファイラ全般については <a href="http://www.dodgson.org/omo/t/?date=20070823">プロファイラのしくみ</a> が参考になります。</p>
</div>
</div>

<!-- You can start editing here. -->


			<!-- If comments are open, but there are no comments. -->

	 


<h3 id="respond">Leave a Reply</h3>


<form action="//blog.8-p.info/2009/wp-comments-post.php" method="post" id="commentform">


<p><input type="text" name="author" id="author" value="" size="22" tabindex="1" aria-required='true' />
<label for="author"><small>Name (required)</small></label></p>

<p><input type="text" name="email" id="email" value="" size="22" tabindex="2" aria-required='true' />
<label for="email"><small>Mail (will not be published, for <a href="http://en.gravatar.com/">Gravatar</a>) (required)</small></label></p>

<p><input type="text" name="url" id="url" value="" size="22" tabindex="3" />
<label for="url"><small>Website</small></label></p>


<!--<p><small><strong>XHTML:</strong> You can use these tags: <code>&lt;a href=&quot;&quot; title=&quot;&quot;&gt; &lt;abbr title=&quot;&quot;&gt; &lt;acronym title=&quot;&quot;&gt; &lt;b&gt; &lt;blockquote cite=&quot;&quot;&gt; &lt;cite&gt; &lt;code&gt; &lt;del datetime=&quot;&quot;&gt; &lt;em&gt; &lt;i&gt; &lt;q cite=&quot;&quot;&gt; &lt;strike&gt; &lt;strong&gt; </code></small></p>-->

<p><textarea name="comment" id="comment" cols="100%" rows="10" tabindex="4"></textarea></p>

<p><input name="submit" type="submit" id="submit" tabindex="5" value="Submit Comment" />
<input type="hidden" name="comment_post_ID" value="1224" />
</p>

</form>


</div>

<div id="footer">Powered by <a href="http://wordpress.org/">WordPress</a></div>


<!-- Google Analytics -->
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
var pageTracker = _gat._getTracker("UA-329758-2");
pageTracker._trackPageview();
</script>

<script type="text/javascript" src="//blog.8-p.info/2009/wp-content/themes/b8i/ALL.js"></script>

<script type="text/javascript">//<![CDATA[
(function () {
  var elements = document.body.getElementsByTagName('pre');
  var i, pre;
  for (i = 0; i < elements.length; i++) {
    pre = elements[i];
    if ((pre.innerText || pre.textContent).match(/^[%\(]/)) {
      ;
    } else {
      pre.className += ' prettyprint';
    }
  }
  prettyPrint();

  (new TextField($('searchKeyword'))).setPlaceholder('Search');
})();
//]]></script>

</body>
</html>
