我試圖比較 sum 函式的 Python 和 Ctypes 版本的性能。我發現 Python 比 ctypes 版本更快。
Sum.c檔案:
int our_function(int num_numbers, int *numbers) {
int i;
int sum = 0;
for (i = 0; i < num_numbers; i ) {
sum = numbers[i];
}
return sum;
}
int our_function2(int num1, int num2) {
return num1 num2;
}
我將其編譯為共享庫:
gcc -shared -o Sum.so sum.c
然后我匯入了共享庫和 ctypes 以使用 C 函式。在Sum.py下方:
import ctypes
_sum = ctypes.CDLL('.\junk.so')
_sum.our_function.argtypes = (ctypes.c_int, ctypes.POINTER(ctypes.c_int))
def our_function_c(numbers):
global _sum
num_numbers = len(numbers)
array_type = ctypes.c_int * num_numbers
result = _sum.our_function(ctypes.c_int(num_numbers), array_type(*numbers))
return int(result)
def our_function_py(numbers):
sum = 0
for i in numbers:
sum = i
return sum
import time
start = time.time()
print(our_function_c([1, 2, 3]))
end = time.time()
print("time taken C", end-start)
start1 = time.time()
print(our_function_py([1, 2, 3]))
end1 = time.time()
print("time taken py", end1-start1)
輸出:
6
time taken C 0.0010006427764892578
6
time taken py 0.0
對于較大的串列,例如list(range(int(1e5))):
start = time.time()
print(our_function_c(list(range(int(1e5)))))
end = time.time()
print("time taken C", end-start)
start1 = time.time()
print(our_function_py(list(range(int(1e5)))))
end1 = time.time()
print("time taken py", end1-start1)
輸出:
704982704
time taken C 0.011005163192749023
4999950000
time taken py 0.00500178337097168
問題:我嘗試使用更多的數字,但 Python 在性能方面仍然勝過 ctypes。所以我的問題是,當我應該通過 Python 遷移到 ctypes 時,是否有經驗法則(就代碼的數量級而言)?另外,請問將 Python 轉換為 Ctypes 的成本是多少?
uj5u.com熱心網友回復:
為什么
好吧,是的,在這種情況下,這并不值得。因為在呼叫 c 函式之前,您需要花費大量時間將數字轉換為c_int.
作為補充,它的擴展性并不低。
通常我們在 C 端生成資料時使用 ctypes。或者當我們從 python 生成它們時,然后將它們用于超過 1 個簡單的操作。
和熊貓一樣
例如,numpy 或 pandas 會發生這種情況。兩個眾所周知的 C 庫示例(或無論如何編譯),只要資料不在 C 空間和 python 空間之間來回傳輸,就可以獲得巨大的時間增益(大約 1000 倍)。
例如,對于許多操作,Numpy 比 list 快。只要不計算每個原子操作的資料轉換。Pandas 經常使用 pandas 從 CSV 讀取的資料。資料保留在 pandas 空間中。
import time
import pandas as pd
lst=list(range(1000000))
start1=time.time()
s1=0
for x in lst:
s1 =x
end1=time.time()
start2=time.time()
df=pd.DataFrame({'x':lst})
middle2=time.time()
s2=df.x.sum()
end2=time.time()
print("python", s1, "t=", end1-start1)
print("pandas", s2, "t=", end2-start2, end2-middle2)
python 499999500000 t= 0.13175106048583984
pandas 499999500000 t= 0.35060644149780273 0.0020313262939453125
正如你所看到的,按照這個標準,pandas 也比 python 慢。但是如果不計算資料創建,速度會更快。
更快,無需資料轉換
嘗試以這種方式運行您的代碼
import time
lst=list(range(1000))*1000
c_lst = (ctypes.c_int * len(lst))(*lst)
c_num = ctypes.c_int(len(lst))
start = time.time()
print(int(_sum.our_function(c_num, c_lst)))
end = time.time()
print("time taken C", end-start)
start1 = time.time()
print(our_function_py(lst))
end1 = time.time()
print("time taken py", end1-start1)
而且c代碼要快得多。
所以,就像熊貓一樣,它不值得,如果真的,你所需要的只是做一次求和,然后忘記資料。
c擴展沒有這樣的問題
請注意,使用允許 c 函式處理 python 型別的 python c 擴展,您不會遇到這個問題(但是,它通常效率較低,因為,好吧,python 陣列不僅int *像 C 所愛的那樣。但至少,您不需要從 python 轉換為 C)這就是為什么您有時可能會看到一些庫,即使計算轉換,使用外部庫也更快。
import numpy as np
np.array(lst).sum()
例如稍微快一點。但幾乎不是這樣,當我們習慣了 numpy 1000 倍的速度時。因為numpy.array從 python 串列資料中幫助自己。
但這不僅僅是 ctypes,(通過 ctypes,我的意思是“使用 c-world 中的 c-functions 處理 c-data,根本不關心 python。”)。另外,我什至不確定這是唯一的原因。Numpy 可能在作弊,使用多個執行緒和矢量化,python 和您的 c 代碼都沒有。
無需大資料轉換的示例
因此,讓我們添加另一個示例,并將其添加到您的代碼中
int sumFirst(int n){
int s=0;
for(int i=0; i<n; i ){
s =i;
}
return s;
}
并嘗試使用
import ctypes
_sum = ctypes.CDLL('./ctypeBench.so')
_sum.sumFirst.argtypes = (ctypes.c_int,)
def c_sumFirst(n):
return _sum.sumFirst(ctypes.c_int(n))
import time
lst=list(range(10000))
start1=time.time()
s1=0
for x in lst:
s1 =x
end1=time.time()
start2=time.time()
s2=c_sumFirst(10000)
end2=time.time()
print(f"python {s1=}, Δt={end1-start1}")
print(f"c {s2=}, Δt={end2-start2}")
結果是
python s1=49995000, Δt=0.0012884140014648438
c s2=49995000, Δt=4.267692565917969e-05
請注意,我對 python 是公平的:我沒有計算當時的資料生成(我明確列出了范圍。變化不大)。
因此,結論是,當您需要對每個資料進行 1 次轉換才能使用它們時,您不能期望 ctypes 函式為每個資料(例如 )的單個操作贏得時間。
要么您需要使用 c-extension 并撰寫臨時代碼而不是處理 python 串列(即使在那里,如果您對每個值只做一個添加,您也不會獲得太多收益)。
或者您需要將資料保留在 c 端,從 c 創建它們,然后將它們放在那里(就像使用 pandas 或 numpy 一樣:您使用 dataframe 或 ndarrays,盡可能使用 pandas 和 numpy 函式或運算子,而不是將它們全部放在 python 中,并帶有完整的索引或.iloc)。
或者,您需要為每個資料添加一個以上的內容。
附錄:c-擴展
只是為了添加另一個支持“問題是轉換”的論點,而且如果你真的需要對一個串列做一個簡單的操作,并且不想轉換之前的每個元素,你可以試試這個
modmy.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#define PY3K
static PyObject *mysum(PyObject *self, PyObject *args){
PyObject *list;
PyArg_ParseTuple(args, "O", &list);
PyObject *it = PyObject_GetIter(list);
long int s=0;
for(;;){
PyObject *v = PyIter_Next(it);
if(!v) break;
long int iv=PyLong_AsLong(v);
s =iv;
}
return PyLong_FromLong(s);
}
static PyMethodDef MyMethods[] = {
{"mysum", mysum, METH_VARARGS, "Sum list"},
{NULL, NULL, 0, NULL} /* Sentinel */
};
static struct PyModuleDef modmy = {
PyModuleDef_HEAD_INIT,
"modmy",
NULL,
-1,
MyMethods
};
PyMODINIT_FUNC
PyInit_modmy()
{
return PyModule_Create(&modmy);
}
編譯
gcc -fPIC `python3-config --cflags` -c modmy.c
gcc -shared -fPIC `python3-config --ldflags` -o modmy.so modmy.o
然后
import time
import modmy
lst=list(range(10000000))
start1=time.time()
s1=0
for x in lst:
s1 =x
end1=time.time()
start2=time.time()
s2=modmy.mysum(lst)
end2=time.time()
print("python res=%d t=%5.2f"%(s1, end1-start1))
print("c res=%d t=%5.2f"%(s2, end2-start2))
這次不需要轉換(或者,更準確地說,是的,仍然需要轉換。但它是由 C 代碼完成的,因為它不是任何 C 代碼,而是專門為擴展 python 撰寫的代碼。 (畢竟,python 解釋器在底層也需要解包元素)
請注意,我的代碼不檢查任何內容。它假定您確實mysum使用一個作為整數串列的引數進行呼叫。如果你不這樣做,天知道會發生什么。嗯,不只是上帝。試試看嘛:
>>> import modmy
>>> modmy.mysum(12)
Segmentation fault (core dumped)
$
Python崩潰(不僅僅是python的代碼。這不是python錯誤。python行程死了)
但結果值得
python res=49999995000000 t= 1.22
c res=49999995000000 t= 0.11
所以,你看,這次 C 贏了。因為它實際上是相同的規則(他們正在做同樣的事情。只是 C 做得更快)
所以,你需要知道你在做什么。但是,這符合您的預期:對整數串列進行非常簡單的操作,在 C 中比在 python 中運行得更快。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/529657.html
標籤:Python表现类型
上一篇:applyInPandas()聚合在大增量表上運行緩慢
下一篇:Julia隨機微分方程非常慢
