以下は、それぞれC#
とPython
でコード化されたプロセスの簡単な部分です(プロセスに興味のある方は、 Project Euler の問題5の解決策です)。
私の質問は、以下のC#
コードの反復に9秒しかかかりませんが、Python
コードの完了には283秒かかります(正確には、Python 3.4.3-64ビット、329秒Python 2.7.9-32ビット)。
これまでのところ、C#
とPython
の両方で同様のプロセスをコーディングしており、実行時間の差は同等でした。ただし、今回は経過時間に極端な違いがあります。
この違いの一部は、柔軟な変数タイプpython language(疑わしい、python変数の一部をdoubleに変換する)まだ説明するのは難しいです。
私は何を間違えていますか?
私のシステム:Windows-7 64ビット、
C#-VS Express 2012(9秒)
Python 3.4.3 64ビット(283秒)
Python 2.7.9 32ビット(329秒)
c-シャープコード:
using System;
namespace bug_vcs {
class Program {
public static void Main(string[] args) {
DateTime t0 = DateTime.Now;
int maxNumber = 20;
bool found = false;
long start = maxNumber;
while (!found) {
found = true;
int i = 2;
while ((i < maxNumber + 1) && found) {
if (start % i != 0) {
found = false;
}
i++;
}
start++;
}
Console.WriteLine("{0:d}", start - 1);
Console.WriteLine("time elapsed = {0:f} sec.", (DateTime.Now - t0).Seconds);
Console.ReadLine();
}
}
}
およびpythonコード:
from datetime import datetime
t0 = datetime.now()
max_number = 20
found = False
start = max_number
while not found:
found = True
i = 2
while ((i < max_number + 1) and found):
if (start % i) != 0:
found = False
i += 1
start += 1
print("number {0:d}\n".format(start - 1))
print("time elapsed = {0:f} sec.\n".format((datetime.now() - t0).seconds))
答えは、Pythonすべてのオブジェクトを処理し、デフォルトでは [〜#〜] jit [〜#〜] を持たないということです。スタック上の数バイトを変更し、コードのホットな部分(つまり、反復)を最適化することにより、非常に効率的です。Pythonは、数値を表すリッチオブジェクトと共に、オンザフライ最適化を行いません。
これをJIT(PyPyなど)を持つPythonのバリアント)で試した場合、大きな違いが生じることを保証します。
一般的なヒントは、標準のPythonが非常に計算負荷の高い操作(特に、これが複数のクライアントからのリクエストを処理するバックエンドの場合)を避けることです。JITを使用するJava、C#、JavaScriptなどは、効率的。
ところで、よりPython的な方法で例を記述したい場合は、次のようにすることができます。
from datetime import datetime
start_time = datetime.now()
max_number = 20
x = max_number
while True:
i = 2
while i <= max_number:
if x % i: break
i += 1
else:
# x was not divisible by 2...20
break
x += 1
print('number: %d' % x)
print('time elapsed: %d seconds' % (datetime.now() - start_time).seconds)
上記は90秒で実行されました。速くなる理由は、x
がstart
より短いなど、一見愚かなことに依存していること、変数をそれほど頻繁に割り当てておらず、変数ではなくPythonの制御構造に依存していることです。ループの内外にジャンプするためのチェック。
TL; DR:C#に対してPython(私の好みの言語)を守ろうとしている私です。この例では、C#のパフォーマンスは向上しますが、同じ量の作業を行うためにさらに多くのコード行が必要ですが、最終的なパフォーマンス上の利点は、C#がPythonの同様のアプローチよりも適切にコーディングされている場合、最大5倍速いことです。最終的には、自分に合った言語を使用する必要があります。
C#の例を実行すると、マシンで完了するのに約3秒かかり、232,792,560の結果が得られました。数値が20の倍数である場合、1から20までの数値で割り切れる数値しか持てないという既知の事実を使用して最適化できます。したがって、1ずつ増やす必要はなく、代わりに20にします。わずか353ミリ秒でコードを10倍高速に実行しました。
Pythonの例を実行するとき、待機をあきらめ、itertoolsを使用して独自のバージョンを作成しようとしましたが、成功率はそれほど高くなく、あなたの例ほど長くかかっていました。それから、私の最大数の倍数のみが最小から最大までのすべての数で割り切れる可能性があることを考慮すると、許容できるバージョンのitertoolsを見つけました。そのため、洗練されたPython(3.6)コードには、実行にかかった秒数を出力するデコレータータイミング関数があります。
import time
from itertools import count, filterfalse
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print(time.time() - start)
return res
return wrapper
@timer
def test(stop):
return next(filterfalse(lambda x: any(x%i for i in range(2, stop)), count(stop, stop)))
print("Test Function")
print(test(20))
# 11.526668787002563
# 232792560
これは最近、PythonでGreatest Common Denominator関数を使用して最小公倍数のCodeFightsで答えなければならなかった質問を思い出しました。そのコードは次のとおりです。
import time
from fractions import gcd
from functools import reduce
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print(time.time() - start)
return res
return wrapper
@timer
def leastCommonDenominator(denominators):
return reduce(lambda a, b: a * b // gcd(a, b), denominators)
print("LCM Function")
print(leastCommonDenominator(range(1, 21)))
# 0.001001596450805664
# 232792560
ほとんどのプログラミングタスクと同様に、最も単純なアプローチが常に最速とは限りません。残念ながら、今回はPythonで試みたときに本当に突き出ていました。とはいえ、Pythonの美しさは、10行のC#を必要とするパフォーマンスの実行を取得するシンプルさであり、1行のラムダ式で正しい答えを返すことができました。 C#での単純な最適化よりも何倍も高速です。私はC#の専門家ではありませんが、ここで使用したコードとその結果(Pythonよりも約5倍高速)が同じアプローチを実装しています:
using System;
using System.Diagnostics;
namespace ConsoleApp1
{
class Program
{
public static void Main(string[] args)
{
Stopwatch t0 = new Stopwatch();
int maxNumber = 20;
long start;
t0.Start();
start = Orig(maxNumber);
t0.Stop();
Console.WriteLine("Original | {0:d}, {1:d}", maxNumber, start);
// Original | 20, 232792560
Console.WriteLine("Original | time elapsed = {0}.", t0.Elapsed);
// Original | time elapsed = 00:00:02.0585575
t0.Restart();
start = Test(maxNumber);
t0.Stop();
Console.WriteLine("Test | {0:d}, {1:d}", maxNumber, start);
// Test | 20, 232792560
Console.WriteLine("Test | time elapsed = {0}.", t0.Elapsed);
// Test | time elapsed = 00:00:00.0002763
Console.ReadLine();
}
public static long Orig(int maxNumber)
{
bool found = false;
long start = 0;
while (!found)
{
start += maxNumber;
found = true;
for (int i=2; i < 21; i++)
{
if (start % i != 0)
found = false;
}
}
return start;
}
public static long Test(int maxNumber)
{
long result = 1;
for (long i = 2; i <= maxNumber; i++)
{
result = (result * i) / GCD(result, i);
}
return result;
}
public static long GCD(long a, long b)
{
while (b != 0)
{
long c = b;
b = a % b;
a = c;
}
return a;
}
}
}
ただし、ほとんどの高レベルのタスクでは、通常、Pythonが.NET実装と比較して非常に優れていることがわかりますが、Python Requestsライブラリが持っているとは別に、現時点では主張を実証できません同じ方法で記述されたC#WebRequestと比較して、パフォーマンスが2倍から3倍も向上しました。これは、Seleniumプロセスを記述するときにも当てはまりました。100ミリ秒以下でPythonのテキスト要素を読み取ることができましたが、各要素を取得するにはC#> 1秒かかりました。とは言っても、実際にはPythonのSelenium実装が機能し、時には読みにくくなるというオブジェクト指向のアプローチのため、C#実装を好んでいます。
Cのように高速にしたいがコードの可読性を少し犠牲にしたい場合は、pypy、numba、cythonなどのJIT実装をpython.
例:pypy
# PyPy
number 232792560
time elapsed = 4.000000 sec.
例:シトン
# Cython
number 232792560
time elapsed = 1.000000 sec.
Cythonソース:
from datetime import datetime
cpdef void run():
t0 = datetime.now()
cdef int max_number = 20
found = False
cdef int start = max_number
cdef int i
while not found:
found = True
i = 2
while ((i < max_number + 1) and found):
if (start % i) != 0:
found = False
i += 1
start += 1
print("number {0:d}\n".format(start - 1))
print("time elapsed = {0:f} sec.\n".format((datetime.now() - t0).seconds))
Python(およびmatlabを含むすべてのスクリプト言語)は、大規模な数値計算に直接使用するためのものではありません。コンパイル済みプログラムとして互換性のある結果を得るには、ループを一切避け、数式を行列形式(少し数学的な理解とスキルを必要とする)に変換して、 numpy、scipyなどが提供するバックグラウンドCライブラリで可能.
繰り返しますが、行列計算が可能な限り、pythonで数値計算用のループを書かないでください!
まず、この問題を解決するためにアルゴリズムを変更する必要があります。
#!/usr/bin/env python
import sys
from timeit import default_timer as timer
pyver = sys.version_info;
print(">")
print("> Smallest multiple of 2 ... K");
print(">")
print("> Python version, interpreter version: {0}.{1}.{2}-{3}-{4}".format(
pyver.major, pyver.minor, pyver.micro, pyver.releaselevel, pyver.serial))
print(">")
K = 20;
print(" K = {0:d}".format(K))
print("")
t0 = timer()
N = K
NP1 = N + 1
N2 = (N >> 1) + 1
vec = range(0, NP1)
smalestMultiple = 1
for i in range(2, N2):
divider = vec[i]
if divider == 1:
continue
for j in range(i << 1, NP1, i):
if (vec[j] % divider) == 0:
vec[j] /= divider
for i in range(2, NP1):
if vec[i] != 1:
smalestMultiple = smalestMultiple * vec[i]
t1 = timer()
print(" smalest multiple = {0:d}".format(smalestMultiple))
print(" time elapsed = {0:f} sec.".format(t1 - t0))
Linux/Fedora 28/Intel(R)Core(TM)i7-2760QM CPU @ 2.40GHzでの出力:
> Smallest multiple of 2 ... K
>
> Python version, interpreter version: 2.7.15-final-0
>
> K = 20
>
> smalest multiple = 232792560
> time elapsed = 0.000032 sec.