Bạn có thể sử dụng cú pháp C trong Python 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ạn có thể sử dụng cú pháp C trong Python không?
Bởi Serdar Yegulalp

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

What is Cython? Python at the speed of CJamesboy 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ể khắc phục nhiều hạn chế gốc của Python hoặc vượt qua chúng hoàn toàn—mà không cần 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ả cá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 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 để 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

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 bằng một tập lệnh Python khác, bởi vì nó sử dụng

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;

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


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/

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 các cấu trúc Python vốn 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 thông dịch viên toàn cầu Python, hoặc GIRL, các luồng được đồng bộ hóa 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 kéo dài, thì 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 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, với tất cả các biến của nó và lệnh gọi hàm nội tuyế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

cython reportIDG

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ấu trúc cấp C trong thư viện tại thời điểm biên dịch để có liên kết 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 nêu


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ỳ

Làm cách nào để sử dụng hàm C trong Python?

Hàm C luôn có hai đối số, được đặt tên theo quy ước là self và args. Đối số self trỏ đến đối tượng mô-đun cho các chức năng cấp mô-đun; . Đối số args sẽ là một con trỏ tới một đối tượng bộ Python chứa các đối số

Python có thể tích hợp với C không?

Nói chung, mã 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 vào Python là ở phía Python . Các bước giao tiếp Python với C bằng Ctypes.

Làm cách nào để đọc tệp C bằng Python?

Đọc tệp theo từng dòng . Đây có lẽ là cách tiếp cận trực quan nhất - mở tệp bằng hàm open(), đọc từng dòng tệp bằng phương thức readline() và xuất dòng ngay sau khi đọc< . .