Python và C có thể hoạt động cùng nhau không?

Một siêu bộ Python biên dịch thành C, Cython kết hợp sự dễ dàng của Python với tốc độ của mã gốc. Dưới đây là hướng dẫn nhanh để tận dụng tối đa Cython trong các chương trình Python của bạn

Bởi Serdar Yegulalp

Nhà văn cao cấp, InfoWorld.

Jamesboy Nuchaikong / Shutterstock

Mục lục

Cho xem nhiều hơn

Python nổi tiếng là một trong những ngôn ngữ lập trình tiện lợi nhất, được trang bị phong phú và hết sức hữu ích. Tốc độ thực hiện?

Nhập Cython. Ngôn ngữ Cython là một siêu ngôn ngữ của Python biên dịch thành C. Điều này giúp tăng hiệu suất có thể từ vài phần trăm đến vài bậc độ lớn, tùy thuộc vào nhiệm vụ hiện tại. Đối với công việc bị ràng buộc bởi các loại đối tượng gốc của Python, tốc độ tăng tốc sẽ không lớn. Nhưng đối với các hoạt động số hoặc bất kỳ hoạt động nào không liên quan đến nội bộ của Python, lợi nhuận có thể rất lớn

Với Cython, bạn có thể bỏ qua nhiều giới hạn bản địa của Python hoặc vượt qua chúng hoàn toàn—mà không phải từ bỏ sự dễ dàng và tiện lợi của Python. Trong bài viết này, chúng ta sẽ tìm hiểu các khái niệm cơ bản đằng sau Cython và tạo một ứng dụng Python đơn giản sử dụng Cython để tăng tốc một trong các chức năng của nó

Biên dịch Python sang C

Mã Python có thể thực hiện cuộc gọi trực tiếp vào các mô-đun C. Các mô-đun C đó có thể là thư viện C chung hoặc thư viện được xây dựng riêng để hoạt động với Python. Cython tạo ra loại mô-đun thứ hai. Thư viện C nói chuyện với nội bộ của Python và có thể đi kèm với mã Python hiện có

Mã Cython trông rất giống mã Python, theo thiết kế. Nếu bạn cung cấp cho trình biên dịch Cython một chương trình Python [Python 2. x và Python3. x đều được hỗ trợ], Cython sẽ chấp nhận nó như hiện tại, nhưng không có khả năng tăng tốc riêng nào của Cython sẽ phát huy tác dụng. Nhưng nếu bạn trang trí mã Python bằng các chú thích loại theo cú pháp đặc biệt của Cython, thì Cython sẽ có thể thay thế các đối tượng Python chậm tương đương với C nhanh

Lưu ý rằng cách tiếp cận của Cython là gia tăng. Điều đó có nghĩa là nhà phát triển có thể bắt đầu với ứng dụng Python hiện có và tăng tốc ứng dụng đó bằng cách thực hiện các thay đổi tại chỗ đối với mã, thay vì viết lại toàn bộ ứng dụng

Cách tiếp cận này phù hợp với bản chất của các vấn đề về hiệu suất phần mềm nói chung. Trong hầu hết các chương trình, phần lớn mã sử dụng nhiều CPU tập trung ở một số điểm nóng—một phiên bản của nguyên tắc Pareto, còn được gọi là quy tắc “80/20”. Do đó, hầu hết mã trong ứng dụng Python không cần phải được tối ưu hóa hiệu suất, chỉ cần một số phần quan trọng. Bạn có thể dịch từng bước các điểm nóng đó sang Cython để đạt được mức tăng hiệu suất mà bạn cần ở nơi quan trọng nhất. Phần còn lại của chương trình có thể vẫn ở dạng Python mà không cần làm thêm

Cách sử dụng Cyton

Hãy xem xét đoạn mã sau, được lấy từ tài liệu của Cython


def f[x]:
    return x**2-x

def integrate_f[a, b, N]:
    s = 0
    dx = [b-a]/N
    for i in range[N]:
        s += f[a+i*dx]
    return s * dx

Đây là một ví dụ về đồ chơi, một cách triển khai hàm tích phân không hiệu quả lắm. Là mã Python thuần túy, nó chậm, vì Python phải chuyển đổi qua lại giữa các loại số gốc của máy và các loại đối tượng bên trong của chính nó

