[英]Why is my computation so much faster in C# than Python
下面是分別用C#
和Python
編寫的一個簡單的過程(對於那些對過程感到好奇的人,它是Project Euler No. 5的解決方案)。
我的問題是,下面的C#
代碼只需要 9 秒來迭代,而Python
代碼的完成需要 283 秒(准確地說,Python 3.4.3 - 64 位為 283 秒,Python 2.7.9 - 32 位為 329 秒) .
到目前為止,我已經在C#
和Python
編寫了類似的過程,並且執行時間差異相當。 然而,這一次,經過的時間之間存在極大的差異。
我認為,這種差異的一部分源於python語言的靈活變量類型(我懷疑,python將部分變量轉換為double),但這仍然難以解釋。
我究竟做錯了什么?
我的系統:Windows-7 64 位,
C# - VS Express 2012(9 秒)
Python 3.4.3 64 位(283 秒)
Python 2.7.9 32 位(329 秒)
c-sharp代碼:
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 的 Python 變體(例如 PyPy)中嘗試過此操作,我向您保證您會看到巨大的差異。
一般提示是避免使用標准 Python 進行計算量非常大的操作(尤其是當后端服務來自多個客戶端的請求時)。 Java、C#、JavaScript 等使用 JIT 效率更高。
順便說一句,如果你想以更 Pythonic 的方式編寫你的例子,你可以這樣做:
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:冗長的帖子是我試圖捍衛 Python(我選擇的語言)對抗 C#。 在這個例子中,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 中的最大公分母函數的 CodeFights for Least Common Multiple 上回答的一個問題。 該代碼如下:
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#,我能夠在(可能)一行 lambda 表達式中返回正確答案,並且比我的快 300 倍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 相比,性能有三倍的回報。 在編寫 Selenium 進程時也是如此,因為我可以在 100 毫秒或更短的時間內讀取 Python 中的文本元素,但每次檢索元素都需要 C# > 1 秒才能返回。 也就是說,我實際上更喜歡 C# 實現,因為它是面向對象的方法,其中 Python 的 Selenium 實現功能強大,有時很難閱讀。
如果您想要像 C 一樣快但犧牲一點代碼可讀性,請嘗試使用 python JIT 實現,例如 pypy 和 numba 或 cython。
例如在pypy
# PyPy
number 232792560
time elapsed = 4.000000 sec.
例如在 cython
# 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))
正如一些人所說,最好的方法是使用 JIT 實現。 我知道這是一個老話題,但我很好奇實現之間的執行時間差異,所以我在 Jupiter Notebook 中用 Numba 和 Cython 做了一些測試,這是我的結果:
%%time
def test():
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
return start-1
test()
CPU 時間:用戶 1 分 18 秒,系統:462 毫秒,總計:1 分 19 秒掛牆時間:1 分 21 秒
%%time
def test():
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
return x
test()
CPU 時間:用戶 40.1 秒,系統:305 毫秒,總計:40.4 秒掛牆時間:41.9 秒
%%time
from numba import jit
@jit(nopython=True)
def test():
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
return x
test()
CPU 時間:用戶 4.48 秒,系統:70.5 毫秒,總計:4.55 秒掛牆時間:5.01 秒
%%time
from numba import jit, int32
@jit(int32())
def test():
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
return x
test()
CPU 時間:用戶 3.56 秒,系統:43.1 毫秒,總計:3.61 秒掛牆時間:3.79 秒
%load_ext Cython
%%time
%%cython
def test():
cdef int max_number = 20
cdef int x = max_number
cdef int i = 2
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
return x
test()
CPU 時間:用戶 617 毫秒,系統:20.7 毫秒,總計:637 毫秒掛牆時間:1.31 秒
Python(以及包括 matlab 在內的所有腳本語言)並非旨在直接用於大規模數值計算。 編譯后的程序要兼容結果,不惜一切代價避免循環,將公式轉換為矩陣格式(需要一點數學理解和技巧),這樣我們就可以盡可能地推送到numpy提供的后台C庫,scipy 等。
再次強調,不要在 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.
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.