Gọi thư viện C từ Python

Các thư viện C và C++ bên ngoài có thể được gọi từ mã Python bằng một số tùy chọn, sử dụng e. g. Cython, CFFI, pybind11 và ctypes. Chúng tôi sẽ thảo luận về hai điều cuối cùng, bởi vì chúng yêu cầu ít bản soạn sẵn nhất, đối với các trường hợp đơn giản - đối với các ví dụ phức tạp hơn có thể không đúng với trường hợp đó. Hãy xem xét chương trình C đơn giản này, kiểm tra. c, cộng các số liên tiếp

#include 
namespace py = pybind11;

long long sum_range(long long high)
{
  long long i;
  long long s = 0LL;

  for (i = 0LL; i < high; i++)
      s += (long long)i;

  return s;
}

PYBIND11_MODULE(test_pybind, m) {
    m.doc() = "Export the sum_range function as sum_range";

    m.def("sum_range", &sum_range, "Adds upp consecutive integer numbers from 0 up to and including high-1");
}

Bạn có thể dễ dàng biên dịch và liên kết nó thành một đối tượng được chia sẻ (. vì vậy) tập tin. Trước tiên, bạn cần pybind11. Bạn có thể cài đặt nó theo một số cách, như pip, nhưng tôi thích tạo môi trường ảo hơn bằng pipenv

pip install pipenv
pipenv install pybind11
pipenv shell

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` test.c -o test_pybind.so

tạo ra một đối tượng chia sẻ

%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
1 mà bạn có thể gọi từ trình bao iPython, như thế này

%import test_pybind
%sum_range=test_pybind.sum_range
%high=1000000000
%brute_force_sum=sum_range(high)

Bây giờ bạn có thể muốn kiểm tra đầu ra, bằng cách so sánh với công thức nổi tiếng về tổng các số nguyên liên tiếp

%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference

Đặt cho tập lệnh này một cái tên phù hợp, chẳng hạn như

%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
2. Điều tương tự có thể được thực hiện bằng cách sử dụng ctypes thay vì pybind11, nhưng yêu cầu bản soạn sẵn nhiều hơn một chút ở phía Python của mã và ít hơn một chút ở phía C. Bài kiểm tra. c sẽ chỉ là thuật toán

long long sum_range(long long high)
{
  long long i;
  long long s = 0LL;

  for (i = 0LL; i < high; i++)
      s += (long long)i;

  return s;
}

Biên dịch và liên kết bằng cách sử dụng

gcc -O3 -g -fPIC -c -o test.o test.c
ld -shared -o libtest.so test.o

mà tạo ra một libtest. vì vậy tập tin

Bạn sẽ cần thêm một số bản soạn sẵn

________số 8_______

Một lần nữa, bạn có thể so sánh với công thức tính tổng các số nguyên liên tiếp

%sum_from_formula=high*(high-1)/2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference

Hiệu suất

Bây giờ chúng ta có thể tính thời gian cho thư viện

%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
3 C đã biên dịch của mình, e. g. từ giao diện iPython

%timeit sum_range(10**7)

2.69 ms ± 6.01 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Nếu bạn so sánh với thời gian Numba từ chương 3, bạn sẽ thấy rằng thư viện C cho

%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
3 nhanh hơn tính toán numpy nhưng chậm hơn đáng kể so với hàm trang trí
%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
5

Thử thách. Kiểm tra xem phiên bản Numba của hàm %sum_from_formula=high*(high-1)//2 %sum_from_formula %difference=sum_from_formula-brute_force_sum %difference 6 có điều kiện này có tốt hơn phiên bản C của nó không

Lấy cảm hứng từ blog của Christopher Swenson

pip install pipenv
pipenv install pybind11
pipenv shell

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` test.c -o test_pybind.so
0

Giải pháp

Chỉ cần chèn một dòng

%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
7 vào mã cho
%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
8 và đổi tên nó thành
%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
9

pip install pipenv
pipenv install pybind11
pipenv shell

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` test.c -o test_pybind.so
1

Hãy kiểm tra xem nó chạy nhanh như thế nào

pip install pipenv
pipenv install pybind11
pipenv shell

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` test.c -o test_pybind.so
2

pip install pipenv
pipenv install pybind11
pipenv shell

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` test.c -o test_pybind.so
3

So sánh thời gian này với thời gian chạy mã C cho có điều kiện_sum_range. Biên dịch và liên kết theo cách thông thường, giả sử tên tệp vẫn là

long long sum_range(long long high)
{
  long long i;
  long long s = 0LL;

  for (i = 0LL; i < high; i++)
      s += (long long)i;

  return s;
}
0

gcc -O3 -g -fPIC -c -o test.o test.c
ld -shared -o libtest.so test.o

Một lần nữa, chúng ta có thể tính thời gian cho thư viện

long long sum_range(long long high)
{
  long long i;
  long long s = 0LL;

  for (i = 0LL; i < high; i++)
      s += (long long)i;

  return s;
}
1 C đã biên dịch của mình, e. g. từ giao diện iPython

pip install pipenv
pipenv install pybind11
pipenv shell

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` test.c -o test_pybind.so
5

pip install pipenv
pipenv install pybind11
pipenv shell

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` test.c -o test_pybind.so
6

Điều này cho thấy rằng đối với ví dụ phức tạp hơn một chút này, mã C có phần nhanh hơn mã Python được trang trí bằng Numba

Truyền mảng Numpy cho thư viện C

Bây giờ chúng ta hãy xem xét một ví dụ phức tạp hơn. Thay vì tính tổng các số đến một giới hạn trên nhất định, chúng ta hãy tính tổng đó cho một mảng các giới hạn trên. Điều này sẽ trả về một mảng các khoản tiền. Việc sửa đổi mã C và Python của chúng tôi để hoàn thành việc này khó đến mức nào?

pip install pipenv
pipenv install pybind11
pipenv shell

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` test.c -o test_pybind.so
7

