Python には プロファイラ がありますが、ここでは簡単な行実行回数プロファイラを作成してみたいと思います。
このプロファイラは 2 つのプログラムからなります。1 つはプロファイルされる Python プログラムを読み込んで、新しい Python プログラムを作成します。新しいプログラムは、コードブロックの先頭でカウンタを 1 インクリメント増やし、プログラム終了時に実行回数をファイルに書き出すようになっています。2 つ目のプログラムは、実行回数が記録されたファイルと元の Python プログラムを読み込んで、コードと共に行実行回数を表示します。
makeprof.py は、プロファイルされる Python プログラムからプロファイル版の Python プログラムを生成します。
# makeprof.py - python プログラムのプロファイル版を作成する # > python makeprof.py program.py >program.p.py とすると # program.py のプロファイル版 program.p.py を生成する import sys init = """ class Prof: def __init__(self, size): self.lst = [0] * size def count(self, n): self.lst[n] += 1 def output(self): file = open("prof.cnts", "w") for i in self.lst: file.write("%d\\n" % i) file.close() """ def parse(filename): def nspc(line): n = 0 for c in line: if c == ' ': n += 1 else: break return n + 2 file = open(filename, "r") counter = 0 lines = [] for line in file.readlines(): lines.append(line) if len(line) > 1 and line[-2] == ":": lines.append("%sprof.count(%d)\n" % (' ' * nspc(line), counter)) counter += 1 file.close() return lines, counter def makeprof(filename): lines, cnt = parse(filename) print init print "prof = Prof(%d)\n" % cnt for line in lines: print line, print "\nprof.output()" makeprof(sys.argv[1])
関数 makeprof では読み込んだ Python プログラムの各行に対し、行末がセミコロン(:)で終わっていれば、その次の行に以下のコードを埋め込みます:
prof.count(n)
n は行によってにユニークな値になるような値にします。
テストプログラムとしてフィボナッチ数を計算するプログラム:
# fib.py def fib(n): if n == 0 or n == 1: return 1 else: return fib(n-1) + fib(n-2) def fib2(n, a=1, b=0): if n < 1: return a else: return fib2(n-1, a+b, a) for i in range(1, 10): print fib(i) for i in range(1, 10): print fib2(i)
fib.py を makeprof.py にかけると、つぎのようなコードを出力します。
# fib.py を入力として makeprof.py から出力されるプログラム class Prof: def __init__(self, size): self.lst = [0] * size def count(self, n): self.lst[n] += 1 def output(self): file = open("prof.cnts", "w") for i in self.lst: file.write("%d\n" % i) file.close() prof = Prof(8) def fib(n): prof.count(0) if n == 0 or n == 1: prof.count(1) return 1 else: prof.count(2) return fib(n-1) + fib(n-2) def fib2(n, a=1, b=0): prof.count(3) if n < 1: prof.count(4) return a else: prof.count(5) return fib2(n-1, a+b, a) for i in range(1, 10): prof.count(6) print fib(i) for i in range(1, 10): prof.count(7) print fib2(i) prof.output()
出力されたプログラムを実行すると、各行の実行回数が記録されたファイル(prof.cnts)が作成されます。
2 つ目のプログラム printprof.py は、prof.cnts と元のプログラム fib.py を元にプロファイル結果を表示します。
# printprof.py - プロファイルで得られた行実行回数を表示 # > python printprof.py program.py とすると # prof.cnts に記録された行実行回数と program.py を表示する import sys def read_profcnts(): lst = [] file = open("prof.cnts", "r") for line in file.readlines(): lst.append(line[:-1]) return lst def printprof(filename): cntslst = read_profcnts() file = open(filename, "r") cnt = 0 for line in file.readlines(): if len(line) > 1 and line[-2] == ":": print "%7s " % cntslst[cnt], line, cnt += 1 else: print " " * 9, line, printprof(sys.argv[1])
fib.py に対するプロファイル実行の様子を以下に示します。
> python makeprof.py fib.py >fib.p.py # プロファイル版の fib.p.py を生成 > python fib.p.py # fib.p.py の実行 …実行結果省略… > python printprof.py fib.py # プロファイルの結果を表示
275 def fib(n): 142 if n == 0 or n == 1: return 1 133 else: return fib(n-1) + fib(n-2)
54 def fib2(n, a=1, b=0): 9 if n < 1: return a 45 else: return fib2(n-1, a+b, a)
9 for i in range(1, 10): print fib(i)
9 for i in range(1, 10): print fib2(i)
プログラムの左側に実行回数が表示されます。たとえば、fib 関数は 275 回呼び出されており、そのうち if 文の真ブロックが 142 回、else ブロックが 133 回実行されているのが分かります(142 + 133 = 275)。
たとえば チェス盤にクイーンをおく問題 で作成した queen.py に対してこのプロファイラを実行してみると次のようになります。
92 def print_board(size, board): 184 def border(): return '+' + '-' * (size*2+1) + '+' 736 def line(x): return '| ' + '. ' * x + 'Q ' + '. ' * (size-x-1) + '|' print border() 736 for x,y in board: print line(x) print border() 5508 def conflict(x, y, board): 19368 def threat(a, b): return abs(x-a) == abs(y-b) 19368 for a,b in board: 3452 if threat(a, b): return True return False 1 def queen(size): 1965 def rec(y, board, rst): 5508 for x in rst: 2056 if not conflict(x, y, board): bd = board + ((x,y),) 92 if y+1 == size: print_board(size, bd) 1964 else: rec(y+1, bd, filter(lambda a: a!=x, rst)) rec(0, (), range(size)) queen(8)
このプロファイラは「行末にセミコロンがある行の次の行に(プロファイル用の)コードを埋め込む」というシンプルな作りなため、fib.py のような単純なコードでは便利に利用できますが、一行で書かれた if 文に対応できていないなど多くの不都合があります。あくまで参考程度にということでご理解ください。
このプロファイラ作成には『プログラミング言語 AWK』を参考にしました。