5

私の Python プログラムは遅すぎました。そのため、プロファイルを作成したところ、ほとんどの時間が 2 つのポイント間の距離を計算する関数に費やされていることがわかりました(ポイントは 3 つの Python float のリストです)。

def get_dist(pt0, pt1):
    val = 0
    for i in range(3):
        val += (pt0[i] - pt1[i]) ** 2
    val = math.sqrt(val)
    return val

この関数が非常に遅い理由を分析するために、2 つのテスト プログラムを作成しました。1 つは Python で、もう 1 つは C++ で同様の計算を行います。100 万対のポイント間の距離を計算します。(Python と C++ でのテスト コードは次のとおりです。)

Python の計算には 2 秒かかりますが、C++ には 0.02 秒かかります。100倍の差!

このような単純な数学計算で、Python コードが C++ コードよりもはるかに遅いのはなぜですか? C++ のパフォーマンスに匹敵するように高速化するにはどうすればよいですか?

テストに使用した Python コード:

import math, random, time

num = 1000000

# Generate random points and numbers

pt_list = []
rand_list = []

for i in range(num):
    pt = []
    for j in range(3):
        pt.append(random.random())
    pt_list.append(pt)
    rand_list.append(random.randint(0, num - 1))

# Compute

beg_time = time.clock()
dist = 0

for i in range(num):
    pt0 = pt_list[i]
    ri  = rand_list[i]
    pt1 = pt_list[ri]

    val = 0
    for j in range(3):
        val += (pt0[j] - pt1[j]) ** 2
    val = math.sqrt(val)

    dist += val

end_time = time.clock()
elap_time = (end_time - beg_time)

print elap_time
print dist

テストに使用した C++ コード:

#include <cstdlib>
#include <iostream>
#include <ctime>
#include <cmath>

struct Point
{
    double v[3];
};

int num = 1000000;

int main()
{
    // Allocate memory
    Point** pt_list = new Point*[num];
    int* rand_list = new int[num];

    // Generate random points and numbers
    for ( int i = 0; i < num; ++i )
    {
        Point* pt = new Point;

        for ( int j = 0; j < 3; ++j )
        {
            const double r = (double) rand() / (double) RAND_MAX;
            pt->v[j] = r;
        }

        pt_list[i] = pt;
        rand_list[i] = rand() % num;
    }

    // Compute

    clock_t beg_time = clock();
    double dist = 0;
    for ( int i = 0; i < num; ++i )
    {
        const Point* pt0 = pt_list[i];
        int r = rand_list[i];
        const Point* pt1 = pt_list[r];

        double val = 0;
        for ( int j = 0; j < 3; ++j )
        {
            const double d = pt0->v[j] - pt1->v[j];
            val += ( d * d );
        }

        val = sqrt(val);
        dist += val;
    }
    clock_t end_time = clock();
    double sec_time = (end_time - beg_time) / (double) CLOCKS_PER_SEC;

    std::cout << sec_time << std::endl;
    std::cout << dist << std::endl;

    return 0;
}
4

5 に答える 5

6

一連の最適化:

小さな変更を加えた元のコード

import math, random, time

num = 1000000

# Generate random points and numbers

# Change #1: Sometimes it's good not to have too much randomness.
# This is one of those cases.
# Changing the code shouldn't change the results.
# Using a fixed seed ensures that the changes are valid.
# The final 'print dist' should yield the same result regardless of optimizations.
# Note: There's nothing magical about this seed.
# I randomly picked a hash tag from a git log.
random.seed (0x7126434a2ea2a259e9f4196cbb343b1e6d4c2fc8)
pt_list = []
rand_list = []

for i in range(num):
    pt = []
    for j in range(3):
        pt.append(random.random())
    pt_list.append(pt)

# Change #2: rand_list is computed in a separate loop.
# This ensures that upcoming optimizations will get the same results as
# this unoptimized version.
for i in range(num):
    rand_list.append(random.randint(0, num - 1))

# Compute

beg_time = time.clock()
dist = 0

for i in range(num):
    pt0 = pt_list[i]
    ri  = rand_list[i]
    pt1 = pt_list[ri]

    val = 0
    for j in range(3):
        val += (pt0[j] - pt1[j]) ** 2
    val = math.sqrt(val)

    dist += val

end_time = time.clock()
elap_time = (end_time - beg_time)

print elap_time
print dist


最適化 #1: コードを関数に入れます。

最初の最適化 (表示されていません) は、 を除くすべてのコードをimport関数に埋め込むことです。この単純な変更により、コンピューターのパフォーマンスが 36% 向上しました。


最適化 #2:**オペレーターを避ける。

これはCでは最適ではないことを誰もが知っているため、Cコードでは使用しませんpow(d,2)。Pythonでも最適ではありません。Python**はスマートです。として評価さx**2x*xます。ただし、賢くなるには時間がかかります。あなたはあなたが欲しいと知っているのでd*d、それを使ってください。その最適化を使用した計算ループは次のとおりです。

for i in range(num):
    pt0 = pt_list[i]
    ri  = rand_list[i]
    pt1 = pt_list[ri]

    val = 0 
    for j in range(3):
        d = pt0[j] - pt1[j]
        val += d*d 
    val = math.sqrt(val)

    dist += val 


最適化 #3: Pythonic であること。

Python コードは、C コードとよく似ています。あなたは言語を利用していません。

import math, random, time, itertools

