2010-12-29 30 views
3

最後に、Pythonでの単純な並べ替えのジェネレータを書きました(Knuthが "The Art ... 4"で説明した "plain changes"アルゴリズムの実装)。 python2とpython3の実行時間の違いが不思議でした。 python3バージョンでpython2とpython3の実行時間が若干異なる

def perms(s): 
    s = tuple(s) 
    N = len(s) 
    if N <= 1: 
     yield s[:] 
     raise StopIteration() 
    for x in perms(s[1:]): 
     for i in range(0,N): 
     yield x[:i] + (s[0],) + x[i:] 

Iプリントとして(X)を印刷するだけで変更された印刷xはPY3内の関数は次のとおりです。 はここに私の関数です。 両方ともtimeitモジュールを使ってテストしました。

私のテスト:

$ echo "python2.6:" && ./testing.py && echo "python3:" && ./testing3.py 

はのpython2.6:

args time[ms] 
1 0.003811 
2 0.008268 
3 0.015907 
4 0.042646 
5 0.166755 
6 0.908796 
7 6.117996 
8 48.346996 
9 433.928967 
10 4379.904032 

のpython3:

args time[ms] 
1 0.00246778964996 
2 0.00656183719635 
3 0.01419159912 
4 0.0406293644678 
5 0.165960511097 
6 0.923101452814 
7 6.24257639835 
8 53.0099868774 
9 454.540967941 
10 4585.83498001 

あなたは、Python 3は、6未満の引数の数を、見ることができるようにより早いですが、役割が逆転し、python2.6が良くなります。 私はPythonプログラミングの初心者ですから、なぜそういうのでしょうか?あるいは、私のスクリプトはPython2のためにより最適化されていますか?

は一種の答えを事前にいただきありがとうございます:)

+0

適切な書式設定を使用してください。 –

+0

まず、 '{}'ボタンを使ってコードを書式設定します。それがなぜ重要なのか説明してください。 –

+0

これはおそらくそれが本当に重要であるということではなく、単に好奇心です。 – ecik

答えて

2

これは実際には非常に興味深い質問です。

私は、Python 2.6,2.7,3.0,3.1、および3.2で動作する以下のスクリプトを使用しました。

from __future__ import print_function 
from timeit import Timer 
from math import factorial 

try: 
    range = xrange 
except: 
    pass 

def perms(s): 
    s = tuple(s) 
    N = len(s) 
    if N <= 1: 
     yield s[:] 
     raise StopIteration() 
    for x in perms(s[1:]): 
     for i in range(0,N): 
      yield x[:i] + (s[0],) + x[i:] 

def testcase(s): 
    for x in perms(s): 
     pass 

def test(): 
    for i in range(1,11): 
     s = "".join(["%d" % x for x in range(i)]) 
     s = "testcase(\"%s\")" % s 
     t = Timer(s,"from __main__ import testcase") 
     factor = 100000 
     factor = int(factor/factorial(i)) 
     factor = (factor>0) and factor or 1 
     yield (i,(1000*min(t.repeat(5,factor))/factor)) 

if __name__=="__main__": 
    print("args\ttime[ms]") 
    for x in test(): 
     print("%i\t%f" % x) 

プラットフォームはUbuntu 10.10,64ビットで、すべてのバージョンのPythonはソースからコンパイルされています。いくつかのより多くの実験の後、私はフラグメントにパフォーマンスの違いに追跡

[email protected]:~$ py27 perms.py 
args time[ms] 
1 0.002221 
2 0.005072 
3 0.010352 
4 0.027648 
5 0.111339 
6 0.618658 
7 4.207046 
8 33.213019 
9 294.044971 
10 2976.780891 

[email protected]:~$ py32 perms.py 
args time[ms] 
1 0.001725 
2 0.004997 
3 0.011208 
4 0.032815 
5 0.139474 
6 0.761153 
7 5.068729 
8 39.760470 
9 356.358051 
10 3566.874027 

::私はちょうどループの先頭で1組を計算し、すべてのyield文のためにそれを返す場合x[:i] + (s[0],) + x[i:]を、両方の私は、次のような結果を得ますPythonのバージョンは同じ速度で動作します。 (そして、順列は間違っていますが、それはポイントではありません)。

私はそれだけで断片を時間をとるとかなり遅くなります。

[email protected]:~$ py27 -m timeit -s "s=(1,2,3,4,5);x=(1,2,3,4,5,6,7,8)" "x[:3] + (s[0],) + x[3:]" 
1000000 loops, best of 3: 0.549 usec per loop 
[email protected]:~$ py32 -m timeit -s "s=(1,2,3,4,5);x=(1,2,3,4,5,6,7,8)" "x[:3] + (s[0],) + x[3:]" 
1000000 loops, best of 3: 0.687 usec per loop 

