PyROOT を使う時の注意点 (書きかけ)

PyROOT (Python + ROOT) を使う時の注意点をいくつか。適宜更新予定です。公式の PyROOT の manual が参考になります。

1. Memory の開放

1.1. C++ 内部で new したものの扱い

Python はメモリ管理を気にしなくて良いと言われますが、気をつけなくてはいけない場合も存在します。

#include "TGraph.h"

TGraph* newgraph()
{
  return new TGraph(1000);
}
import ROOT
import resource

ROOT.gROOT.ProcessLine('.L mem.C+')

for i in range(10000):
    graph = ROOT.newgraph() # graph という変数が新しい TGraph で上書きするだけ
    if i%1000 == 0:
        print i, resource.getrusage(resource.RUSAGE_SELF)[2]

for i in range(10000):
    graph = ROOT.newgraph()
    del graph # 試しに del を実行してみる
    if i%1000 == 0:
        print i, resource.getrusage(resource.RUSAGE_SELF)[2]

for i in range(10000):
    graph = ROOT.newgraph()
    graph.IsA().Destructor(graph) # 明示的に TObject の destructor を呼び出す
    if i%1000 == 0:
        print i, resource.getrusage(resource.RUSAGE_SELF)[2]

上記のような mem.C と mem.py を用意して、mem.py を実行してみましょう。どのような場合に new された TGraph が解放されるかが分かります。

$ python mem.py
0 42803200
1000 59510784
2000 76222464
3000 92925952
4000 109633536
5000 126341120
6000 143048704
7000 159764480
8000 176472064
9000 193183744
0 209895424
1000 226611200
2000 243314688
3000 260022272
4000 276729856
5000 293437440
6000 310153216
7000 326860800
8000 343564288
9000 360280064
0 378089472
1000 378109952
2000 378109952
3000 378109952
4000 378109952
5000 378109952
6000 378109952
7000 378109952
8000 378126336
9000 378126336

3 つ目の方法、明示的に TGraph::~TGraph() を呼び出した場合以外は、memory leak することが分かります。これは考えると当たり前なのですが、うっかり「Python は memory 管理しなくて大丈夫」と思って作業すると大変なことになります。

Python 側では new された TGraph が C++ 内部で使われ続けているのか判断できないため、Python が自動的には delete をしてくれません。

参照 http://wlav.web.cern.ch/wlav/pyroot/memory.html

2. 参照

例 1. TGraph::GetPoint(Int_t i, Double_t& x, Double_t& y)
import ROOT
import array

graph = ROOT.TGraph()
graph.SetPoint(0, 10, 20)

x = array.array('d', [0])
y = array.array('d', [0])

graph.GetPoint(0, x, y)
print x[0], y[0] # 10.0 20.0

3. ポインタ

例 1. TGraph.GetX()

TGraph::GetX() では、member 変数である Double_t* fX という配列の pointer を返します。PyROOT の中ではこれを ROOT.PyDoubleBuffer という type に変換しますが、中身は C の pointer のようなものです。

そのため、以下の例のように配列内の要素に直接演算を行ったり、配列の要素を取り出すことができます。また、当然 C の配列ですので、segmentation fault を引き起こす可能性があります。

import ROOT

graph = ROOT.TGraph()
graph.SetPoint(0, 10, 20)

print graph.GetX()[0] # -> 10.0

graph.GetX()[0] += 30

print graph.GetX()[0] # -> 40.0

graph.GetX()[10000000] # -> seg fault

4. 関数呼び出し速度の向上

PyROOT は CINT や ACliC で実行する C++ な ROOT script に比べると処理速度が遅くなる場合があります。そのひとつに、関数呼び出し時の overhead の存在があります。

これは PyROOT の問題と言うより、Python の仕様なのですが、次のように code を工夫することで、若干ですが処理速度が上がるはずです。

hist = ROOT.TH2D('hist', '', 100, -5, 5, 100, -5, 5)
x = ROOT.gRandom.Gaus() # 別に乱数じゃなくても良いが
y = ROOT.gRandom.Gaus()

for i in range(100000):
    hist.Fill(x, y) # これが単純なやり方
hist = ROOT.TH2D('hist', '', 100, -5, 5, 100, -5, 5)
x = ROOT.gRandom.Gaus()
y = ROOT.gRandom.Gaus()
fill = hist.Fill # 別名で呼べるようにする

for i in xrange(1000000):
    fill(x, y) # hist.Fill を探しに行かない分、速度が改善する

time で測定すると、10% くらい早くなったのが分かります。

python tmp.py  15.17s user 5.42s system 109% cpu 18.829 total
python tmp.py  13.86s user 4.56s system 108% cpu 16.914 total