36

私はいくつかの Python コードを最適化し、次の実験を試みました。

import time

start = time.clock()
x = 0
for i in range(10000000):
    x += 1
end = time.clock()

print '+=',end-start

start = time.clock()
x = 0
for i in range(10000000):
    x -= -1
end = time.clock()

print '-=',end-start

2 番目のループは、実行するシステムに応じて、ひげから 10% まで確実に高速です。ループの順序、実行回数などを変えてみましたが、それでもうまくいくようです。

知らない人、

for i in range(10000000, 0, -1):

(つまり、ループを逆方向に実行する) は、

for i in range(10000000):

ループの内容が同じであっても。

ここでより一般的なプログラミングのレッスンはありますか?

4

10 に答える 10

84

Q6600 (Python 2.6.2) でこれを再現できます。範囲を 100000000 に増やします。

('+=', 11.370000000000001)
('-=', 10.769999999999998)

まず、いくつかの観察:

  • これは、簡単な操作では 5% です。それは重要です。
  • ネイティブの加算および減算オペコードの速度は関係ありません。それはノイズフロアにあり、バイトコード評価によって完全に小さくなっています。これは、数千程度の 1 つまたは 2 つのネイティブ命令について話しています。
  • バイトコードはまったく同じ数の命令を生成します。唯一の違いはINPLACE_ADDvs.INPLACE_SUBTRACTと +1 vs -1 です。

Python のソースを見ると、推測できます。これは、 の ceval.c で処理されPyEval_EvalFrameExます。 INPLACE_ADD文字列の連結を処理するために、コードの重要な追加ブロックがあります。INPLACE_SUBTRACT文字列を減算できないため、そのブロックは には存在しません。つまりINPLACE_ADD、より多くのネイティブ コードが含まれています。コンパイラによるコードの生成方法によっては (かなり!)、この余分なコードが残りの INPLACE_ADD コードとインライン化される場合があります。これは、加算が減算よりも命令キャッシュにヒットする可能性が高いことを意味します。これにより、余分な L2 キャッシュ ヒット発生し、パフォーマンスに大きな違いが生じる可能性があります。

これは、使用しているシステム (プロセッサーが異なれば、キャッシュとキャッシュ アーキテクチャの量も異なります)、特定のバージョンやコンパイル オプションを含む使用中のコンパイラー (コンパイラーが異なれば、重要なコードのビットを異なる方法で決定します) にも大きく依存します。アセンブリ コードをまとめる方法を決定するパス) など。

また、Python 3.0.1 (+: 15.66, -: 16.71) では違いが逆転しています。この重要な機能が大きく変化したことは間違いありません。

于 2009-09-08T22:47:51.943 に答える
13
$ python -m timeit -s "x=0" "x+=1"
10000000 loops, best of 3: 0.151 usec per loop
$ python -m timeit -s "x=0" "x-=-1"
10000000 loops, best of 3: 0.154 usec per loop

測定バイアスがあるようです

于 2009-09-08T22:09:23.127 に答える
7

「一般的なプログラミングの教訓」は、ソースコードを見るだけでは、どのステートメントシーケンスが最速になるかを予測するのは非常に難しいということだと思います。あらゆるレベルのプログラマーは、この種の「直感的な」最適化にしばしば巻き込まれます。あなたが知っていると思っていることが、必ずしも真実であるとは限りません。

プログラムのパフォーマンスを実際に測定する以外に方法はありません。そうしてくれてありがとう。この場合、間違いなくPythonの実装を深く掘り下げる必要がある理由に答える.

Java、Python、.NET などのバイト コンパイル言語では、1 台のマシンでパフォーマンスを測定するだけでは不十分です。VM のバージョン間の違い、ネイティブ コード変換の実装、CPU 固有の最適化などにより、この種の質問への回答はますます難しくなります。

于 2009-09-08T22:27:52.203 に答える
5

「2 番目のループの方が確実に高速です...」

それがあなたの説明です。スクリプトの順序を変更して、減算テストが最初に実行され、次に加算が実行され、突然加算が再び高速な操作になるようにします。

-= 3.05
+= 2.84

