私は長い間Pythonユーザーです。数年前、私はC++の学習を始めて、速度の点でC++が何を提供できるかを確認しました。この間、私はPythonをプロトタイピングのツールとして引き続き使用します。これは良いシステムのように思われました:Pythonによるアジャイル開発、C++での高速実行。
最近、私はPythonを何度も使用していて、この言語を使用して以前にすぐに使用した落とし穴とアンチパターンをすべて回避する方法を学びました。特定の機能(内包表記、列挙など)を使用するとパフォーマンスが向上することは理解しています。
しかし、Pythonスクリプトが同等のC++プログラムと同じくらい高速になるのを妨げる技術的な制限や言語機能はありますか?
数年前にフルタイムでPythonプログラミングの仕事をしたとき、私はこの壁にぶつかりました。私はPythonが大好きです。本当に好きですが、パフォーマンスのチューニングを始めたとき、失礼なショックがありました。
厳格なPythonの専門家は私を正すことができますが、私が見つけたものは、非常に広いストロークで描かれています。
これはパフォーマンスに影響を与えます。これは、他の言語と比較して、大量のメモリの周りにログを記録することに加えて、実行時に間接レベルが増えることを意味するためです。
他の人は実行モデルと話すことができますが、Pythonは実行時にコンパイルされて解釈されます。つまり、コードを機械化することはできません。これはパフォーマンスにも影響しますCまたはC++モジュールで簡単にリンクしたり、それらを見つけたりできますが、Pythonをそのまま実行すると、パフォーマンスに影響が出ます。
さて、Webサービスのベンチマークでは、Pythonは、RubyまたはPHPのような他のコンパイル時の言語と比較して有利です。しかし、ほとんどのコンパイルされた言語。中間言語にコンパイルされ、VM(JavaまたはC#)のように)で実行される言語でさえ、はるかに優れています。
以下は、私が時々参照する非常に興味深い一連のベンチマークテストです。
http://www.techempower.com/benchmarks/
(そうは言っても、私は今でもPythonが大好きです。使用している言語を選択する機会を得れば、それが私の最初の選択です。ほとんどの場合、私はそうではありません。とにかく、クレイジーなスループット要件による制約を受けます。)
Pythonリファレンス実装は「CPython」インタープリターです。かなり高速になるように努めていますが、現在は高度な最適化を採用していません。そして、多くの使用シナリオでは、これは良いことです:コンパイル一部の中間コードはランタイムの直前に発生し、プログラムが実行されるたびにコードが新たにコンパイルされます。したがって、最適化に必要な時間は、最適化によって得られた時間と比較検討する必要があります。正味の利益がない場合、最適化非常に長時間実行されるプログラム、または非常にタイトなループを持つプログラムの場合、高度な最適化を採用すると便利です。ただし、CPythonは、積極的な最適化を妨げるいくつかのジョブに使用されます。
使用される短期実行スクリプト。 sysadminタスク用。 Ubuntuなどの多くのオペレーティングシステムは、Pythonの上にインフラストラクチャの優れた部分を構築しています。CPythonはその仕事に十分な速度ですが、実質的に起動時間はありません。 bashよりも高速である限り、それは良いことです。
CPythonはリファレンス実装であるため、明確なセマンティクスが必要です。これにより、「foo演算子の実装を最適化する」や「リスト内包をより高速なバイトコードにコンパイルする」などの単純な最適化が可能になりますが、一般に、関数のインライン化などの情報を破壊する最適化はできません。
もちろん、CPython以外にもPython=実装があります:
JythonはJVMの上に構築されています。 JVMは、提供されたバイトコードを解釈またはJITコンパイルでき、プロファイルに基づく最適化機能を備えています。起動時間が長く、JITが始まるまでに時間がかかります。
PyPyは最先端のJITting Python VMです。PyPyは、Pythonの制限付きサブセットであるRPythonで記述されています。このサブセットは、Pythonから一部の表現力を取り除きますが、変数の型を静的に推論されます。RPythonで記述されたVMは、Cにトランスパイルできます。これにより、RPythonのようなパフォーマンスが得られます。ただし、RPythonはCよりも表現力があり、新しい最適化をより速く開発できます。 PyPyはコンパイラのブートストラップの例であり、RPythonではなくPyPyはCPythonリファレンス実装とほとんど互換性があります。
Cythonは(RPythonのように)互換性のないPython方言と静的型付けを備えています。また、Cコードに変換され、CPythonインタープリター用のC拡張を簡単に生成できます。
PythonコードをCythonまたはRPythonに変換する場合は、Cのようなパフォーマンスが得られます。ただし、これらは「Pythonのサブセット」として理解されるべきではありませんが、 PyPyに切り替えると、バニラPythonコードは大幅に速度が向上しますが、CまたはC++で記述された拡張機能とインターフェースすることもできなくなります。
しかし、長い起動時間を除いて、どのプロパティまたは機能がバニラPythonがCのようなパフォーマンスレベルに到達するのを妨げていますか?
寄稿者と資金。 JavaまたはC#とは異なり、この言語をクラス最高にすることに関心を持つ言語の背後に単一の推進会社はありません。これにより、開発は主にボランティアに限定され、場合によっては助成金に限定されます。
遅延バインディングと静的型付けの欠如。 Pythonを使用すると、次のようながらくたを書くことができます。
_import random
# foo is a function that returns an empty list
def foo(): return []
# foo is a function, right?
# this ought to be equivalent to "bar = foo"
def bar(): return foo()
# ooh, we can reassign variables to a different type – randomly
if random.randint(0, 1):
foo = 42
print bar()
# why does this blow up (in 50% of cases)?
# "foo" was a function while "bar" was defined!
# ah, the joys of late binding
_
Pythonでは、任意の変数をいつでも再割り当てできます。これにより、キャッシュやインライン化が防止されます。すべてのアクセスは変数を通過する必要があります。この間接性により、パフォーマンスが低下します。もちろん、コードがそのようなめちゃくちゃなことをしないので、コンパイル前に各変数に明確な型を指定でき、各変数が1回だけ割り当てられる場合、理論的には、より効率的な実行モデルを選択できます。これを念頭に置いた言語は、識別子を定数としてマークする何らかの方法を提供し、少なくともオプションの型注釈(「段階的型付け」)を許可します。
問題のあるオブジェクトモデル。スロットが使用されない限り、オブジェクトがどのフィールドを持っているかを理解するのは困難です(Pythonオブジェクトは基本的にフィールドのハッシュテーブルです)。これらのフィールドのタイプ。これは、C++の場合のように、オブジェクトを密にパックされた構造体として表現することを防ぎます(もちろん、C++のオブジェクトの表現も理想的ではありません。構造体のような性質のため、プライベートフィールドでさえ、オブジェクトのパブリックインターフェイス。)
ガベージコレクション。多くの場合、GCは完全に回避できます。 C++では、現在のスコープを離れると自動的に破棄されるオブジェクトを静的に割り当てることができます:Type instance(args);
。それまでは、オブジェクトは生きており、他の機能に貸すことができます。これは通常、「参照渡し」を介して行われます。 Rustのような言語を使用すると、そのようなオブジェクトへのポインタがオブジェクトの寿命を超えていないことをコンパイラが静的にチェックできます。このメモリ管理スキームは完全に予測可能で、非常に効率的であり、複雑なオブジェクトなしでほとんどの場合に適しています残念ながら、Pythonはメモリ管理を考慮して設計されていません。理論的には、エスケープ分析を使用して、GCを回避できるケースを見つけることができます。実際には、foo().bar().baz()
は、存続期間の短い多数のオブジェクトをヒープに割り当てる必要があります(世代別GCは、この問題を小さく保つ1つの方法です)。
他の場合では、プログラマーはリストなどのオブジェクトの最終的なサイズをすでに知っている場合があります。残念ながら、Pythonは、新しいリストを作成するときにこれを通信する方法を提供していません。代わりに、新しい項目が最後にプッシュされ、複数の再割り当てが必要になる場合があります。いくつかのメモ:
特定のサイズのリストは、_fixed_size = [None] * size
_のように作成できます。ただし、そのリスト内のオブジェクトのメモリは、個別に割り当てる必要があります。 _std::array<Type, size> fixed_size
_を実行できるC++と比較してください。
特定のネイティブタイプのパックされた配列は、array
組み込みモジュールを介してPython=で作成できます。また、numpy
は、特定の形状を持つデータバッファーの効率的な表現を提供します。ネイティブ数値型。
Pythonは、パフォーマンスではなく使いやすさを考慮して設計されています。その設計により、非常に効率的な実装を作成することがかなり難しくなっています。プログラマーが問題のある機能を断念する場合、残りのイディオムを理解しているコンパイラーは、Cのパフォーマンスに匹敵する効率的なコードを生成できます。
すべての動的言語のパフォーマンスに影響を与える主な要因は3つあります。
C/C++の場合、これら3つの要素の相対コストはほぼゼロです。命令はプロセッサによって直接実行され、ディスパッチは最大で1つまたは2つの間接参照を使用します。特に断らない限り、ヒープメモリは割り当てられません。適切に記述されたコードは、アセンブリ言語に近づく可能性があります。
JITコンパイルを伴うC#/ Javaの場合、最初の2つは少ないですが、ガベージコレクションされたメモリにはコストがかかります。よく書かれたコードは2x C/C++に近づくかもしれません。
Python/Ruby/Perlの場合、これら3つの要素すべてのコストは比較的高くなります。 C/C++以下と比較して5倍考えてください。 (*)
ランタイムライブラリコードは、プログラムと同じ言語で記述されている場合があり、同じパフォーマンス制限があることに注意してください。
(*)Just-In_Time(JIT)コンパイルがこれらの言語に拡張されているため、適切に作成されたC/C++コードの速度に(通常2倍)近づきます。
(競合する言語間で)ギャップが狭くなると、違いはアルゴリズムと実装の詳細によって左右されることにも注意してください。 JITコードはC/C++に勝る場合があり、C/C++はアセンブリ言語に勝る場合があります。優れたコードを記述する方が簡単だからです。
はい。主な問題は、言語が動的であると定義されていることです。つまり、実行しようとするまで、何をしているのかを決して理解できません。マシンコードを生成する方法がわからないため、効率的なマシンコードを生成することは非常に困難ですfor。 JITコンパイラーはこの領域でsomeの機能を実行できますが、プログラムの実行に費やしていない時間とメモリであるため、JITコンパイラーは実行に時間とメモリを費やすことができないため、C++に匹敵することはありません。動的言語のセマンティクスを壊さずに達成できることには厳しい制限があります。
これが容認できないトレードオフであると主張するつもりはありません。しかし、Pythonの性質の基本は、実際の実装がC++実装ほど高速になることはないということです。
しかし、Pythonスクリプトが同等のC++プログラムと同じくらい高速になるのを妨げる技術的な制限や言語機能はありますか?
いいえ。C++を高速に実行するために注がれるお金とリソースと、Pythonを高速に実行するために注がれるお金とリソースの問題です。
たとえば、Self VMが登場したとき、それは最速の動的OO言語であっただけでなく、最速のOO言語期間でした。 信じられないほど動的言語(たとえば、Python、Ruby、PHPまたはJavaScriptよりはるかに多い)であるにもかかわらず、ほとんどの言語よりも高速でした利用可能なC++実装の.
しかし、SunはSelfプロジェクト(大規模システムを開発するための成熟した汎用OO言語)をキャンセルし、テレビセットトップボックスのアニメーションメニュー用の小さなスクリプト言語に焦点を当てました(ご存知かもしれませんが、Javaと呼ばれています)。 、これ以上の資金はありませんでした。同時に、Intel、IBM、Microsoft、Sun、Metrowerks、HPなど。莫大な金額とリソースを費やしてC++を高速化しました。 CPUメーカーは、C++を高速化するためにチップに機能を追加しました。オペレーティングシステムは、C++を高速にするために作成または変更されました。したがって、C++は高速です。
私はPythonにそれほど精通していません。私はRubyの方が好きなので、Rubyの例を挙げます。RubiniusでのHash
クラス(Pythonのdict
と同等の機能と重要性)Ruby実装は100%純粋なRubyで記述されています。それでも有利に競争し、時にはYARVのHash
クラスよりも優れており、手作業で最適化されたCで書かれています。そして、商用LISPまたはSmalltalkシステム(または前述のSelf VM)のいくつかと比較すると、Rubiniusのコンパイラはそれさえしていません賢い。
Pythonには、遅くなるような固有の要素はありません。今日のプロセッサとオペレーティングシステムには、Pythonを害する機能があります(たとえば、仮想メモリはガベージコレクションのパフォーマンスが悪いことが知られています)。 C++には役立ちますが、Pythonには役立たない機能があります(最新のCPUは非常に高価であるため、キャッシュミスを回避しようとします。残念ながら、OOとポリモーフィズムがある場合、キャッシュミスの回避は困難です。むしろ、キャッシュミスのコストを削減する必要があります。これは、Java用に設計されたAzul Vega CPUが行います。)
C++の場合と同様に、Pythonを高速にするために多くのお金、研究、リソースを費やし、Pythonプログラムを高速で実行するオペレーティングシステムを作るために同じくらいのお金、研究、リソースを費やす場合C++で行われたのと同じようにPythonプログラムを高速に実行するCPUを作成するために多くのお金、研究、およびリソースを費やし、Pythonが同等のC++へのパフォーマンス。
ECMAScriptでは、1人のプレーヤーだけがパフォーマンスに真剣に取り組むとどうなるかを確認しました。 1年以内に、基本的にすべての主要ベンダーのパフォーマンスが全体で10倍に向上しました。