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 をしてくれません。
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