一般社団法人Pythonエンジニア育成推進協会(以下、当協会)の顧問理事の寺田学です。私は試験の問題策定とコミュニティ連携を行う立場です。

先日、Pythonの生みの親であるGuido氏がPyCon JP 2021に寄せたコメントの中で、Pythonのパフォーマンスアップを今後進めていく予定であると発言しました。 確かに、Pythonの実行速度が遅いということはこれまでいろいろな場面で課題となっており、Pythonエンジニアの多くが速度問題に取り組んできました。そこで今回はPythonと速度についてお話ししたいと思います。

  • 一般社団法人Pythonエンジニア育成推進協会 顧問理事 寺田学氏

    著者:寺田学
    一般社団法人Pythonエンジニア育成推進協会 顧問理事

相互換性は維持しつつパフォーマンスは2倍に

Pythonの生みの親Guido van Russum氏がPyCon JP 2021に寄せたオープニングビデオで、今後、相互互換性は大事にしつつ、スピードアップが取り組めるという見込みを立てており、次のバージョンである3.11ではパフォーマンスを2倍にまで向上させると語っています。

実際にどういったスケジュールでどれほど速くなるかはわかりませんし、徐々にという形になるのではないかとは思いますが、確かに、パフォーマンスアップのための調整ができる部分があるはずだと考えています。

Pythonの実行速度が遅い理由は「型推論」

Pythonが遅い理由はこれまでもいろいろと言われてきましたが、変数を代入する場合には型推論が必ず入るというのもその一つに挙げられています。 たとえば、for構文を回す際、inの後に配置される繰り返し要素にはどのような型のデータが来てもいいという事になっています。その理由としては、与えられるデータが整数や文字列、浮動小数点数、リストと、どの型が与えられるのかがわからない状態であるためです。 つまり、Pythonは自由に書ける分、型推論が必要になるため、パフォーマンスに影響しているのではないかということです。

バージョン3.11以降はこういった遅い要因を解消していくということですので、どのような改善に取り組むのか個人的にも非常に期待しています。

数字だけを扱うならNumPyでパフォーマンスアップ

NumPyを使う人にとっては常識ではありますが、数値だけを扱うのであればNumPyだけで処理を閉じれば処理速度は格段に上がります。

NumPyにはC言語で書かれた高速に動作する仕組みが取り入れられており、効率的にコードが書けるだけでなく、NumPy自体が数値を処理するものであるため、整数や浮動小数点数、オブジェクトなど、データ型を一つに整える必要があります。また、何ビットにするかも含めて内部で管理されます。 そのため、最初の時点ですべての値が同一の条件で定義され、データの型にばらつきがなく、型の確認をせずともその定義に従って計算と答えを出すことができます。

たとえばNumPyにはndarrayというNumPyの配列や、全部の合計値を出してくれるメソッドndarray.sumなどの内部に高速で演算してくれる仕組みなどがありますが、こうしたメソッドを使用した方がより高速に内部計算をしてくれます。

◆NumPyでできる処理はNumPyで閉じよう

Pythonに慣れている人やスクリプト言語に慣れている人の場合、NumPyのメソッドを使うより、直感的に一つずつ処理をしていく方がわかりやすいと考えることが多いため、二次元配列の特定の要素の計算をしたい場合にはfor構文を展開しながら計算するというパターンがよくあります。 ですが、この場合、Pythonの領域で処理されるため、型推論の処理が走ります。そうなればプログラムの効率が悪くなり、処理速度が遅くなります。

それに対し、NumPyのメソッドを使えば、NumPyが内部で計算してくれるため、処理によっては何十倍にも高速化するケースがあるほど高速に進めることができます。 全部の合計値を出すだけなら簡単ですが、ある条件に基づいた平均値や分散値を取る場合には、for構文を使うより、NumPyだけで処理を閉じられるようにしましょう。

◆計算処理ではオーバーヘッドが発生するpandasは使わない

