web-dev-qa-db-ja.com

リスト内包表記と機能関数は、「forループ」よりも高速ですか?

Pythonのパフォーマンスの観点では、リストの理解、またはmap()、filter()、reduce()などの関数はforループよりも高速ですか?技術的には、「forループはpython仮想マシンの速度で実行される」のに対して、技術的には「Cの速度で実行される」のはなぜですか。

開発中のゲームで、forループを使用して複雑で巨大なマップを描画する必要があるとします。この質問は間違いなく関連性があります。たとえば、リストの理解が実際に速い場合、ラグを避けるためのはるかに優れたオプションです(コードの視覚的な複雑さにもかかわらず)。

119

以下は、大まかなガイドラインと経験に基づいた経験に基づいた推測です。 timeitまたは具体的なユースケースのプロファイルを作成してハード番号を取得する必要がありますが、これらの番号は以下と一致しない場合があります。

リストの内包表記は通常、正確に同等のforループ(実際にリストを作成する)よりも少し高速です。これは、ほとんどの場合、リストとそのappendメソッドを調べる必要がないため繰り返し。ただし、リストの内包表記は引き続きバイトコードレベルのループを実行します。

>>> dis.dis(<the code object for `[x for x in range(10)]`>)
 1           0 BUILD_LIST               0
             3 LOAD_FAST                0 (.0)
       >>    6 FOR_ITER                12 (to 21)
             9 STORE_FAST               1 (x)
            12 LOAD_FAST                1 (x)
            15 LIST_APPEND              2
            18 JUMP_ABSOLUTE            6
       >>   21 RETURN_VALUE

does n'tがリストを構築し、無意味に無意味な値のリストを蓄積し、その後リストを捨てるループの代わりにリスト内包表記を使用する多くの場合、リストの作成と拡張のオーバーヘッドのため、slower。リストの内包表記は、古き良きループよりも本質的に速い魔法ではありません。

機能リスト処理関数に関して:これらはCで書かれており、おそらくPythonで書かれた同等の関数よりも優れていますが、notは必ずしも最速のオプションです。ある程度の高速化が期待されますif関数もCで書かれています。しかし、ほとんどの場合、lambda(または他のPython関数)を使用すると、Pythonスタックフレームなどを繰り返しセットアップするオーバーヘッドが節約になります。関数呼び出しを行わずに(たとえば、mapまたはfilterの代わりにリストを理解する)インラインで同じ作業を行うと、多くの場合わずかに速くなります。

開発中のゲームで、forループを使用して複雑で巨大なマップを描画する必要があるとします。この質問は間違いなく関連性があります。たとえば、リストの理解が実際に速い場合、ラグを回避するためのはるかに優れたオプションになります(コードの視覚的な複雑さにもかかわらず)。

おそらく、このようなコードが「最適化されていない」Pythonで記述されたときにまだ十分に高速でない場合、Pythonレベルのマイクロ最適化では十分に高速化できないため、考え始める必要がありますCにドロップします。大規模なマイクロ最適化によりPythonコードが大幅に高速化されることがよくありますが、これには(絶対的に)低い制限があります。さらに、その上限に達する前であっても、弾丸を噛んでCを書くと、単純にコスト効率が向上します(15%の高速化と300%の高速化)。

115
user395760

python.orgの情報 を確認すると、次の概要を確認できます。

Version Time (seconds)
Basic loop 3.47
Eliminate dots 2.45
Local variable & no dots 1.79
Using map function 0.54

ただし、実際にはshouldで上記の記事を詳細に読んで、パフォーマンスの違いの原因を理解してください。

また、 timeit を使用してコードの時間を調整することを強くお勧めします。一日の終わりには、たとえば、条件が満たされたときにforループから抜け出す必要がある場合があります。 mapを呼び出して結果を見つけるよりも高速である可能性があります。

16
Anthony Kong

Map()、filter()、reduce()について具体的に尋ねますが、関数型プログラミング全般について知りたいと思います。一連のポイント内のすべてのポイント間の距離を計算する問題についてこれを自分でテストしたところ、関数プログラミング(組み込みitertoolsモジュールのstarmap関数を使用)はforループ(1.25倍の時間がかかる)よりもやや遅いことが判明しました、 実際には)。これが私が使用したサンプルコードです。

import itertools, time, math, random

class Point:
    def __init__(self,x,y):
        self.x, self.y = x, y

point_set = (Point(0, 0), Point(0, 1), Point(0, 2), Point(0, 3))
n_points = 100
pick_val = lambda : 10 * random.random() - 5
large_set = [Point(pick_val(), pick_val()) for _ in range(n_points)]
    # the distance function
f_dist = lambda x0, x1, y0, y1: math.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2)
    # go through each point, get its distance from all remaining points 
f_pos = lambda p1, p2: (p1.x, p2.x, p1.y, p2.y)