def main (num=1000000) :
    # This small optimization speeds things up by a couple percent.
    sqrt = math.sqrt

    # Generate random points and numbers

    random.seed (0x7126434a2ea2a259e9f4196cbb343b1e6d4c2fc8)

    def random_point () :
        return [random.random(), random.random(), random.random()]

    def random_index () :
       return random.randint(0, num-1)

    # Big optimization:
    # Don't generate the lists of points.
    # Instead use list comprehensions that create iterators.
    # It's best to avoid creating lists of millions of entities when you don't
    # need those lists. You don't need the lists; you just need the iterators.
    pt_list = [random_point() for i in xrange(num)]
    rand_pts = [pt_list[random_index()] for i in xrange(num)]


    # Compute

    beg_time = time.clock()
    dist = 0 

    # Don't loop over a range. That's too C-like.
    # Instead loop over some iterable, preferably one that doesn't create the
    # collection over which the iteration is to occur.
    # This is particularly important when the collection is large.
    for (pt0, pt1) in itertools.izip (pt_list, rand_pts) :

        # Small optimization: inner loop inlined,
        # intermediate variable 'val' eliminated.
        d0 = pt0[0]-pt1[0]
        d1 = pt0[1]-pt1[1]
        d2 = pt0[2]-pt1[2]

        dist += sqrt(d0*d0 + d1*d1 + d2*d2)

    end_time = time.clock()
    elap_time = (end_time - beg_time)

    print elap_time
    print dist


アップデート

最適化 #4、numpy を使用する

以下は、コードの timed セクションで元のバージョンの約 1/40 の時間を要します。Cほど速くはありませんが、近いです。

コメントアウトされた「Mondo slow」計算に注意してください。これには、元のバージョンの約 10 倍の時間がかかります。numpy を使用するとオーバーヘッド コストが発生します。numpy でない最適化 #3 と比較して、次のコードではセットアップにかなり長い時間がかかります。

結論: numpy を使用するときは注意が必要であり、セットアップ コストがかなりの額になる可能性があります。

import numpy, random, time

def main (num=1000000) :

    # Generate random points and numbers

    random.seed (0x7126434a2ea2a259e9f4196cbb343b1e6d4c2fc8)

    def random_point () :
        return [random.random(), random.random(), random.random()]

    def random_index () :
       return random.randint(0, num-1)

    pt_list = numpy.array([random_point() for i in xrange(num)])
    rand_pts = pt_list[[random_index() for i in xrange(num)],:]

    # Compute

    beg_time = time.clock()

    # Mondo slow.
    # dist = numpy.sum (
    #            numpy.apply_along_axis (
    #                numpy.linalg.norm, 1, pt_list - rand_pts))

    # Mondo fast.
    dist = numpy.sum ((numpy.sum ((pt_list-rand_pts)**2, axis=1))**0.5)

    end_time = time.clock()
    elap_time = (end_time - beg_time)

    print elap_time
    print dist
于 2013-04-25T14:21:02.433 に答える
4

一般的なヒント:

すべてのコードを main() 関数に移動し、通常の

if __name__ == "__main__":
    main()

構築します。可変スコープにより、速度が大幅に向上します。Python コードが関数内でより速く実行されるのはなぜですか?を参照してください。理由の説明のために。

range()一度に完全な範囲を生成するため、使用しないでください。大きな数の場合は遅くなります。代わりにxrange()、ジェネレーターを使用する which を使用します。

于 2013-04-25T09:09:57.263 に答える
3

Python は高速な言語ではなく、「コンピューター コード」を生成せず、Python 仮想マシンで実行されます。「すべて」はオブジェクトなので、C のような静的型付けはありません。これだけで、処理が大幅に遅くなります。- とにかく、それは私の分野ではないので、あまり話さない.

PyPy、Cython、さらには C で Python 拡張機能を作成することを検討する必要があります。

私は PyPy でコードを実行しました。使用された時間は 250 ミリ秒でした <-- それはあなたが探しているものですか? Cython の簡単なテストを作成し、500ms まで下げることができました。

したがって、速度が本当に重要な場合は、PyPy または Cython を使用するのが最善の策です。

于 2013-04-25T09:21:40.780 に答える
2

Python で C++ のパフォーマンスに匹敵することは期待できませんが、Python コードを少し調整して高速化することはできます。

def get_dist(pt0, pt1):
    val = 0
    for i in range(3):
        val += (pt0[i] - pt1[i]) ** 2
    val = math.sqrt(val)
    return val

このコードのforループ バージョンと C++forループは完全に異なります。Python バージョンはリストを作成し、それを反復処理しますが、C++ バージョンは単に変数をインクリメントします。forPython バージョンをスピードアップしたい場合、それを行う最善の方法は、Pythonループのオーバーヘッドを節約するために明示的に書き出すことです。

def get_dist(pt0, pt1, sqrt=math.sqrt): # cache function at definition time
    return sqrt((pt0[0] - pt1[0]) ** 2 + (pt0[1] - pt1[1]) ** 2 + (pt0[2] - pt1[2]) ** 2)

そして、それはおそらくその特定の関数に対して(を使用せずに)取得できるのと同じくらい高速ですnumpy。メインコードでも改善できる点が他にもあります。

于 2013-04-25T09:11:24.543 に答える
0

このページは非常に混乱しており、ほとんどの回答は実際にはコメントに記載されているため、考えられる最適化の概要を以下に示します。

  • Jamlak の答え: Python コードを最適化します。

    def get_dist(pt0, pt1, sqrt=math.sqrt):  # cache function at definition time
        return sqrt((pt0[0] - pt1[0]) ** 2 + (pt0[1] - pt1[1]) ** 2 + (pt0[2] - pt1[2]) ** 2) 
    
  • 計算にはnumpyモジュールを使用します

  • CPython の代わりにpypyでコードを実行する
  • Cythonを使用してタイム クリティカルなコードに準拠する
于 2013-04-25T10:00:56.203 に答える