3

ここから- 分岐予測問題、Python バージョンのプログラムを作成して、Python で並べ替えられたバージョンと並べ替えられていないバージョンの実行時間を確認しました。最初にソートしてみました。

コードは次のとおりです。

import time

from random import *
arraysize = 327
data = []

for  c in range(arraysize):
    data.append( randint( 1 , 256 )  ) 


## !!! with this, the next loop runs faster
data.sort()

## test

start_time = time.clock()

sum = 0


for i in range(100000):
    for c in range(arraysize):
        if data[c] >= 128:
            sum += data[c]


print time.clock() - start_time

私の単純なタイミング方法論の正確さについてはよくわかりませんが、それで十分なようです。arraysize = 32768初めて設定したとき、20分以上待ちました!! 20分以上!しかし、でarraysize = 327、私はの時間を取得し8.141656691sます。

コードのどこかが間違っている場合、または Numpy/Scipy を何らかの方法で使用すると速度が向上するかどうかを修正してください。ありがとう。

4

2 に答える 2

8

@mgilson の回答から始めて、少し作り直しました。元の質問への回答で説明したように、「決定ビット」とルックアップ テーブルの手法をテストしたかった: https://stackoverflow.com/a/17782979/166949

オリジナルにいくつかの変更を加えました。いくつかは、私の個人的な好みを反映したスタイルのものでした. しかし、オリジナルでバグを発見したので、正しいコードを測定することが重要だと思います。

Python コードがコマンド ラインから引数を取るようにし、Python 2.x、Python 3.x、および PyPy を使用して Python スクリプトを実行するシェル スクリプトを作成しました。正確なバージョンは、Python 2.7.6、Python 3.4.0、および PyPy 2.7.3 でした。

Linux Mint 17、64 ビット バージョンでテストを実行しました。CPU は AMD Phenom 9850 で、2.5 GHz で 16 GB の RAM を搭載しています。あたりの Linux カーネルのバージョンuname -rは次のとおりです: 3.13.0-24-generic

コードがコマンド ラインから引数を取るようにした理由は、327 がかなり短いリストだからです。sum()リストがはるかに長い場合は and ジェネレーター式の方がうまくいくと考えたので、コマンド ラインからリストのサイズと試行回数を渡すようにしました。結果には、どの Python か、リストの長さと試行回数が表示されます。

結論: 驚いたことに、リストが長くても、Python はsum()リスト内包表記を使用するのが最速でした! ジェネレーターを実行すると、リストを作成して破棄するオーバーヘッドよりも遅いと思われるオーバーヘッドがあります。

ただし、リストが本当に大きくなった場合、ジェネレーターがリスト内包表記よりも優れたパフォーマンスを発揮し始めると予想しました。100 万個のランダム値のリストでは、listcomp はさらに高速でしたが、1,600 万個のランダム値になると、genexp の方が高速になりました。また、ジェネレーター式の速度のペナルティは、短いリストでは大きくありません。そのため、Python でこの問題を解決するための頼りになる方法として、ジェネレーター式を今でも支持しています。

興味深いことに、PyPy はテーブル ルックアップで最速でした。これは理にかなっています。これは私が C で見つけた最速の方法であり、PyPy は JIT からネイティブ コードを生成しています。

仮想マシンを備えた CPython の場合、複数の操作よりも 1 つの操作を呼び出す方が高速です。Python VM のオーバーヘッドは、より高価な基本的な操作を上回る可能性があります。したがって、整数除算はビット マスキングとビット シフトよりも高速です。これは、除算が 1 回の演算であるためです。しかし、PyPy では、ビット マスキング + シフトは除算よりもはるかに高速です。

また、CPython では、 using をsum()使用するとコードを C 内部で実行できるため、非常に高速になります。しかし、PyPy ではsum()、JIT が非常に高速なネイティブ ループに変わる可能性がある単純なループを記述するよりも遅くなります。私の推測では、ジェネレーターの機構は、PyPy が理解してネイティブ コードに最適化するのが難しいということです。

