CFFI 是Python的C語言外部函數(shù)接口。通過CFFI,Python可以與C語言代碼進行交互,使用起來也比較方便。
本文主要內(nèi)容有:
- CFFI數(shù)組的基本使用
- CFFI數(shù)組和Numpy ndarry相互轉(zhuǎn)換
測試環(huán)境:Python3.7.4(x64) + CFFI 1.3.1 + VS2019
安裝CFFI
運行pip install cffi即可。
測試:
import cffi
print(cffi.__version__)
1.13.1
CFFI數(shù)組基本使用
首先導(dǎo)入庫文件并實例化FFI
import numpy as np
from cffi import FFI
ffi = FFI()
CFFI數(shù)組使用
ffi.new
ffi.new(cdecl, init=None)根據(jù)指定的C語言類型創(chuàng)建實例并返回指針,如果C語言類型是數(shù)組,則返回它的引用。具體介紹見ffi.new。
例如:
a = ffi.new("int[]", 10)
print(len(a), a, type(a))
10 <cdata 'int[]' owning 40 bytes> <class '_cffi_backend.CDataOwn'>
對于返回的<cdata>指針,可以直接通過下標對其讀取和賦值,也可以進行切片操作。使用切片時,注意不能省略開始和結(jié)束位置,不能使用負索引,不能使用步長。
# 通過下標讀取賦值
a[0] = 11
a[1] = 55
for i in range(len(a)):
print(a[i], end=' ')
11 55 0 0 0 0 0 0 0 0
為了簡便,后續(xù)<cdata>數(shù)組打印,直接將其轉(zhuǎn)換為list。
切片
<cdata>數(shù)組的切片是對原數(shù)組的引用,它們共用相同的內(nèi)存,對新切片進行賦值操作會影響原數(shù)組,這和Python的list切片操作有很大的不同。
# 切片賦值
a = ffi.new("int[]", 10)
a[3:5]=[11,22] # 長度必須一致
print(list(a))
# 切片引用
b = a[5:10]
print(b)
b[2] = 1200
print(list(a), list(b))
[0, 0, 0, 11, 22, 0, 0, 0, 0, 0]
<cdata 'int[]' sliced length 5>
[0, 0, 0, 11, 22, 0, 0, 1200, 0, 0] [0, 0, 1200, 0, 0]
從上面可以看出,數(shù)組a的切片也是<cdata>類型,對b的賦值操作會影響a數(shù)組。
多維數(shù)組
多維數(shù)組最簡單的定義方式是定義時直接指定數(shù)組大小,可以避免復(fù)雜的初始化。例如二維數(shù)組使用ffi.new("int[2][10]"),三維數(shù)組使用ffi.new("int[2][5][10]")。使用len獲取多維數(shù)組長度時,返回的是第一維的長度,例如:
b = ffi.new("int[2][5][10]")
print(b)
print(len(b), len(b[0]), len(b[0][0]))
<cdata 'int[2][5][10]' owning 400 bytes>
2 5 10
多維數(shù)組同樣是通過下標進行讀取賦值:
b[0][0][0] = 100
b[0][0][1:3] = [2,3]
print(list(b[0][0]))
[100, 2, 3, 0, 0, 0, 0, 0, 0, 0]
CFFI數(shù)組解包
ffi.unpack
ffi.unpack(cdata, length)解包指定長度的C語言數(shù)組,并返回一個list。對于一維數(shù)組,ffi.unpack和list()的效果類似。從測試結(jié)果來看,當數(shù)組長度較小時,list()的性能好一些;數(shù)據(jù)量變大時,ffi.unpack就更具優(yōu)勢,推薦編程時使用ffi.unpack對數(shù)組進行解包操作。
a = ffi.new("int[]", 10)
a[1] = 100
b = ffi.unpack(a, 10)
print(b, type(b))
print(b == list(a))
%timeit ffi.unpack(a, 10)
%timeit list(a)
a = ffi.new("int[]", 10000)
%timeit ffi.unpack(a, 10000)
%timeit list(a)
[0, 100, 0, 0, 0, 0, 0, 0, 0, 0] <class 'list'>
True
451 ns ± 36 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
265 ns ± 4.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
54.2 μs ± 2.05 μs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
74.8 μs ± 294 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
對于多維數(shù)組,例如a = ffi.new("int[4][10]"),解包時需要指定一維數(shù)組長度,使用時注意長度不要越界。
from pprint import pprint
a = ffi.new("int[4][10]")
a[3][9] = 1000
b = ffi.unpack(a,5)
pprint(b)
# 解包a[3]
b3 = ffi.unpack(b[3],10)
pprint(b3)
# 或者直接解包a[3]
b3 = ffi.unpack(a[3],10)
pprint(b3)
[<cdata 'int[10]' 0x0000027A3AA28548>,
<cdata 'int[10]' 0x0000027A3AA28570>,
<cdata 'int[10]' 0x0000027A3AA28598>,
<cdata 'int[10]' 0x0000027A3AA285C0>,
<cdata 'int[10]' 0x0000027A3AA285E8>]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1000]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1000]
可以看到,ffi.unpack操作返回一個list,其元素是<cdata>數(shù)組。多維數(shù)組的解包不是很直觀,使用也不多,實際使用時,可以將其轉(zhuǎn)換為Numpy ndarry。
CFFI數(shù)組和Numpy 相互轉(zhuǎn)換
ndarray轉(zhuǎn)CFFI數(shù)組
CFFI提供了ffi.from_buffer([cdecl,] python_buffer, require_writable=False)函數(shù),可以把python_buffer轉(zhuǎn)換為<cdata>數(shù)組。其中cdecl為python_buffer類型,默認為<cdata 'char[]'>。require_writable如果為True,則緩沖區(qū)python_buffer必須可寫,否則函數(shù)會調(diào)用失敗,具體介紹見ffi.from_buffer。
ndarray轉(zhuǎn)CFFI數(shù)組:
a = np.arange(10, dtype=np.int)
# ndarray to cdata
b = ffi.from_buffer("int*", a)
print(b)
print(ffi.unpack(b, 10)) # 由于b是"int *"類型,不能使用list()
b[3] = 300
print(a, ffi.unpack(b, 10))
<cdata 'int *' buffer from 'numpy.ndarray' object>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[ 0 1 2 300 4 5 6 7 8 9] [0, 1, 2, 300, 4, 5, 6, 7, 8, 9]
把ndarray轉(zhuǎn)換為<cdata>數(shù)組時,它們共用同一塊內(nèi)存,對b的賦值操作也會影響數(shù)組a。
另外可以使用ffi.cast函數(shù),把ndarray轉(zhuǎn)換為<cdata>數(shù)組,例如:
b = ffi.cast("int*", a.ctypes.data)
其效果和ffi.from_buffer一樣。
CFFI數(shù)組轉(zhuǎn)ndarray
CFFI數(shù)組轉(zhuǎn)ndarray有兩步:
- 先用ffi.buffer(cdata, [size])把CFFI數(shù)組轉(zhuǎn)換成python buffer。需要注意
size是字節(jié)長度。 - 再用numpy.frombuffer把python buffer轉(zhuǎn)換成ndarray。實際使用時,需要指定
dtype和count。
例如:
a = np.arange(10, dtype=np.int)
# ndarray to cdata
b = ffi.from_buffer("int*", a)
# cdata to buffer
c = ffi.buffer(b, 40)
# buffer to ndarray
d = np.frombuffer(c, dtype=np.int, count=10)
print(d)
d[1] = 100
print(a, d)
[0 1 2 3 4 5 6 7 8 9]
[ 0 100 2 3 4 5 6 7 8 9] [ 0 100 2 3 4 5 6 7 8 9]
可以看到,a和d對應(yīng)的是同一塊內(nèi)存。
下面使用float型完整介紹一遍CFFI數(shù)組和ndarray的相互轉(zhuǎn)換:
a = np.arange(10, dtype=np.float32)
# ndarray to cdata
b = ffi.from_buffer("float*", a)
# cdata to buffer
c = ffi.buffer(b, 40) # 10 * 4 = 40
# buffer to ndarray
d = np.frombuffer(c, dtype=np.float32, count=10) # 數(shù)據(jù)類型需要對應(yīng)
print(d)
d[1] = 100.0
print(a, d)
[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
[ 0. 100. 2. 3. 4. 5. 6. 7. 8. 9.] [ 0. 100. 2. 3. 4. 5. 6. 7. 8. 9.]
以上就是本文的內(nèi)容,后續(xù)會介紹下cffi、cython和ctypes性能對比。
本文代碼地址:https://github.com/txfly/cffi_cython/archive/master.zip
版權(quán)聲明:本文為「txfly」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:http://m.itdecent.cn/p/80e0423ee70e