このBlogは移転しました。今後は aish.dev を御覧ください。

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__を使用するかどうかは、実際のデータを使用して十分にテストを行ってから決定した方が良いだろう。