次に、両方のバージョンで生成されたバイトコードを見るためにdis.dis()を使用しました。

[email protected]:~/src/Python-3.0.1$ py32 
Python 3.2b2 (r32b2:87398, Dec 21 2010, 21:39:59) 
[GCC 4.4.5] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import dis 
>>> s=(1,2,3,4,5) 
>>> x=(1,2,3,4,5,6,7,8) 
>>> def f(s,x): 
... return x[:3] + (s[0],) + x[3:] 
... 
>>> dis.dis(f) 
    2   0 LOAD_FAST    1 (x) 
       3 LOAD_CONST    0 (None) 
       6 LOAD_CONST    1 (3) 
       9 BUILD_SLICE    2 
      12 BINARY_SUBSCR   
      13 LOAD_FAST    0 (s) 
      16 LOAD_CONST    2 (0) 
      19 BINARY_SUBSCR   
      20 BUILD_TUPLE    1 
      23 BINARY_ADD   
      24 LOAD_FAST    1 (x) 
      27 LOAD_CONST    1 (3) 
      30 LOAD_CONST    0 (None) 
      33 BUILD_SLICE    2 
      36 BINARY_SUBSCR   
      37 BINARY_ADD   
      38 RETURN_VALUE   
>>> exit() 
[email protected]:~/src/Python-3.0.1$ py26 
Python 2.6.6 (r266:84292, Oct 24 2010, 15:27:46) 
[GCC 4.4.5] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import dis 
>>> s=(1,2,3,4,5) 
>>> x=(1,2,3,4,5,6,7,8) 
>>> def f(s,x): 
... return x[:3] + (s[0],) + x[3:] 
... 
>>> dis.dis(f) 
    2   0 LOAD_FAST    1 (x) 
       3 LOAD_CONST    1 (3) 
       6 SLICE+2    
       7 LOAD_FAST    0 (s) 
      10 LOAD_CONST    2 (0) 
      13 BINARY_SUBSCR  
      14 BUILD_TUPLE    1 
      17 BINARY_ADD   
      18 LOAD_FAST    1 (x) 
      21 LOAD_CONST    1 (3) 
      24 SLICE+1    
      25 BINARY_ADD   
      26 RETURN_VALUE   
>>> 

生成されるバイトコードは、2つのバージョンで大きく異なります。残念ながら、私はなぜバイトコードが異なるのか分からないので、私は本当に質問に答えていません。しかし、タプルをスライスしたり構築したりする際には、パフォーマンスに大きな違いがあります。

+0

なぜそれらの順列が間違っていると言っていますか?私は何が間違っているのか分からない。 –

+0

@kosa:タプルを作成するための実行時間を削除するには、正しい長さのタプルを1つ作成し、範囲(0、N)のすべての反復で同じタプルを返しました。その特定のテストで返された実際の順列は正しくありませんでしたが、実行時間はPython 2と3で同じでした。perms()の実行時間の3分の2近くはフラグメントです:x [:i] +(s [ 0]、)+ x [i:]である。その断片はPython 3では25%遅く、実行時間の65%を占めているため、実行時間は約17%遅く、テストとほぼ同じです。 – casevh

+0

ああ、今私は参照してください。まあ、これは私が望む答えです。ありがとうございました! –

0

私は確かに、統計的に意味のないこれらの数字を呼び出します。

実際に意味を持つバリエーションのために、仕事には多すぎる要素があります。

4

Quoting:3.0 一般化の

正味の結果は、Python 3.0 は、Python 2.5よりも約10%遅い pystoneベンチマークを実行することです。ほとんどの場合、 の最大の原因は、小さな整数用の特殊ケーシング の削除です。 改善の余地がありますが、 は3.0がリリースされた後に発生します。

>>> 4585.83498001/4379.904032 
1.0470172283468882 

だから、5%の速度低下に関する見ています。引用されたテキストは、10%の減速を予想しています。だから私は合理的な減速としてそれを受け入れるだろう。

しかし、herehereのように改善されています。したがって、5%の減速が懸念される場合は、3.1または3.2を試してみてください。

+0

@ΤΖωΤΖΙΟΥ私はそれを言っていませんでした。 3.0リリースノートの引用です。そして、3.0のリリースの後に改良が行われました:私の最後の段落で述べた3.1と3.2で。 – marcog

+0

あなたは正しいです。私は非常にコントラストの低い液晶画面(古い:read)でラップトップであなたの返信を見たので、見積もりの​​背景は目に見えるほどではありませんでした。 – tzot

関連する問題