Bây giờ hãy xem xét phiên bản Cython của cùng một mã, với phần bổ sung Cython được gạch dưới


cdef double f[double x]:
    return x**2-x

def integrate_f[double a, double b, int N]:
    cdef int i
    cdef double s, dx
    s = 0
    dx = [b-a]/N
    for i in range[N]:
        s += f[a+i*dx]
    return s * dx

Nếu chúng ta khai báo rõ ràng các loại biến, cho cả tham số của hàm và các biến được sử dụng trong phần thân của hàm [double, int, v.v.], Cython sẽ dịch tất cả những điều này sang C. Chúng ta cũng có thể sử dụng từ khóa cdef để xác định các hàm được triển khai chủ yếu bằng C để tăng tốc độ, mặc dù các hàm đó chỉ có thể được gọi bởi các hàm Cython khác chứ không phải bởi các tập lệnh Python. Trong ví dụ trên, chỉ có thể gọi integrate_f bằng một tập lệnh Python khác, bởi vì nó sử dụng def;

Lưu ý rằng mã thực tế của chúng tôi đã thay đổi rất ít. Tất cả những gì chúng tôi đã làm là thêm khai báo kiểu vào mã hiện có để tăng hiệu suất đáng kể

Giới thiệu về cú pháp 'Python thuần túy' của Cython

Cython cung cấp hai cách để viết mã của nó. Ví dụ trên sử dụng cú pháp ban đầu của Cython, được phát triển trước khi cú pháp gợi ý kiểu Python hiện đại ra đời. Nhưng một cú pháp Cython mới hơn được gọi là chế độ Python thuần túy cho phép bạn viết mã gần với cú pháp của Python hơn, bao gồm cả các khai báo kiểu

Đoạn mã trên, sử dụng chế độ Python thuần túy, sẽ giống như thế này


import cython

@cython.cfunc
def f[x: cython.double] -> cython.double:
    return x**2 - x

def integrate_f[a: cython.double, b: cython.double, N: cython.int]:
    s: cython.double = 0
    dx: cython.double = [b - a] / N
    i: cython.int
    for i in range[N]:
        s += f[a + i * dx]
    return s * dx

Chế độ Python thuần túy Cython dễ hiểu hơn một chút và cũng có thể được xử lý bằng các công cụ linting Python gốc. Nó cũng cho phép bạn chạy mã nguyên trạng mà không cần biên dịch [mặc dù không có lợi ích về tốc độ]. Thậm chí có thể chạy mã có điều kiện tùy thuộc vào việc nó có được biên dịch hay không. Thật không may, một số tính năng của Cython, chẳng hạn như làm việc với các thư viện C bên ngoài, không khả dụng ở chế độ Python thuần túy

Tìm hiểu thêm về chế độ Python thuần túy của Cython

Bạn muốn tiến xa hơn với Cython?

Ưu điểm của Cyton

Ngoài khả năng tăng tốc mã bạn đã viết, Cython còn mang lại một số lợi thế khác

Hiệu suất nhanh hơn khi làm việc với các thư viện C bên ngoài

Các gói Python như NumPy bọc các thư viện C trong giao diện Python để giúp chúng dễ dàng làm việc với. Tuy nhiên, qua lại giữa Python và C thông qua các trình bao bọc đó có thể làm mọi thứ chậm lại. Cython cho phép bạn nói chuyện trực tiếp với các thư viện bên dưới mà không cần Python cản trở. [Các thư viện C++ cũng được hỗ trợ. ]

Bạn có thể sử dụng cả quản lý bộ nhớ C và Python

Nếu bạn sử dụng các đối tượng Python, chúng sẽ được quản lý bộ nhớ và thu gom rác giống như trong Python thông thường. Nếu muốn, bạn cũng có thể tạo và quản lý các cấu trúc cấp C của riêng mình và sử dụng malloc/


cdef double f[double x]:
    return x**2-x

def integrate_f[double a, double b, int N]:
    cdef int i
    cdef double s, dx
    s = 0
    dx = [b-a]/N
    for i in range[N]:
        s += f[a+i*dx]
    return s * dx
0 để làm việc với chúng. Chỉ cần nhớ để làm sạch sau khi chính mình

Bạn có thể lựa chọn an toàn hoặc tốc độ khi cần thiết