明らかに、スクリプトの後半で何かが発生して、スクリプトが高速化されます。私の推測では、Python はこのような長いリストに十分なメモリを割り当てる必要があるため、への最初の呼び出しrange()は遅くなりますが、への 2 回目の呼び出しにはそのメモリを再利用できますrange()

import time
start = time.clock()
x = range(10000000)
end = time.clock()
del x
print 'first range()',end-start
start = time.clock()
x = range(10000000)
end = time.clock()
print 'second range()',end-start

このスクリプトを数回実行すると、最初に必要な余分な時間が、上記range()の「+=」と「-=」の間の時間差のほぼすべてを占めていることがわかります。

first range() 0.4
second range() 0.23
于 2009-09-09T00:55:59.800 に答える
4

質問をするときは、使用しているプラ​​ットフォームと Python のバージョンを言うことをお勧めします。時々それは問題ではありません。これはそれらの時間の 1 つではありません。

  1. time.clock()Windows でのみ適切です。独自の測定コードを捨てて-m timeit、pixelbeat の回答に示されているように使用してください。

  2. Python 2.Xrange()はリストを作成します。Python 2.x を使用している場合は、に置き換えrangexrange、何が起こるかを確認してください。

  3. Python 3.Xintは Python2.Xlongです。

于 2009-09-08T22:46:51.073 に答える
2
ここにもっと一般的なプログラミングのレッスンはありますか?

ここでのより一般的なプログラミングの教訓は、コンピューターコードの実行時のパフォーマンスを予測する場合、直感は不十分なガイドであるということです。

アルゴリズムの複雑さ、コンパイラの最適化に関する仮説、キャッシュパフォーマンスの見積もりなどについて推論することができます。ただし、これらは重要な方法で相互作用する可能性があるため、特定のコードがどれだけ速くなるかを確認する唯一の方法は、ターゲット環境でそれをベンチマークすることです(正しく行ったように)。

于 2010-11-02T13:46:31.030 に答える
0

それは注目に値するので、私はあなたのコードを徹底的に評価し、より正しいことがわかるように実験をセットアップしました(すべての宣言とループ外の関数呼び出し)。どちらのバージョンも 5 回実行しました。

  • コードを実行すると、主張が検証されました: -= 常に時間がかかりません。平均3.6%
  • ただし、私のコードを実行すると、実験の結果と矛盾します: += は平均で (常にではありません) 0.5% 少ない時間がかかります。

すべての結果を表示するために、プロットをオンラインにしました。

したがって、あなたの実験には偏りがあり、それは有意であると結論付けます。

最後に、私のコードは次のとおりです。

import time

addtimes = [0.] * 100
subtracttimes = [0.] * 100

range100 = range(100)
range10000000 = range(10000000)

j = 0
i = 0
x = 0
start = 0.


for j in range100:
 start = time.clock()
 x = 0
 for i in range10000000:
  x += 1
 addtimes[j] = time.clock() - start

for j in range100:
 start = time.clock()
 x = 0
 for i in range10000000:
  x -= -1
 subtracttimes[j] = time.clock() - start

print '+=', sum(addtimes)
print '-=', sum(subtracttimes)
于 2009-09-09T13:26:04.900 に答える
0

Python 2.5 での最大の問題は、範囲を使用することです。これは、それを反復処理するために大きなリストを割り当てます。xrange を使用する場合、2 番目に行う方が少し速くなります。(Python 3 で range がジェネレーターになったかどうかは不明です。)

于 2009-09-08T22:39:06.813 に答える
0

あなたの実験は間違っています。この実験を設計する方法は、2 つの異なるプログラムを作成することです。1 つは足し算用、もう 1 つは引き算用です。それらはまったく同じであり、ファイルに入れられるデータと同じ条件下で実行される必要があります。次に、実行を平均化する必要があります (少なくとも数千回) が、統計学者に適切な数値を教えてもらう必要があります。

足し算、引き算、ループのさまざまな方法を分析したい場合は、それぞれ別のプログラムにする必要があります。

実験的なエラーは、プロセッサの熱や CPU で発生するその他のアクティビティから発生する可能性があるため、さまざまなパターンで実行を実行します...

于 2009-09-09T00:37:51.963 に答える
-1

数値が 0 に等しいかどうかをコンピューターが簡単に比較できるため、ループを逆方向に実行すると高速になります。

于 2009-09-08T22:12:52.593 に答える