web-dev-qa-db-ja.com

Goは実際にPythonよりもはるかに高速化できますか?

結果が意味をなさないため、これを誤って実装した可能性があると思います。 1000000000にカウントされるGoプログラムがあります。

package main

import (
    "fmt"
)

func main() {
    for i := 0; i < 1000000000; i++ {}
    fmt.Println("Done") 
}

1秒未満で終了します。一方、Pythonスクリプト:

x = 0
while x < 1000000000:
    x+=1
print 'Done'

数分で終了します。

Goバージョンがこれほど高速なのはなぜですか?彼らは両方とも1000000000まで数えていますか、何か不足していますか?

44
bab

10億はそれほど大きな数字ではありません。合理的に最新のマシンshouldせいぜい数秒でこれを行うことができますネイティブ型で作業ができる場合。同等のCプログラムを記述し、アセンブリを読んで実際に追加を行っていることを確認し、タイミングを計ります(私のマシンでは約1.8秒で完了します)。

ただし、Pythonにはネイティブに型指定された変数(または意味のある型注釈)の概念がないため、この場合は何百倍もの作業を行う必要があります。つまり、見出しの質問に対する答えは「はい」です。本当に行くcan副作用のないループを最適化するようなコンパイラーのトリックがなくても、Pythonよりもはるかに高速になります。

81
hobbs

pypyは実際にこのループを高速化する印象的な仕事をしています

def main():
    x = 0
    while x < 1000000000:
        x+=1

if __== "__main__":
    s=time.time()
    main()
    print time.time() - s

$ python count.py 
44.221405983
$ pypy count.py 
1.03511095047

〜97%高速化!

「取得」していない3人の説明。 Python言語自体は遅くありません。CPython実装は、コードを実行する比較的単純な方法です。Pypyは、多くのトリッキーな(特にJIT)を行う言語の別の実装です。タイトルの質問に直接答える-GoはPythonよりも「それほど」速くはありませんが、GoはCPythonよりもはるかに速いです。

そうは言っても、コードサンプルは実際には同じことをしていません。 Python intオブジェクトの1000000000をインスタンス化する必要があります。Goは1つのメモリ位置をインクリメントするだけです。

63
John La Rooy

このシナリオは、非常に好意的まともなネイティブコンパイルされた静的型付け言語になります。ネイティブにコンパイルされた静的型付け言語は、終了のために単純なチェック条件を利用する4〜6 CPUオペコードなどの非常に簡単なループを生成できます。このループにはzero分岐予測ミスが効果的にあり、CPUサイクルごとに増分を実行すると効果的に考えることができます(これは完全に真実ではありませんが、..)

Pythonの実装では、主に動的な型指定により、significantlyより多くの作業を行う必要があります。Python 2つのintsを一緒に追加するために、いくつかの異なる呼び出し(内部および外部)を行う必要があります。 Python itmustcall ___add___(実質的にi = i.__add__(1)ですが、この構文はPythonでのみ機能します_ 3.x)、渡された値の型を確認し(intであることを確認するため)、整数値を追加します(の両方からそれらを抽出します) objects)、新しい整数値はnew objectに再びラップされます。最後に、新しいオブジェクトをローカル変数に再割り当てします。これは、単一のオペコードをインクリメントするよりもかなり多くの作業であり、ループ自体に対処することすらしていません-比較すると、Go/nativeバージョンは、副作用によってレジスタをインクリメントするだけです。

Javaはこのような些細なベンチマークでmuchより公平になり、Goにかなり近いでしょう。 JITおよびカウンタ変数のstatic-typingにより、これを保証できます(特別な整数のJVM命令の追加を使用します)。繰り返しますが、Pythonにはそのような利点はありません。現在、 PyPy/RPython のような実装があります。これは静的型付けフェーズを実行し、ここでCPythonよりもはるかに優れているはずです。

20
user166390

ここには2つのことがあります。その1つは、Goがマシンコードにコンパイルされ、CPUで直接実行される一方で、Pythonは(特に遅い)VMに対して実行されるバイトコードにコンパイルされることです。