Cython tự động thực hiện kiểm tra thời gian chạy đối với các sự cố phổ biến xuất hiện trong C, chẳng hạn như truy cập vượt quá giới hạn trên một mảng, bằng cách trang trí và chỉ thị trình biên dịch [e. g. ,


cdef double f[double x]:
    return x**2-x

def integrate_f[double a, double b, int N]:
    cdef int i
    cdef double s, dx
    s = 0
    dx = [b-a]/N
    for i in range[N]:
        s += f[a+i*dx]
    return s * dx
1]. Do đó, mã C do Cython tạo theo mặc định an toàn hơn nhiều so với mã C cuộn thủ công, mặc dù có khả năng phải trả giá bằng hiệu năng thô.

Nếu bạn tự tin rằng mình sẽ không cần những kiểm tra đó trong thời gian chạy, bạn có thể tắt chúng để tăng thêm tốc độ, trên toàn bộ mô-đun hoặc chỉ trên các chức năng được chọn

Cython cũng cho phép bạn truy cập nguyên bản các cấu trúc Python sử dụng giao thức bộ đệm để truy cập trực tiếp vào dữ liệu được lưu trữ trong bộ nhớ [không cần sao chép trung gian]. Chế độ xem bộ nhớ của Cython cho phép bạn làm việc với các cấu trúc đó ở tốc độ cao và với mức độ an toàn phù hợp với nhiệm vụ. Chẳng hạn, dữ liệu thô bên dưới chuỗi Python có thể được đọc theo cách này [nhanh] mà không cần phải trải qua thời gian chạy Python [chậm]

Mã Cython C có thể hưởng lợi từ việc phát hành GIL

Khóa phiên dịch toàn cầu của Python, hoặc GIL, đồng bộ hóa các luồng trong trình thông dịch, bảo vệ quyền truy cập vào các đối tượng Python và quản lý tranh chấp tài nguyên. Nhưng GIL đã bị chỉ trích rộng rãi như một trở ngại đối với Python hoạt động tốt hơn, đặc biệt là trên các hệ thống đa lõi

Nếu bạn có một đoạn mã không tham chiếu đến các đối tượng Python và thực hiện một thao tác chạy dài, bạn có thể đánh dấu đoạn mã đó bằng lệnh 


cdef double f[double x]:
    return x**2-x

def integrate_f[double a, double b, int N]:
    cdef int i
    cdef double s, dx
    s = 0
    dx = [b-a]/N
    for i in range[N]:
        s += f[a+i*dx]
    return s * dx
2 để cho phép đoạn mã đó chạy mà không cần GIL. Điều này giải phóng trình thông dịch Python để tạm thời làm những việc khác và cho phép mã Cython sử dụng nhiều lõi [với công việc bổ sung]

Cython có thể được sử dụng để che khuất mã Python nhạy cảm

Các mô-đun Python rất dễ dịch ngược và kiểm tra, nhưng các tệp nhị phân đã biên dịch thì không. Khi phân phối ứng dụng Python cho người dùng cuối, nếu bạn muốn bảo vệ một số mô-đun của nó khỏi bị rình mò thông thường, bạn có thể làm như vậy bằng cách biên dịch chúng bằng Cython

Tuy nhiên, xin lưu ý rằng việc che giấu như vậy là tác dụng phụ của các khả năng của Cython, không phải là một trong các chức năng dự định của nó. Ngoài ra, không thể dịch ngược hoặc thiết kế ngược một tệp nhị phân nếu một tệp được dành riêng hoặc đủ quyết tâm. Và, như một quy tắc chung, các bí mật, chẳng hạn như mã thông báo hoặc thông tin nhạy cảm khác, không bao giờ được ẩn trong các tệp nhị phân—chúng thường rất dễ bị lộ bằng một kết xuất hex đơn giản

Bạn có thể phân phối lại các mô-đun do Cython biên dịch

Nếu bạn đang xây dựng một gói Python để phân phối lại cho những người khác, nội bộ hoặc thông qua PyPI, thì các thành phần do Cython biên dịch có thể được đưa vào gói đó. Các thành phần đó có thể được biên dịch trước cho các kiến ​​trúc máy cụ thể, mặc dù bạn sẽ cần xây dựng các bánh xe Python riêng cho từng kiến ​​trúc. Nếu không, người dùng có thể biên dịch mã Cython như một phần của quy trình thiết lập, miễn là có trình biên dịch C trên máy đích