Bây giờ hãy xem điều gì sẽ xảy ra nếu chúng ta chuyển

%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
1 một mảng thay vì một số nguyên

pip install pipenv
pipenv install pybind11
pipenv shell

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` test.c -o test_pybind.so
8

cho

pip install pipenv
pipenv install pybind11
pipenv shell

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` test.c -o test_pybind.so
9

Nó không sụp đổ. Thay vào đó, nó trả về một mảng mà bạn có thể kiểm tra xem có đúng không bằng cách lấy mỗi tổng trừ đi tổng trước đó (trừ tổng đầu tiên)

%import test_pybind
%sum_range=test_pybind.sum_range
%high=1000000000
%brute_force_sum=sum_range(high)
0

cái nào mang lại

%import test_pybind
%sum_range=test_pybind.sum_range
%high=1000000000
%brute_force_sum=sum_range(high)
1

các yếu tố của

long long sum_range(long long high)
{
  long long i;
  long long s = 0LL;

  for (i = 0LL; i < high; i++)
      s += (long long)i;

  return s;
}
5 - ngoại trừ yếu tố cuối cùng - như bạn mong đợi

Gọi đồng thời thư viện C từ nhiều luồng

Chúng tôi có thể nhanh chóng chỉ cho bạn cách thư viện C được biên dịch bằng pybind11 có thể chạy đa luồng. hãy thử cách sau từ vỏ iPython

%import test_pybind
%sum_range=test_pybind.sum_range
%high=1000000000
%brute_force_sum=sum_range(high)
2

cho

%import test_pybind
%sum_range=test_pybind.sum_range
%high=1000000000
%brute_force_sum=sum_range(high)
3

Bây giờ, hãy thử song song hóa đơn giản 20 cuộc gọi của

%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
3, trên hai luồng, do đó, 10 lệnh gọi trên mỗi luồng. Quá trình này sẽ mất khoảng
long long sum_range(long long high)
{
  long long i;
  long long s = 0LL;

  for (i = 0LL; i < high; i++)
      s += (long long)i;

  return s;
}
7 nếu quá trình xử lý song song đang chạy mà không có chi phí. Hãy thử

%import test_pybind
%sum_range=test_pybind.sum_range
%high=1000000000
%brute_force_sum=sum_range(high)
4

điều này mang lại

%import test_pybind
%sum_range=test_pybind.sum_range
%high=1000000000
%brute_force_sum=sum_range(high)
5

i. e. nhiều hơn gấp đôi thời gian chúng ta mong đợi. Điều thực sự xảy ra là

%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
3 được chạy tuần tự thay vì song song. Chúng ta cần thêm một khai báo để kiểm tra. c.
long long sum_range(long long high)
{
  long long i;
  long long s = 0LL;

  for (i = 0LL; i < high; i++)
      s += (long long)i;

  return s;
}
9

pip install pipenv
pipenv install pybind11
pipenv shell

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` test.c -o test_pybind.so
7

như thế này

%import test_pybind
%sum_range=test_pybind.sum_range
%high=1000000000
%brute_force_sum=sum_range(high)
7

Bây giờ biên dịch lại

%import test_pybind
%sum_range=test_pybind.sum_range
%high=1000000000
%brute_force_sum=sum_range(high)
8

Nhập lại đối tượng được chia sẻ đã xây dựng lại - điều này chỉ có thể được thực hiện bằng cách thoát và khởi chạy lại trình thông dịch iPython - và lặp lại lần nữa

%import test_pybind
%sum_range=test_pybind.sum_range
%high=1000000000
%brute_force_sum=sum_range(high)
9

điều này mang lại

%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
0

như bạn mong đợi đối với hai mô-đun

%sum_from_formula=high*(high-1)//2
%sum_from_formula
%difference=sum_from_formula-brute_force_sum
%difference
3 chạy song song

Những điểm chính

  • Có nhiều tùy chọn khi gọi các thư viện C và C++ bên ngoài và lựa chọn tốt nhất có thể phụ thuộc vào mức độ phức tạp của vấn đề của bạn

  • Rõ ràng, có thêm một bước biên dịch và liên kết, nhưng bạn sẽ thực thi nhanh hơn nhiều so với Python thuần túy

    Làm cách nào để gọi thư viện C bằng Python?

    Nó bao gồm các bước sau. .
    Tạo tệp C (. c phần mở rộng) với các chức năng cần thiết
    Tạo tệp thư viện dùng chung (. so extension) bằng trình biên dịch C
    Trong chương trình Python, hãy tạo một ctypes. Phiên bản CDLL từ tệp được chia sẻ
    Cuối cùng, gọi hàm C bằng định dạng {CDLL_instance}

    Python có thể nhập thư viện C không?

    ctypes cho phép bạn tải trực tiếp thư viện C có sẵn vào chương trình Python của mình .

    Python có thể tương tác với C không?

    Nói chung, mã C đã được viết sẵn sẽ không yêu cầu sửa đổi để Python sử dụng . Công việc duy nhất chúng ta cần làm để tích hợp mã C trong Python là ở phía Python. Các bước giao tiếp Python với C bằng Ctypes.

    Python có thể sử dụng C++ DLL không?

    Chạy Tập lệnh Python. Với tập lệnh Python đang mở trong trình chỉnh sửa, nhấn nút F5 để chạy tập lệnh. Thao tác này sẽ lưu mã Python, ẩn cửa sổ Trình soạn thảo Python và sau đó thực thi mã Python sẽ gọi DLL C++ .