web-dev-qa-db-ja.com

なぜPythonは単純なforループに対して非常に遅いのですか?

PythonでいくつかのkNNおよびSVD実装を作成しています。他の人はJavaを選びました。実行時間は非常に異なります。私はcProfileを使用して、どこでミスを犯したかを確認しましたが、実際はすべて fine です。はい、numpyも使用します。しかし、私は簡単な質問をしたいと思います。

total = 0.0
for i in range(9999): # xrange is slower according 
    for j in range(1, 9999):            #to my test but more memory-friendly.
        total += (i / j)
print total

このスニペットは、コンピューターで31.40秒かかります。

このコードのJavaバージョンは、同じコンピューター上で1秒以下かかります。型チェックはこのコードの主な問題だと思います。しかし、私のプロジェクトではこのような操作をたくさん行う必要があり、9999 * 9999はそれほど大きな数字ではないと思います。

Pythonが多くの科学プロジェクトで使用されていることを知っているので、間違いを犯していると思います。しかし、このコードが非常に遅いのはなぜですか。

PsycoなどのJITコンパイラを使用する必要がありますか?

編集

また、このループの問題は一例にすぎないと言います。コードはこのように単純ではなく、改善/コードサンプルを実践するのは難しいかもしれません。

別の質問は、正しく使用すれば、numpyおよびscipyを使用して多くのデータマイニングおよび機械学習アルゴリズムを実装できるかということです。

39
Baskaya

Pythonは多くの科学プロジェクトで使用されていることを知っているので、間違いを犯していると思います。

彼らはSciPyを頻繁に使用しています(NumPyは最も顕著なコンポーネントですが、NumPyのAPIを中心に開発されたエコシステムはさらに重要だと聞きました)vastlyこれらのプロジェクトに必要なすべての種類の操作を高速化します。あなたが間違っていることはあります:あなたはCでcriticalコードを書いていません。Pythonは一般的に開発するのに最適ですが、適切に配置された拡張モジュールは重要な最適化自体(少なくとも数値を計算する場合)。Pythonは、タイトな内部ループを実装するための本当にくだらない言語です。

デフォルトの(そして今のところ最も一般的で広くサポートされている)実装は、単純なバイトコードインタープリターです。整数除算のような最も単純な操作でも、数百(または整数の場合は単一)の代わりに、数百のCPUサイクル、複数のメモリアクセス(型チェックが一般的な例)、複数のC関数呼び出しなどを行うことができます。部門)命令。さらに、言語はオーバーヘッドを追加する多くの抽象化を使用して設計されています。 xrangeを使用する場合、ループはヒープに9999個のオブジェクトを割り当てます-range(キャッシュされる小さな整数の場合、9999 * 9999整数マイナス256 * 256を使用する場合)また、xrangeバージョンは、各反復でメソッドを呼び出して進みます-シーケンスの反復が特に最適化されていない場合、rangeバージョンも呼び出します。それでも、バイトコードのディスパッチ全体が必要であり、それ自体は非常に複雑です(もちろん整数除算と比較して)。

JIT(PsyPよりもPyPyをお勧めします。Psycoはもはや積極的に開発されておらず、範囲が非常に限られています-この単純な例ではうまくいくかもしれません)。ほんの少しの反復の後、いくつかのガード(単純な整数の比較、失敗した場合はジャンプ)で強化された最適なマシンコードループを生成し、そのリストに文字列がある場合に正確性を維持する必要があります。 Javaは同じことを、より早く(最初にトレースする必要はありません)、少ないガードで(少なくともintsを使用する場合)行うことができます。それが理由です。はるかに高速。

32
user395760

科学コードについて言及しているので、numpyを見てください。あなたがしていることはおそらくすでに行われているでしょう(むしろ、SVDのようなものにLAPACKを使用しています)。 pythonが科学的コードに使用されていることを聞いたとき、人々はあなたの例のようにそれを使用することをおそらく言及していないでしょう。

簡単な例として:

(python3を使用している場合、例では浮動小数点除算を使用します。私の例では、python2.x、したがって整数除算を使用していると想定しています。そうでない場合は、i = np.arange(9999, dtype=np.float)などを指定します)

import numpy as np
i = np.arange(9999)
j = np.arange(1, 9999)
print np.divide.outer(i,j).sum()

タイミングのアイデアを与えるために...(ここでは、例の整数除算の代わりに、浮動小数点除算を使用します):

import numpy as np

def f1(num):
    total = 0.0
    for i in range(num): 
        for j in range(1, num):
            total += (float(i) / j)
    return total

def f2(num):
    i = np.arange(num, dtype=np.float)
    j = np.arange(1, num, dtype=np.float)
    return np.divide.outer(i, j).sum()

def f3(num):
    """Less memory-hungry (and faster) version of f2."""
    total = 0.0
    j = np.arange(1, num, dtype=np.float)
    for i in xrange(num):
        total += (i / j).sum()
    return total