Hạn chế của Cython

Hãy nhớ rằng Cython không phải là cây đũa thần. Nó không tự động biến mọi phiên bản mã Python tẻ nhạt thành mã C cực nhanh. Để tận dụng tối đa Cython, bạn phải sử dụng nó một cách khôn ngoan—và hiểu những hạn chế của nó

Tăng tốc tối thiểu cho mã Python thông thường

Khi Cython gặp mã Python, nó không thể dịch hoàn toàn sang C, nó sẽ biến mã đó thành một loạt lệnh gọi C đến phần bên trong của Python. Điều này tương đương với việc đưa trình thông dịch của Python ra khỏi vòng lặp thực thi, giúp mã tăng tốc khiêm tốn từ 15 đến 20 phần trăm theo mặc định. Lưu ý rằng đây là trường hợp tốt nhất; . Đo lường hiệu suất trước và sau để xác định những gì đã thay đổi

Tăng tốc một chút cho cấu trúc dữ liệu Python gốc

Python cung cấp một loạt cấu trúc dữ liệu—chuỗi, danh sách, bộ dữ liệu, từ điển, v.v. Chúng cực kỳ tiện lợi cho các nhà phát triển và chúng có tính năng quản lý bộ nhớ tự động của riêng chúng. Nhưng chúng chậm hơn C thuần túy

Cython cho phép bạn tiếp tục sử dụng tất cả các cấu trúc dữ liệu Python, mặc dù không tăng tốc nhiều. Một lần nữa, điều này là do Cython chỉ đơn giản gọi các API C trong thời gian chạy Python để tạo và thao tác các đối tượng đó. Do đó, các cấu trúc dữ liệu Python hoạt động giống như mã Python được tối ưu hóa cho Cython nói chung. Đôi khi bạn nhận được một sự thúc đẩy, nhưng chỉ một chút. Để có kết quả tốt nhất, hãy sử dụng các biến và cấu trúc C. Tin tốt là Cython giúp bạn dễ dàng làm việc với chúng

Mã Cython chạy nhanh nhất khi ở 'thuần C'

Nếu bạn có một hàm trong C được gắn nhãn với từ khóa cdef, với tất cả các biến và lệnh gọi hàm nội tuyến của nó tới những thứ khác thuần C, thì nó sẽ chạy nhanh nhất có thể. Nhưng nếu chức năng đó tham chiếu đến bất kỳ mã gốc Python nào, chẳng hạn như cấu trúc dữ liệu Python hoặc lệnh gọi đến API Python nội bộ, thì lệnh gọi đó sẽ là nút cổ chai hiệu năng

May mắn thay, Cython cung cấp một cách để phát hiện những tắc nghẽn này. báo cáo mã nguồn cho biết nhanh phần nào trong ứng dụng Cython của bạn là C thuần túy và phần nào tương tác với Python. Ứng dụng càng được tối ưu hóa tốt thì càng ít tương tác với Python

IDG

Báo cáo mã nguồn được tạo cho ứng dụng Cython. Các khu vực màu trắng là C tinh khiết; . Một chương trình Cython được tối ưu hóa tốt sẽ có càng ít màu vàng càng tốt. Dòng cuối cùng được mở rộng hiển thị mã C làm cơ sở cho mã Cython tương ứng của nó. Dòng 8 có màu vàng do mã xử lý lỗi mà Cython xây dựng cho phép chia theo mặc định, mặc dù điều đó có thể bị vô hiệu hóa

Cython và NumPy

Cython cải thiện việc sử dụng các thư viện xử lý số của bên thứ ba dựa trên C như NumPy. Vì mã Cython biên dịch thành C nên nó có thể tương tác trực tiếp với các thư viện đó và loại bỏ các nút cổ chai của Python ra khỏi vòng lặp

Nhưng NumPy, đặc biệt, hoạt động tốt với Cython. Cython có hỗ trợ riêng cho các cấu trúc cụ thể trong NumPy và cung cấp quyền truy cập nhanh vào các mảng NumPy. Và cùng một cú pháp NumPy quen thuộc mà bạn sử dụng trong tập lệnh Python thông thường có thể được sử dụng trong Cython nguyên trạng

