Python高速化テクニック
ここ2〜3日、InfoPileのパフォーマンスチューニングをしており、ちょっともたつきを感じるような部分をほとんど解消することができた。InfoPileで使用した高速化テクニックの中で効果が大きく、よくつかわれそうなものを紹介しよう。尚、以下のスクリプトはPython 2.6.4で実行した。
listよりtupleを使う
可変長である必要のないシーケンスは、できるだけlistではなくtupleを使って構築しよう。listの生成/解放コストは意外と大きいのだ。
import time def run1(): for i in xrange(1000000): [i, i+1, i+2, i+3, i+4, i+5, i+6, i+7, i+8] def run2(): for i in xrange(1000000): (i, i+1, i+2, i+3, i+4, i+5, i+6, i+7, i+8) f = time.clock() for i in range(10): run1() t = time.clock() print "list", (t-f)/10 f = time.clock() for i in range(10): run2() t = time.clock() print "tuple", (t-f)/10
上のスクリプトを実行すると、
list 0.523081439121 tuple 0.380445943549
のような結果となる。
__slots__を定義する
通常、Pythonのクラスインスタンスは辞書オブジェクトを持っており、属性値はこの辞書に登録されている。辞書オブジェクトは高速に値を参照することができるのだが、その生成はちょっと重たい。
そこでクラス定義に __slots__ という変数を含めておくと、属性値を辞書ではなくシーケンスに登録するように指定することができる。__slots__の使用にはいろいろと注意が必要だが、大量のオブジェクトを生成する必要がある場合には大きな効果がある。
import time class Test1(object): def __init__(self, a, b, c, d, e, f, g, h): self.a = a self.b = b self.c = c self.d = d self.e = e self.f = f self.g = g self.h = h class Test2(object): __slots__ = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h') def __init__(self, a, b, c, d, e, f, g, h): self.a = a self.b = b self.c = c self.d = d self.e = e self.f = f self.g = g self.h = h def run1(): for i in xrange(100000): Test1(i, i, i, i, i, i, i, i) def run2(): for i in xrange(100000): Test2(i, i, i, i, i, i, i, i) f = time.clock() for i in range(10): run1() t = time.clock() print "__dict__", (t-f)/10 f = time.clock() for i in range(10): run2() t = time.clock() print "__slot__", (t-f)/10
このスクリプトの実行結果は以下の通りだ。一行足しただけにしては、なかなかの成果である。
__dict__ 0.216156124591 __slot__ 0.141107857284
ただし、__slots__を使用したオブジェクトは生成は速いが、属性値の参照が少々遅くなる。
import time class Test1(object): def __init__(self, a, b, c, d, e, f, g, h): self.a = a self.b = b self.c = c self.d = d self.e = e self.f = f self.g = g self.h = h class Test2(object): __slots__ = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h') def __init__(self, a, b, c, d, e, f, g, h): self.a = a self.b = b self.c = c self.d = d self.e = e self.f = f self.g = g self.h = h def run1(): o = Test1(1,2,3,4,5,6,7,8) for i in xrange(1000000): o.a, o.b, o.c, o.d, o.e, o.f, o.g, o.h def run2(): o = Test2(1,2,3,4,5,6,7,8) for i in xrange(1000000): o.a, o.b, o.c, o.d, o.e, o.f, o.g, o.h f = time.clock() for i in range(10): run1() t = time.clock() print "__dict__", (t-f)/10 f = time.clock() for i in range(10): run2() t = time.clock() print "__slot__", (t-f)/10
このスクリプトを実行すると
__dict__ 0.54417960942 __slot__ 0.585244549237
となり、数%の速度低下が確認できる。__slots__を使用するかどうかは、実際のデータを使用して十分にテストを行ってから決定した方が良いだろう。