extract_dists = lambda x: itertools.starmap(f_dist, 
                          itertools.starmap(f_pos, 
                          itertools.combinations(x, 2)))

print('Distances:', list(extract_dists(point_set)))

t0_f = time.time()
list(extract_dists(large_set))
dt_f = time.time() - t0_f

機能バージョンは手続きバージョンよりも高速ですか?

def extract_dists_procedural(pts):
    n_pts = len(pts)
    l = []    
    for k_p1 in range(n_pts - 1):
        for k_p2 in range(k_p1, n_pts):
            l.append((pts[k_p1].x - pts[k_p2].x) ** 2 +
                     (pts[k_p1].y - pts[k_p2].y) ** 2)
    return l

t0_p = time.time()
list(extract_dists_procedural(large_set)) 
    # using list() on the assumption that
    # it eats up as much time as in the functional version

dt_p = time.time() - t0_p

f_vs_p = dt_p / dt_f
if f_vs_p >= 1.0:
    print('Time benefit of functional progamming:', f_vs_p, 
          'times as fast for', n_points, 'points')
else:
    print('Time penalty of functional programming:', 1 / f_vs_p, 
          'times as slow for', n_points, 'points')
12
andreipmbcn

私は速度をテストする簡単なスクリプトを書きました。これが私が見つけたものです。実際、私の場合はforループが最速でした。それは本当に驚きました。以下をチェックしてください(平方和を計算していました)。

from functools import reduce
import datetime


def time_it(func, numbers, *args):
    start_t = datetime.datetime.now()
    for i in range(numbers):
        func(args[0])
    print (datetime.datetime.now()-start_t)

def square_sum1(numbers):
    return reduce(lambda sum, next: sum+next**2, numbers, 0)


def square_sum2(numbers):
    a = 0
    for i in numbers:
        i = i**2
        a += i
    return a

def square_sum3(numbers):
    sqrt = lambda x: x**2
    return sum(map(sqrt, numbers))

def square_sum4(numbers):
    return(sum([int(i)**2 for i in numbers]))


time_it(square_sum1, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum2, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum3, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum4, 100000, [1, 2, 5, 3, 1, 2, 5, 3])

0:00:00.302000 #Reduce 0:00:00.144000 #For loop 0:00:00.318000 #Map 0:00:00.390000 #List comprehension

7
alphiii

Alphii answer にツイストを追加すると、実際にはforループが2番目に良く、mapよりも約6倍遅くなります。

from functools import reduce
import datetime


def time_it(func, numbers, *args):
    start_t = datetime.datetime.now()
    for i in range(numbers):
        func(args[0])
    print (datetime.datetime.now()-start_t)

def square_sum1(numbers):
    return reduce(lambda sum, next: sum+next**2, numbers, 0)


def square_sum2(numbers):
    a = 0
    for i in numbers:
        a += i**2
    return a

def square_sum3(numbers):
    a = 0
    map(lambda x: a+x**2, numbers)
    return a

def square_sum4(numbers):
    a = 0
    return [a+i**2 for i in numbers]

time_it(square_sum1, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum2, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum3, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum4, 100000, [1, 2, 5, 3, 1, 2, 5, 3])

主な変更点は、遅いsum呼び出しと、最後のケースではおそらく不要なint()呼び出しを排除することです。 forループとマップを同じ用語で表現すると、実際にはかなり事実になります。ラムダは機能的な概念であり、理論的には副作用がないはずですが、aに追加するような副作用がある可能性があることを忘れないでください。この場合の結果は、Python 3.6.1、Ubuntu 14.04、Intel(R)Core(TM)i7-4770 CPU @ 3.40GHzで

0:00:00.257703
0:00:00.184898
0:00:00.031718
0:00:00.212699
5
jjmerelo

@ alpiii's コードの一部を修正し、リスト内包表記がforループよりも少し速いことを発見しました。 int()が原因である可能性があります。リストの内包とforループの間で公平ではありません。

from functools import reduce
import datetime
def time_it(func, numbers, *args):
    start_t = datetime.datetime.now()
    for i in range(numbers):
        func(args[0])
    print (datetime.datetime.now()-start_t)
def square_sum1(numbers):
    return reduce(lambda sum, next: sum+next*next, numbers, 0)
def square_sum2(numbers):
    a = []
    for i in numbers:
        a.append(i*2)
    a = sum(a)
    return a
def square_sum3(numbers):
    sqrt = lambda x: x*x
    return sum(map(sqrt, numbers))
def square_sum4(numbers):
    return(sum([i*i for i in numbers]))
time_it(square_sum1, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum2, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum3, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum4, 100000, [1, 2, 5, 3, 1, 2, 5, 3])

0:00:00.101122

0:00:00.089216

0:00:00.101532

0:00:00.068916

0
Alisca Chen