Tuy nhiên, nếu bạn muốn tạo các ràng buộc gần nhất có thể giữa Cython và NumPy, bạn cần trang trí thêm mã bằng cú pháp tùy chỉnh của Cython. Chẳng hạn, câu lệnh 


cdef double f[double x]:
    return x**2-x

def integrate_f[double a, double b, int N]:
    cdef int i
    cdef double s, dx
    s = 0
    dx = [b-a]/N
    for i in range[N]:
        s += f[a+i*dx]
    return s * dx
4 cho phép mã Cython xem các cấu trúc cấp C trong thư viện tại thời điểm biên dịch để có các ràng buộc nhanh nhất có thể

Vì NumPy được sử dụng rộng rãi nên Cython hỗ trợ NumPy “ngay lập tức. ” Nếu bạn đã cài đặt NumPy, bạn chỉ cần ghi 


cdef double f[double x]:
    return x**2-x

def integrate_f[double a, double b, int N]:
    cdef int i
    cdef double s, dx
    s = 0
    dx = [b-a]/N
    for i in range[N]:
        s += f[a+i*dx]
    return s * dx
5 trong mã của mình, sau đó thêm phần trang trí khác để sử dụng các chức năng được hiển thị.  

Hồ sơ và hiệu suất của Cython

Bạn có được hiệu suất tốt nhất từ ​​bất kỳ đoạn mã nào bằng cách lập hồ sơ cho nó và tận mắt nhìn thấy các điểm tắc nghẽn. Cython cung cấp các hook cho mô-đun cProfile của Python, vì vậy bạn có thể sử dụng các công cụ định hình riêng của Python, như cProfile, để xem mã Cython của bạn hoạt động như thế nào. [Chúng tôi cũng đã đề cập đến công cụ nội bộ của Cython để tìm hiểu xem mã của bạn được dịch sang C hiệu quả như thế nào. ]

Điều này giúp ghi nhớ trong mọi trường hợp rằng Cython không phải là ma thuật—các phương pháp thực hiện hợp lý trong thế giới thực vẫn được áp dụng. Bạn càng ít chuyển đổi qua lại giữa Python và Cython, ứng dụng của bạn sẽ chạy càng nhanh

Chẳng hạn, nếu bạn có một tập hợp các đối tượng mà bạn muốn xử lý trong Cython, đừng lặp lại nó trong Python và gọi một hàm Cython ở mỗi bước. Chuyển toàn bộ bộ sưu tập tới mô-đun Cython của bạn và lặp lại ở đó. Kỹ thuật này thường được sử dụng trong các thư viện quản lý dữ liệu, vì vậy đây là một mô hình tốt để mô phỏng trong mã của riêng bạn

Chúng tôi sử dụng Python vì nó mang lại sự thuận tiện cho lập trình viên và cho phép phát triển nhanh. Đôi khi năng suất của lập trình viên phải trả giá bằng hiệu suất. Với Cython, chỉ cần thêm một chút nỗ lực là bạn có thể đạt được điều tốt nhất của cả hai thế giới

Có liên quan

  • con trăn
  • Phát triển phần mềm
  • Công cụ phát triển
  • Mã nguồn mở

Serdar Yegulalp là một nhà văn cao cấp tại InfoWorld, tập trung vào học máy, container hóa, devops, hệ sinh thái Python và đánh giá định kỳ

Python làm việc với C như thế nào?

Mã Python có thể gọi trực tiếp vào các mô-đun C . Các mô-đun C đó có thể là thư viện C chung hoặc thư viện được xây dựng riêng để hoạt động với Python. Cython tạo ra loại mô-đun thứ hai. Các thư viện C nói chuyện với nội bộ của Python và có thể được gói cùng với mã Python hiện có.

Học Python và C cùng lúc có tốt không?

Tôi không khuyên bạn nên học nhiều ngôn ngữ lập trình cùng một lúc nếu bạn là người mới bắt đầu. Tuy nhiên, nếu bạn định chọn hai ngôn ngữ bất kỳ, học C++ và Python cùng lúc là một sự kết hợp khá tốt. Hai ngôn ngữ chia sẻ một ngôn ngữ tổ tiên chung, C. Cũng như làm theo cùng một mô hình lập trình

Bạn có thể kết hợp C++ và Python không?

Cũng có thể nhúng Python vào chương trình C++ ; . Không cần phải tự biên dịch lại Python bằng C++.

Chủ Đề