商品等のご購入はAmazonがおすすめ

Pythonのお手軽な高速化方法 part2

プログラミング

以前Pythonのお手軽な高速化方法をいくつか紹介しました。

その時に紹介しきれなかった分をまたいくつか紹介しようと思います。

並列化による高速化

並列と並行の違い

実はPython3.12以下の並列化は並列ではありません。

厳密に言うと、一般的に言うマルチスレッディングのイメージと異なります。

以下の画像がわかりやすかったのでお借りしました。

https://www.zunouissiki.com/concurrent-parallel-diff/より引用

Pythonにおけるマルチスレッディングとは「並行化」のことで、「並列化は」マルチプロセッシングといいます。

マルチスレッディング

IO処理などのCPU待ち時間が生じる処理に向いています。1

逆に常に計算するような処理には向いていません。

以下のようにファイルを出力するようなプログラムの場合は明確に性能が向上します。

import os
from concurrent.futures import ThreadPoolExecutor
from timeit import timeit

DIRNAME = "output"

def output_file(filename):
    with open(f"{DIRNAME}/{filename}", "w", encoding="utf-8") as f:
        f.write(f"{filename}")

def io_multi():
    os.makedirs(f"{DIRNAME}", exist_ok=True)
    with ThreadPoolExecutor() as executor:
        results = executor.map(output_file, range(1000))

def io_single():
    os.makedirs(f"{DIRNAME}", exist_ok=True)
    for i in range(1000):
        output_file(i)

def main():
    loop = 10
    elapsed = timeit(io_single, number=loop)
    print(f"io_single(): {elapsed / loop * 1000:.2f} ms")
    elapsed = timeit(io_multi, number=loop)
    print(f"io_multi(): {elapsed / loop * 1000:.2f} ms")

if __name__ == '__main__':
    main()
io_single(): 389.09 ms
io_multi(): 182.04 ms

マルチプロセッシング

CPUでの計算を常に行うような処理に向いています。

ただし、オーバーヘッドが大きいため、大規模な計算でなければ恩恵が得られず、大規模な計算の場合は他のプログラミング言語を使用したり、GPGPUを用いるほうが効果的なのでおすすめしません。

読書をバイトコード化して高速化

ファイルの入出力をテキストデータとして扱うとそのサイズに応じてそれなりの処理時間が掛かってしまいます。

その処理時間を短縮できるのがpickleです。

Pythonでしか扱えませんが、その分お手軽かつ高速です。

注意事項

警告
pickle モジュールは 安全ではありません 。信頼できるデータのみを非 pickle 化してください。
非 pickle 化の過程で任意のコードを実行する ような、悪意ある pickle オブジェクトを生成することが可能です。信頼できない提供元からのデータや、改竄された可能性のあるデータの非 pickle 化は絶対に行わないでください。
データが改竄されていないことを保証したい場合は、 hmac による鍵付きハッシュ化を検討してください。
信頼できないデータを処理する場合 json のようなより安全な直列化形式の方が適切でしょう。 json との比較 を参照してください。

https://docs.python.org/ja/3/library/pickle.html

ちゃんと自身で用意したデータのみを扱ってください。

速度計測

計測にはちょうどPC内にあったビットコインのOHLCVデータを使用します。

そこまで大きいデータでなくとも以下のようにかなりの差が出ます。

from timeit import timeit
import pandas as pd
def write_csv(df):
    csv_name = "write.csv"
    df.to_csv(csv_name)

def write_pickle(df):
    pickle_name = "write.pickle"
    df.to_pickle(pickle_name)

def read_csv():
    csv_name = "btc_jpy_train_data.csv"
    df = pd.read_csv(csv_name, index_col=0)
    return df

def read_pickle():
    pickle_name = "btc_jpy_train_data.pickle"
    df = pd.read_pickle(pickle_name)
    return df

def main():
    loop = 10
    elapsed = timeit(
        read_csv,
        number=loop
    )
    print(f"read_csv(): {elapsed / loop * 1000:0.2f} ms")
    elapsed = timeit(
        read_pickle,
        number=loop
    )
    print(f"read_pickle(): {elapsed / loop * 1000:0.2f} ms")
    elapsed = timeit(
        "write_csv(df)",
        setup="df=read_csv()",
        globals=globals(),
        number=loop
    )
    print(f"write_csv(): {elapsed / loop * 1000:0.2f} ms")
    elapsed = timeit(
        "write_pickle(df)",
        setup="df=read_pickle()",
        globals=globals(),
        number=loop
    )
    print(f"write_pickle(): {elapsed / loop * 1000:0.2f} ms")

if __name__ == '__main__':
    main()
read_csv(): 4.79 ms
read_pickle(): 0.49 ms
write_csv(): 7.66 ms
write_pickle(): 0.82 ms

まとめ

あまり信用していませんが、Python高速化計画なるものも存在しますので気長に小細工していきましょう。

  1. 2024年10月にリリース予定のPython3.13ではGILによる制約が撤廃される可能性があり、その場合CPU待ち時間に関係なく高速化が期待できます。 ↩︎

コメント

モバイルバージョンを終了
タイトルとURLをコピーしました