タイミングを比較する場合:

In [30]: %timeit f1(9999)
1 loops, best of 3: 27.2 s per loop

In [31]: %timeit f2(9999)
1 loops, best of 3: 1.46 s per loop

In [32]: %timeit f3(9999)
1 loops, best of 3: 915 ms per loop
14
Joe Kington

Python=)は、Java(このリフレクションメカニズムのみがある場合)と比較して、はるかに柔軟性が高いことです(たとえば、クラスはオブジェクトです)

ここで言及されていないのは Cython です。型付き変数を導入し、サンプルをC/C++にトランスコンパイルできます。その後、はるかに高速です。ループ内の境界も変更しました...

from __future__ import division

cdef double total = 0.00
cdef int i, j
for i in range(9999):
    for j in range(1, 10000+i):
        total += (i / j)

from time import time
t = time()
print("total = %d" % total)
print("time = %f[s]" % (time() - t))

に続く

$ cython loops.pyx
$ gcc -I/usr/include/python2.7 -shared -pthread -fPIC -fwrapv -Wall -fno-strict-aliasing -O3 -o loops.so loops.c
$ python -c "import loops"

与える

total = 514219068
time = 0.000047[s]
5
Harald Schilly

リストの内包表記またはジェネレーター式が非常に高速であることがわかります。例えば:

total = sum(i / j for j in xrange(1, 9999) for i in xrange(9999))

これは、マシン上で約11秒で実行されますが、元のコードでは約26秒です。まだJavaよりも1桁遅いですが、それはあなたが期待するものとほぼ一致しています。

ところで、浮動小数点加算ではなく整数を使用するために、0ではなくtotal0.0に初期化することにより、元のコードを少し高速化できます。すべての部門には整数の結果があるため、結果を浮動小数点数に合計しても意味がありません。

私のマシンでは、Psycoは実際にslows downジェネレーター式を元のループとほぼ同じ速度にします(まったく加速しません)。

4
kindall

これは既知の現象です。pythonコードは動的で解釈されます。Javaコードは静的に型付けされコンパイルされます。驚くことはありません。

人々がpythonを好む理由は次のとおりです。

  • より小さなコードベース
  • 冗長性が少ない(乾燥が多い)
  • クリーナーコード

ただし、Cで記述されたライブラリ(Pythonから)を使用すると、パフォーマンスが大幅に向上する可能性があります(picklecpickleを比較)。

4
Matt Fenwick

Kindallのリスト内包表記を使用する

total = sum(i / j for j in xrange(1, 9999) for i in xrange(9999))

は10.2秒で、pypy 1.7を使用すると2.5秒になります。 pypyは元のバージョンを2.5秒にも高速化するので面白いです。したがって、pypyリストの内包表記は最適化が時期尚早です;)。よくやった!

4
Pawel

Python forループは静的に型付けされ、解釈されます。コンパイルされません。 Javaは、Pythonにはない追加のJITアクセラレーション機能があるため、高速です。

http://en.wikipedia.org/wiki/Just-in-time_compilation

違いがどれだけ大きいかを説明するために、Java JITは約5分かかるプログラムpythonプログラムを見てください:

if __=='__main__':
    total = 0.0
    i=1
    while i<=9999:
        j=1
        while j<=9999:
            total=1
            j+=1
        i+=1
    print total

この基本的に同等のJavaプログラムは約23ミリ秒かかります:

public class Main{
    public static void main(String args[]){
        float total = 0f; 

        long start_time = System.nanoTime();
        int i=1;

        while (i<=9999){
            int j=1;
            while(j<=9999){
                total+=1;
                j+=1;
            }
            i+=1;
        }
        long end_time = System.nanoTime();

        System.out.println("total: " + total);
        System.out.println("total milliseconds: " + 
           (end_time - start_time)/1000000);
    }
}

Forループで何かを行うという点では、Javaは1から1000桁高速になることでpythonのクロックをきれいにします。

ストーリーの教訓:基本的なpython forループは、高速なパフォーマンスが必要な場合は、すべてのコストで回避する必要があります。これは、Guido van Rossumが配列のようなJavaよりも高速に動作するスプライシング。

3
spiderman

pythonで科学的な計算を行うことは、C/C++で書かれた計算ソフトウェアを最も重要な部分で使用することを意味します。python (これには多くのpythonコードも含まれています)。

これは役に立つと思う: http://blog.dhananjaynene.com/2008/07/performance-comparison-c-Java-python-Ruby-jython-jruby-groovy/

ご覧のとおり、psyco/PyPyは一定の改善をもたらすことができますが、それでもC++やJavaよりもはるかに遅いでしょう。

0
Krystian

推奨が行われたかどうかはわかりませんが、forループをリスト内包表記に置き換えるのが好きです。より速く、よりクリーンで、よりPythonicです。

http://www.pythonforbeginners.com/basics/list-comprehensions-in-python

0
Jesse Watson