シェルスクリプト:

for P in python python3 pypy; do
    echo "$P ($1, $2)"
    $P test_branches.py $1 $2
    echo
done

Python コード:

import random
import sys
import timeit

try:
    RANGE = xrange
except NameError:
    RANGE = range

if len(sys.argv) != 3:
    print("Usage: python test_branches.py <length_of_array> <number_of_trials>")
    sys.exit(1)

TEST_DATA_LEN = int(sys.argv[1])
NUM_REPEATS = int(sys.argv[2])

_test_data = [random.randint(0,255) for _ in RANGE(TEST_DATA_LEN)]

def test0(data):
    """original way"""
    total = 0
    for i in RANGE(TEST_DATA_LEN):
        if data[i] >= 128:
            total += data[i]
    return total


def test1(data):
    """better loop"""
    total = 0
    for n in data:
        if n >= 128:
            total += n
    return total

def test2(data):
    """sum + generator"""
    return sum(n for n in data if n >= 128)

def test3(data):
    """sum + listcomp"""
    return sum([n for n in data if n >= 128])

def test4(data):
    """decision bit -- bit mask and shift"""
    lst = [0, 0]
    for n in data:
        lst[(n & 0x80) >> 7] += n
    return lst[1]

def test5(data):
    """decision bit -- division"""
    lst = [0, 0]
    for n in data:
        lst[n // 128] += n
    return lst[1]

_lut = [0 if n < 128 else n for n in RANGE(256)]

def test6(data):
    """lookup table"""
    total = 0
    for n in data:
        total += _lut[n]
    return total

def test7(data):
    """lookup table with sum()"""
    return sum(_lut[n] for n in data)

test_functions = [v for k,v in globals().items() if k.startswith("test")]
test_functions.sort(key=lambda x: x.__name__)

correct = test0(_test_data)

for fn in test_functions:
    name = fn.__name__
    doc = fn.__doc__
    if fn(_test_data) != correct:
        print("{}() not correct!!!".format(name))
    s_call = "{}(_test_data)".format(name)
    s_import = "from __main__ import {},_test_data".format(name)
    t = timeit.timeit(s_call,s_import,number=NUM_REPEATS)
    print("{:7.03f}: {}".format(t, doc))

結果:

python (327, 100000)
  3.170: original way
  2.211: better loop
  2.378: sum + generator
  2.188: sum + listcomp
  5.321: decision bit -- bit mask and shift
  4.977: decision bit -- division
  2.937: lookup table
  3.464: lookup table with sum()

python3 (327, 100000)
  5.786: original way
  3.444: better loop
  3.286: sum + generator
  2.968: sum + listcomp
  8.858: decision bit -- bit mask and shift
  7.056: decision bit -- division
  4.640: lookup table
  4.783: lookup table with sum()

pypy (327, 100000)
  0.296: original way
  0.312: better loop
  1.932: sum + generator
  1.011: sum + listcomp
  0.172: decision bit -- bit mask and shift
  0.613: decision bit -- division
  0.140: lookup table
  1.977: lookup table with sum()


python (65536, 1000)
  6.528: original way
  4.661: better loop
  4.974: sum + generator
  4.150: sum + listcomp
 10.971: decision bit -- bit mask and shift
 10.218: decision bit -- division
  6.052: lookup table
  7.070: lookup table with sum()

python3 (65536, 1000)
 12.999: original way
  7.618: better loop
  6.826: sum + generator
  5.587: sum + listcomp
 19.326: decision bit -- bit mask and shift
 14.917: decision bit -- division
  9.779: lookup table
  9.575: lookup table with sum()

pypy (65536, 1000)
  0.681: original way
  0.884: better loop
  2.640: sum + generator
  2.642: sum + listcomp
  0.316: decision bit -- bit mask and shift
  1.573: decision bit -- division
  0.280: lookup table
  1.561: lookup table with sum()


python (1048576, 100)
 10.371: original way
  7.065: better loop
  7.910: sum + generator
  6.579: sum + listcomp
 17.583: decision bit -- bit mask and shift
 15.426: decision bit -- division
  9.285: lookup table
 10.850: lookup table with sum()

python3 (1048576, 100)
 20.435: original way
 11.221: better loop
 10.162: sum + generator
  8.981: sum + listcomp
 29.108: decision bit -- bit mask and shift
 23.626: decision bit -- division
 14.706: lookup table
 14.173: lookup table with sum()

pypy (1048576, 100)
  0.985: original way
  0.926: better loop
  5.462: sum + generator
  6.623: sum + listcomp
  0.527: decision bit -- bit mask and shift
  2.334: decision bit -- division
  0.481: lookup table
  5.800: lookup table with sum()


python (16777216, 10)
 15.704: original way
 11.331: better loop
 11.995: sum + generator
 13.787: sum + listcomp
 28.527: decision bit -- bit mask and shift
 25.204: decision bit -- division
 15.349: lookup table
 17.607: lookup table with sum()

python3 (16777216, 10)
 32.822: original way
 18.530: better loop
 16.339: sum + generator
 14.999: sum + listcomp
 47.755: decision bit -- bit mask and shift
 38.930: decision bit -- division
 23.704: lookup table
 22.693: lookup table with sum()

pypy (16777216, 10)
  1.559: original way
  2.234: better loop
  6.586: sum + generator
 10.931: sum + listcomp
  0.817: decision bit -- bit mask and shift
  3.714: decision bit -- division
  0.752: lookup table
  3.837: lookup table with sum()
于 2014-06-25T03:20:22.260 に答える
4

スタイルの問題でもある小さな最適化の 1 つとして、リストにインデックスを付ける代わりに、リストを直接反復処理できます。

for d in data:
    if d >= 128:
        sum += d

これにより、いくつかの関数呼び出しが節約されます。

sum組み込み関数を使用すると、このループも高速になる可能性があります。

my_sum += sum( d for d in data if d>=128 )

リスト comp、上記のジェネレーターよりも高速な場合があります (少し余分なメモリを犠牲にします)。

my_sum += sum( [d for d in data if d>=128] )

もちろん、アルゴリズムの観点からは、内側のループの合計は変化しないため、最も外側のループを取り除くことができます。

my_sum = 100000 * sum( d for d in data if d>=128 )

しかし、私はあなたがすでにそれを知っていたと思います...


最後に、これをベンチマークする方法は次のとおりです。

import random
import timeit

N = 327
testarr = [random.randint(1,256) for _ in range(N)]

def test1(data):
    """Original way"""
    s = 0
    for c in range(N):
        if data[c] >= 128:
            s += data[c]


def test2(data):
    """better loop"""
    s = 0
    for d in data:
        if d >= 128:
            s += d

def test3(data):
    """ sum + generator """
    sum( d for d in data if d >= 128 )

def test4(data):
    """ sum + listcomp """
    sum( [ d for d in data if d >= 128 ])

NNUMBER = 100000
print timeit.timeit('test1(testarr)','from __main__ import test1,testarr',number=NNUMBER)
print timeit.timeit('test2(testarr)','from __main__ import test2,testarr',number=NNUMBER)
print timeit.timeit('test3(testarr)','from __main__ import test3,testarr',number=NNUMBER)
print timeit.timeit('test4(testarr)','from __main__ import test4,testarr',number=NNUMBER)

私の結果(OS-X Mavericks、python 2.7.5 - マイレージは異なる場合があります):

2.80526804924  # loop with range
2.04129099846  # loop over elements
1.91441488266  # sum with generator
2.05234098434  # sum with list

私にとってはsum、わずかな差で発電機が勝っています。list-comp withsumと明示的なループはほぼ同じです。インデックスのループは (当然のことながら) 最も遅くなります。

于 2012-09-21T12:47:37.120 に答える