2016-10-23 11 views
3

PEP 484では、「パフォーマンスの最適化にタイプヒントを使用することは、読者の練習として残されています。これはCommon Lispのように、型宣言を使って、私が何をやっているかを誓ったときに、パフォーマンス集約型の関数の中で型のディスパッチを脇に置くことができることを示唆しています。これを自分自身で試してみるために、私はpシリーズを使ってpiを計算するための少しのベンチマークを書きました。パフォーマンスの最適化のためのPython 3タイプのヒント

import math 
import time 

def baselpi0(n): 
    baselsum = 0; 
    for i in range(1,n): 
     baselsum += 1.0/(i * i) 
    return math.sqrt(6.0 * baselsum) 

def baselpi1(n : int) -> float: 
    n = float(n) 
    baselsum = 0.0 
    i = 1.0 
    while i < n: 
     baselsum += 1.0/(i * i) 
     i += 1.0 
    return math.sqrt(6.0 * baselsum) 

start = time.time() 
print(baselpi0(1000000000)) 
end = time.time() 
print(end - start) 
start = time.time() 
print(baselpi1(1000000000)) 
end = time.time() 
print(end - start) 

私がエミュレートしようとしているCommon Lispの類推がある:まず、私はその後、私は賢いことをしようとすると、パフォーマンスのための型のヒントを活用し、それを素朴な方法を行うには

(defun baselpi0 (n) 
    (let ((baselsum 0.0d0)) 
    (loop for i from 1 to n do 
     (setf baselsum (+ baselsum (/ 1.0 (* i i))))) 
    (sqrt (* 6 baselsum)))) 

(defun baselpi1 (n) 
    (let ((baselsum 0.0d0) 
     (n (coerce n 'double-float))) 
    (declare (type double-float baselsum n) 
     (optimize (speed 3) (safety 0) (debug 0))) 
    (loop for i from 1.0d0 to n do 
      (setf baselsum (+ baselsum (/ 1.0d0 (* i i))))) 
    (sqrt (* 6.0d0 baselsum)))) 

(time (princ (baselpi0 1000000000))) 
(time (princ (baselpi1 1000000000))) 
(exit) 

私マシンでは、sbclで実行されるLispバージョンは、低速バージョンでは22秒、タイプヒントバージョンでは4秒、Cと同じです。CPythonは、ナイーブバージョンで162秒、タイプヒントバージョンで141秒です。 Pypyはタイプヒントのないバージョンをわずか5秒で実行しますが、ライブラリのサポートは私のプロジェクトには十分ではありません。

lispやPypyに近いパフォーマンスを得るためにヒント付きのバージョンを改善する方法はありますか?

答えて

3

スピードの違いはタイプヒントによるものではありません。 Python 現在、近い将来、は、常にのように動的に提供し、実行を続けます。ヒントを破棄します。

あるケースでは、コード全体で浮動算術を使用するため(実行が速くなります)、そうでない場合は浮動算術を使用することが原因です。ポイントで

ケース:以下に変更しbaselpi1

def baselpi1(n : int) -> float: 
    n = float(n) 
    baselsum = 0 
    i = 1 
    while i < n: 
     baselsum += 1.0/(i * i) 
     i += 1 
    return math.sqrt(6.0 * baselsum) 

そして今、実行時間を見てみましょう:

3.141591698659554 
0.2511475086212158 
3.141591698659554 
0.4525010585784912 

はい、それが道に遅いです。

+0

あなたは一般的なパフォーマンスを助けるためにタイプヒントを期待してはいけないと言っていますか? PEPの "エクササイズ"は、それがすべきことを示唆している。 – Juanote

+1

@Juanoteいいえ、一般的ではありません。あなたはそれを行う方法を理解している限り、自分のコードでそれらを利用することができます、それはエクササイズです。しかし、他の人のコード(CPythonを読んでください)が、文書化されていない限り、タイプヒントを使って何かをすることは期待できません。 – Goyo

+0

私は参照してください。 Common Lispで示したことを私にさせてくれる別の言語機能がありますか、または速い演算のためにCに落とす必要がありますか?私はむしろPythonの新機能であり、セマンティクスの点ではCommon Lispに非常に近いと思われますが、私が知る限りでは、高速な宣言は利用できません。 – Juanote

0

数値計算を大量に行う必要がある場合は、numpyが一般的に適しています。 Numpyは、より低いレベルのデータ型(固定幅の整数など - Pythonは無制限です)で動作します。 numpyは既知の型の配列で大量のデータを処理するように設計されているため、配列全体に対して同じ操作を効率的に実行できます。これはまた、numpyがSIMD命令(私はSIMDのない最新のCPUを認識していません)のCPUでうまく動作するようにします。

私は普通のようなあなたの関数を書き換えます:

import math 
import numpy 

def baselpi_numpy(n): 
    i = numpy.arange(1, n) # array of 1..n 
    baselsum = (1/(i * i)).sum() 
    return math.sqrt(6 * baselsum) 

しかし、大nのために、あなたは十分なメモリを持っていません。あなたは、あなたのために操作をバッチ処理するために少しのコードを追加する必要があります。つまり、

def baselpi_numpy(n, batch_size=1 << 16): 
    basel_sum = 0 
    i = 1 
    for i in range(1, n, batch_size): 
     j = min(n, i + batch_size) 
     basel_sum += baselsum_numpy(i, j) 
    return math.sqrt(6 * basel_sum) 

def baselsum_numpy(start, end): 
    # equivalent -> sum(1/(i * i) for i in range(start, end)) 
    i = numpy.arange(start, end, dtype=float) 
    # this line and next are memory optimisations which double speed 
    # equivalent to i = 1/(i * i) 
    i *= i 
    i = numpy.divide(1, i, out=i) 
    basel_sum = i.sum() 
    return basel_sum 

私のラップトップでは5.2秒後に結果が返ってきます。私がnの値をテストしていませんが、より低いnの場合、numpyのバージョンは20倍以上高速です。

関連する問題