パフォーマンスに影響する2番目の、より重要なことは、2つのプログラムのセマンティクスが実際には大幅に異なることです。 Goバージョンは、「x」と呼ばれる「ボックス」を作成します。このボックスには、数値を保持し、プログラムを通過するたびに1ずつ増分します。 Pythonバージョンでは、実際に各サイクルで新しい「ボックス」(intオブジェクト)を作成する必要があります(最終的には破棄する必要があります)。プログラムを少し変更することでこれを実証できます。

package main

import (
    "fmt"
)

func main() {
    for i := 0; i < 10; i++ {
        fmt.Printf("%d %p\n", i, &i)
    }
}

...そして:

x = 0;
while x < 10:
    x += 1
    print x, id(x)

これは、GoがCのルートであるため、変数名を使用してplaceを参照するためです。ここでPython thingsを参照する変数名を取ります。整数はPythonで一意で不変のエンティティと見なされるため、常に新しいものを作成する必要があります。PythonはGoより遅いはずですが、最悪のシナリオを選択しました- ベンチマークゲーム 、平均で約25倍(100倍最悪の場合)。

あなたはおそらくあなたのPythonプログラムが遅すぎるなら、物事をCに移すことによってそれらをスピードアップすることができます。幸いなことに、この場合、誰かがすでにあなたのためにこれを行っています。 xrange() を使用するように空のループを書き換えます:

for x in xrange(1000000000):
    pass
print "Done."

...約2倍の速度で実行されます。ループカウンターが実際にプログラムの主要なボトルネックになっている場合は、問題を解決する新しい方法を調査するときが来るかもしれません。

8

@troq

私はパーティーに少し遅れていますが、答えはイエスとノーです。 @gnibblerが指摘したように、単純な実装ではCPythonの方が遅くなりますが、pypyは必要なときにコードを高速化するためにコンパイルされます。

CPythonを使用して数値処理を行う場合、ほとんどの場合、numpyを使用して処理が行われ、配列と行列の操作が高速になります。最近、numbaで多くのことを行っています。これにより、コードに簡単なラッパーを追加できます。このために、上記のコードを実行する関数incALot()に@njitを追加しました。

私のマシンではCPythonに61秒かかりますが、numbaラッパーを使用すると7.2マイクロ秒かかります。これはCに似ており、Goよりも高速です。それは800万倍の高速化です。

そのため、Pythonでは、数字のあるものが少し遅いように思える場合、それに対処するツールがあります-そして、あなたはまだPythonのプログラマーの生産性とREPLを取得します。

def incALot(y):
    x = 0
    while x < y:
        x += 1

@njit('i8(i8)')
def nbIncALot(y):
    x = 0
    while x < y:
        x += 1
    return x

size = 1000000000
start = time.time()
incALot(size)
t1 = time.time() - start
start = time.time()
x = nbIncALot(size)
t2 = time.time() - start
print('CPython3 takes %.3fs, Numba takes %.9fs' %(t1, t2))
print('Speedup is: %.1f' % (t1/t2))
print('Just Checking:', x)

CPython3 takes 58.958s, Numba takes 0.000007153s
Speedup is: 8242982.2
Just Checking: 1000000000
2
John 9631

問題はPythonが解釈され、GOはそうではないため、テスト速度をベンチする実際の方法はありません。通常、解釈された言語(常にvmコンポーネントを持っているわけではありません)に問題があります。実行するテストは、実際のランタイム境界ではなく、解釈された境界で実行されています。 Goは速度の点でCよりわずかに遅く、それは主に手動のメモリ管理の代わりにガベージコレクションを使用するためです。 GOはPythonと比較して高速であるため、コンパイルされた言語であるため、GOに欠けているのはバグテストだけで、間違っている場合は修正されます。

1
Dawg

コンパイラーは、ループの後に「i」変数を使用しなかったことを認識した可能性があるため、ループを削除して最終コードを最適化しました。

後で使用したとしても、コンパイラーはおそらく、ループを

i = 1000000000;

これがお役に立てば幸いです=)