gae アプリ 開発メモ

Google App Engine アプリの開発メモ / 言語: python, javascript / ビギナー

Python は関数を呼ぶコストがハンパない

テキストファイルを1バイトずつ走査するテストコードを書いてみた。
テストするテキストファイルのサイズは 6,811,956 バイト。

まずは元のソース。

def main():
    buffer = ''
    filei = open('sample.txt', 'r')
    while True:
        read_buffer = filei.read(0x2000)
        if read_buffer == '':
            break

        index = 0
        length = len(read_buffer)
        while index < length:
            buffer += read_buffer[index]
            index += 1
    filei.close()

    fileo = open('out.txt', 'w')
    fileo.write(buffer)
    fileo.close()

if __name__ == '__main__':
    main()

profile した結果、

         1622 function calls in 5.103 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        2    0.000    0.000    0.000    0.000 :0(close)
      806    0.001    0.000    0.001    0.000 :0(len)
        2    0.001    0.000    0.001    0.000 :0(open)
      807    0.041    0.000    0.041    0.000 :0(read)
        1    0.007    0.007    0.007    0.007 :0(setprofile)
        1    0.024    0.024    0.024    0.024 :0(write)
        1    0.000    0.000    5.096    5.096 RLE-SA~1.PY:7(<module>)
        1    5.029    5.029    5.096    5.096 RLE-SA~1.PY:7(main)
        1    0.000    0.000    5.103    5.103 profile:0()
        0    0.000             0.000          profile:0(profiler)

ここに空関数を足してみる。

# ↓ コレを追加
def foo():
    pass

def main():
    buffer = ''
    filei = open('sample.txt', 'r')
    while True:
        read_buffer = filei.read(0x2000)
        if read_buffer == '':
            break

        index = 0
        length = len(read_buffer)
        while index < length:
            buffer += read_buffer[index]
            index += 1
            foo()  # ← コレを追加
    filei.close()

    fileo = open('out.txt', 'w')
    fileo.write(buffer)
    fileo.close()

if __name__ == '__main__':
    main()

profile した結果はこちら。

         6598988 function calls in 14.462 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        2    0.000    0.000    0.000    0.000 :0(close)
      806    0.001    0.000    0.001    0.000 :0(len)
        2    0.001    0.000    0.001    0.000 :0(open)
      807    0.042    0.000    0.042    0.000 :0(read)
        1    0.007    0.007    0.007    0.007 :0(setprofile)
        1    0.023    0.023    0.023    0.023 :0(write)
        1   10.526   10.526   14.454   14.454 RLE-SA~1.PY:10(main)
        1    0.000    0.000   14.455   14.455 RLE-SA~1.PY:7(<module>)
  6597366    3.861    0.000    3.861    0.000 RLE-SA~1.PY:7(foo)
        1    0.000    0.000   14.462   14.462 profile:0()
        0    0.000             0.000          profile:0(profiler)

5.103 seconds → 14.462 seconds って…。
プロファイルの結果によると、ざっくり考えても

  • 空関数の foo の実行時間は 3.861 seconds
  • 関数の呼び出しコストは 5.498 seconds

ってこと。

速度の最適化を考える場合、『ループの中の関数呼び出しを減らす』のは Python には効果てきめんのようだ。