データ分析では主にNumPyが使われますが、中にはNumPyを使わずにpandasを使う人もいます。 確かに手元で何かをやる分には使い勝手がいいので便利ですが、スピードアップの観点からみると、pandasはオーバーヘッドが発生しやすいため、サーバサイドエンジニアからすると、おすすめできません。 pandasがオーバーヘッドを起こす理由は、速度が遅くデータ容量も大きい上に、NumPyが一つのデータ型にするのと違い、列ごとにデータ型を決められるからです。

重視するのはスピードか、メンテナンス性か、仕様か

シミュレーションに必要な数多くの計算(四則演算や分散など)を使う場合、for構文を使ってもできますが、あるパターンでは爆発的に処理が増えるため、処理に数分かかってしまうものがあります。 以前、仕事のプロジェクトでNumPyを3次元から4次元にするという方法を取ったプログラムを作りましたが、コードの可読性がとても悪く、コードを見ただけでは何をやっているか想像できない、紙に書き出すにも表しにくいという、後から大変になりそうな代物になったことがあります。 このときは、処理に数十秒掛かっても問題がないものだったため、そこまで高速化に力を入れませんでしたが、15秒程度になるように処理を調整しました。もう少し仕様とスペック、アルゴリズムをきっちりしておけば数秒で答えが出せるようになるのではないかなという感じではありました。

メンテナンス性や処理速度、仕様といった要素でバランスをとるのが難しいことは確かにあります。 マシンパワーをどれだけ計算リソースに活用するかという問題もありますので、そこまで処理速度を上げるために頑張る必要があるかという考えもあります。 今までデータ分析は、自分のマシンで結果を出し、相手に渡せばいいというものでしたが、今はWebなどの外部サービス上でユーザーが入力したデータをサーバで計算するということが盛んに行われるようになりました。 何を重視していくか、バランスを見極めていけるといいと思います。

パフォーマンスのボトルネックを見極める方法

パフォーマンスのボトルネックを知ることは改善の大きな一歩になります。 たとえば、Webアプリであれば計算量がボトルネックになることはなく、ほとんどがIOバウンドやインプット、アウトプット待ちにかかる時間です。 Python特有の問題なのか、それとも使用している構文が遅いのか、それとも別の要因かを考えてみましょう。それによって対策の仕方が異なってきます。

具体的にどう見極めるかですが、最初のポイントはIOバウンドかCPUバウンドにあります。 例えばCPUバウンドが要因なら、計算アルゴリズムに問題があるか、計算ツールに問題あるかを見極めましょう。 さらに、アルゴリズムの問題である場合にはプログラムがデータをどのように探すかによって処理の時間が変わります。たとえば、1万件のリストがあったときに、頭から探すか、末尾から探すか、それ以外の速い探し方がないか、といった具合に掘り下げていきましょう。

さいごに

確かにfor構文など自分でできるところから始めることは大切ですし、Pythonのリストを使いながら学習することも大事です。これらの処理ができるようになった上で、必要に応じてNumPyやPythonのif構文を使うなど、手法を使い分けていければいいと思います。 これはやってはいけないというものはなく、パフォーマンスを改善したいと思うのであればNumPyの方が効率よく速くできるものがあるというだけのことです。 もちろん、Pythonにもともとある機能を使いこなしてパフォーマンスを上げるというのでもいいと思いますので、いろいろ試しながら真剣に取り組んで行ってほしいと思っています。

Pythonは1年に1回、新しいバージョンが公開されますが、毎回、相互互換性が大事にされているため、目新しいものに急いで飛びつかなくても問題がないという特徴があることは事実です。 ですが、新しいものでパフォーマンスが向上し、相互互換性があるのなら積極的に採用してみるといいと思っています。ただ、ライブラリが対応されているかなど、対応するのに適切な時期の見極めは必要です。 今後、Pythonの遅いという本質が改善され、スピードアップしていくことに期待していきたいですね。

当協会の最新情報は公式サイトか、公式Facebookページでご覧いただけます。FacebookページではPythonに関連したニュースもお知らせしていますので、ぜひフォローしてみてください。また、YouTubeチャンネル「Pythonエンジニア認定試験」では、私が試験概要や学習のコツをお話ししたものや、合格した方のコメント動画を公開しています。こちらもぜひご覧ください。

[PR]提供:Pythonエンジニア育成推進協会