Python được biên dịch nhanh như c?

Python là ngôn ngữ kịch bản trong khi C là ngôn ngữ lập trình. C/C++ tương đối nhanh so với Python vì khi bạn chạy tập lệnh Python, trình thông dịch của nó sẽ diễn giải từng dòng tập lệnh và tạo đầu ra nhưng trong C, trình biên dịch trước tiên sẽ biên dịch nó và tạo đầu ra được tối ưu hóa đối với . Trong trường hợp các ngôn ngữ khác như Java và. NET, mã byte Java và. NET bytecode tương ứng chạy nhanh hơn Python vì trình biên dịch JIT biên dịch bytecode thành mã gốc khi chạy. CPython không thể có trình biên dịch JIT vì bản chất động của Python gây khó khăn cho việc viết một trình biên dịch

Python được biên dịch nhanh như c?

Sự khác biệt

Như chúng ta đã biết, Python là ngôn ngữ thông dịch, trong khi C là ngôn ngữ biên dịch. Mã được giải thích luôn chậm hơn mã máy trực tiếp vì cần nhiều hướng dẫn hơn để thực hiện lệnh được giải thích hơn là thực hiện lệnh máy thực tế. Trước khi chương trình có thể thực hiện công việc thực tế, CPU phải hiểu các hướng dẫn Python. Vì vậy, trình thông dịch Python kiểm tra từng câu lệnh theo các quy tắc của ngôn ngữ Python, chẳng hạn như phân bổ bộ nhớ để lưu trữ các biến, loại bỏ các khoảng trống và nhận xét từ chương trình và các tác vụ liên quan khác. Quá trình này được lặp lại cho mỗi dòng của chương trình và làm tăng đáng kể chi phí thực thi chương trình.

Python được biên dịch nhanh như c?

C nhanh

Mặt khác, C không mất nhiều thời gian kiểm tra lại mã nguồn và nhanh chóng chuyển thành các lệnh của CPU. Trước khi thực thi, một trình biên dịch riêng biệt sẽ chuyển đổi chương trình ngôn ngữ của con người thành các lệnh của CPU, kiểm tra lỗi, phân bổ bộ nhớ và các biến, loại bỏ các nhận xét và khoảng trống, đồng thời tối ưu hóa các lệnh kết quả. Kết quả của mã được biên dịch được liên kết với các mã được tạo sẵn khác và kết quả là bạn sẽ nhận được các lệnh CPU vững chắc, sẵn sàng thực hiện công việc được giao mà không cần chuẩn bị nhiều. Trong nội bộ, lý do khiến mã Python thực thi chậm hơn là do mã được diễn giải trong thời gian chạy thay vì được biên dịch thành mã gốc tại thời điểm biên dịch

Python chậm và các ngôn ngữ được biên dịch như Rust, C hoặc C++ thì nhanh. Vì vậy, khi ứng dụng của bạn quá chậm, việc viết lại một số mã của bạn trong tiện ích mở rộng đã biên dịch có vẻ như là cách tiếp cận tự nhiên để tăng tốc mọi thứ

Thật không may, các tiện ích mở rộng được biên dịch đôi khi thực sự chậm hơn mã Python tương đương. Và ngay cả khi chúng nhanh hơn, cải thiện hiệu suất có thể ít hơn nhiều so với bạn tưởng tượng, do chi phí ẩn gây ra bởi hai yếu tố

  1. Chi phí cuộc gọi chức năng
  2. Chi phí serialization/deserialization

Hãy xem những chi phí hoạt động ẩn này đến từ đâu và sau đó xem một số giải pháp để khắc phục chúng

Vấn đề #1. Chi phí cuộc gọi

Chi phí hoạt động đầu tiên mà chúng ta sẽ đối mặt là các lệnh gọi hàm. Hãy viết một hàm bằng Cython, một ngôn ngữ biến thể của Python biên dịch thành C và xem mất bao lâu để chạy nó

Đây là hàm Cython

def do_nothing():
    pass

Chúng ta có thể sử dụng chức năng ma thuật IPython

In [1]: from overhead_cythong import do_nothing

In [2]: %timeit do_nothing()
30 ns ± 0.0352 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: def py_do_nothing(): pass

In [4]: %timeit py_do_nothing()
62.5 ns ± 0.114 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
1 để đo lường hiệu suất

In [1]: from overhead_cythong import do_nothing

In [2]: %timeit do_nothing()
30 ns ± 0.0352 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: def py_do_nothing(): pass

In [4]: %timeit py_do_nothing()
62.5 ns ± 0.114 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Gọi hàm Cython nhanh hơn gọi hàm Python, đó là sự thật. Nhưng thậm chí 30 nano giây cũng khá chậm so với tiêu chuẩn của các ngôn ngữ được biên dịch. để so sánh, một hàm C được gọi bởi một hàm C khác có thể chỉ mất 3 nano giây hoặc ít hơn nhiều nếu nó được nội tuyến

Vấn đề #2. (De)chi phí xê-ri hóa

Ngoài chi phí gọi tiện ích mở rộng, còn có chi phí đưa các đối số vào hàm và nhận lại kết quả. Cách Python đại diện cho các đối tượng khác với cách C hoặc Rust đại diện cho nó và do đó cần có quá trình chuyển đổi. Và mã chuyển đổi cũng cần xử lý lỗi trong trường hợp không thành công

Kết quả là nhiều chi phí hơn. Ví dụ: đây là một hàm Cython lấy danh sách các số nguyên trong Python, tính tổng và trả về kết quả

def sum(values):
    cdef long result = 0
    cdef long v
    for v in values:
        result += v
    return result

Chúng ta có thể so sánh hiệu suất với Python

In [1]: from example_cython import sum as cython_sum

In [2]: l = list(range(1000000))

In [3]: sum(l), cython_sum(l)
Out[3]: (499999500000, 499999500000)

In [4]: %timeit sum(l)
7.64 ms ± 27.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [5]: %timeit cython_sum(l)
6.99 ms ± 29.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Mã Cython được biên dịch không nhanh hơn

In [1]: from overhead_cythong import do_nothing

In [2]: %timeit do_nothing()
30 ns ± 0.0352 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: def py_do_nothing(): pass

In [4]: %timeit py_do_nothing()
62.5 ns ± 0.114 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
2 tích hợp sẵn của Python. Và điều đó không có gì đáng ngạc nhiên.
In [1]: from overhead_cythong import do_nothing

In [2]: %timeit do_nothing()
30 ns ± 0.0352 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: def py_do_nothing(): pass

In [4]: %timeit py_do_nothing()
62.5 ns ± 0.114 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
2 được viết bằng C và toán học thực tế khá nhanh như chúng ta sẽ thấy bên dưới. Tất cả thời gian chạy được dành để chuyển đổi các đối tượng Python thành số nguyên C

Vậy làm thế nào để chúng ta giải quyết hai hình thức chi phí này?

Giải pháp số 1. Quản lý dữ liệu bên trong tiện ích mở rộng

Giải pháp đầu tiên là quản lý tất cả dữ liệu của chúng tôi bằng tiện ích mở rộng Rust hoặc C, điều đó có nghĩa là chúng tôi không có tất cả chi phí tuần tự hóa

Đây là những gì NumPy làm với mảng. về lý thuyết, nó có thể đã sử dụng các danh sách Python, nhưng sau đó nó sẽ bị (khử) tuần tự hóa. Vì vậy, thay vào đó, các mảng NumPy lưu trữ các số bên trong không phải dưới dạng danh sách Python của các số nguyên Python, mà là các mảng C của các số nguyên C. Do đó, các thao tác trên mảng NumPy không yêu cầu giải tuần tự hóa mọi mục nhập trong mảng

Ví dụ: chúng ta có thể tạo một mảng NumPy là một dãy số. Điều đó sẽ liên quan đến việc tạo một mảng C với các số nguyên C, ít công việc hơn (và ít bộ nhớ hơn. ) so với danh sách Python của các số nguyên Python. Sau đó, chúng ta có thể tính tổng và tất cả logic đó sẽ ở trong C, không cần giải tuần tự hóa ngoại trừ tổng cuối cùng

In [1]: import numpy as np

In [2]: l = list(range(1_000_000))

In [3]: arr = np.arange(1_000_000)

In [4]: type(arr)
Out[4]: numpy.ndarray

In [5]: sum(l), arr.sum()
Out[5]: (499999500000, 499999500000)

In [6]: %timeit sum(l)
7.68 ms ± 26.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [7]: %timeit arr.sum()
620 µs ± 11 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Mã NumPy mất ít hơn một phần nghìn giây. nó nhanh hơn 12 lần so với Python. Sự thành công

Giải pháp số 2. Chuyển nhiều công việc hơn vào tiện ích mở rộng

Một cách tiếp cận khác mà chúng ta có thể thực hiện là chuyển nhiều tính toán hơn vào phần mở rộng. Trong tất cả các ví dụ của chúng tôi, chúng tôi đã tính tổng một dãy số, thường là từ 0 đến 999.999. Nếu đó là tất cả những gì chúng ta cần làm, chúng ta không cần phải tạo toàn bộ danh sách các số trong Python, chúng ta chỉ cần viết một hàm Cython tính tổng một dãy số nguyên

________số 8_______

Chúng ta có thể đo lường hiệu suất

In [1]: from example_cython import sum_range

In [2]: sum(list(range(1_000_000))), sum_range(0, 1_000_000)
Out[2]: (499999500000, 499999500000)

In [3]: %timeit sum_range(0, 1_000_000)
306 µs ± 359 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Điều đó khá tốt, thậm chí còn nhanh hơn so với triển khai của NumPy, ngoài ra chúng tôi không có chi phí tạo toàn bộ mảng

nhưng chúng ta có thể làm tốt hơn

Hãy chuyển đổi ngôn ngữ và dùng thử Rust. Thật không may, các ràng buộc Rust Python thông qua thư viện PyO3 có chi phí gọi và (khử) tuần tự hóa cao hơn Cython, vì PyO3 là một dự án mới hơn. Điều này đang được cải thiện theo thời gian. PyO3 v0. Ví dụ, bản phát hành 14 đã giảm đáng kể chi phí cuộc gọi chức năng. Về mặt tích cực, Rust có tính năng an toàn bộ nhớ, an toàn luồng và ngôn ngữ cấp cao hơn nhiều mà vẫn có hiệu năng tương tự như C/C++

Trong Rust, chúng ta có thể sử dụng một đối tượng phạm vi không khác nhiều so với các lát cắt của Python

#[pyfunction]
fn sum_range(start: u64, end: u64) -> u64 {
    assert!(start <= end);
    (start..end).sum()
}

Sau đó chúng ta có thể đo lường hiệu suất

In [1]: from example_rust import sum_range

In [2]: sum(list(range(1_000_000))), sum_range(0, 1_000_000)
Out[2]: (499999500000, 499999500000)

In [3]: %timeit sum_range(0, 1_000_000)
165 ns ± 0.0381 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Lưu ý kết quả tính bằng nano giây. Rust nhanh hơn 1.800 lần so với Cython–làm thế nào điều này có thể xảy ra?

Giải pháp tốt nhất. làm ít việc hơn

Hóa ra là khi chúng tôi chuyển nhiều công việc hơn sang một công trình cấp cao như Rust's Ranges, chúng tôi đã có được một giải pháp thông minh hơn nhiều. Tính tổng một mảng số nguyên tùy ý yêu cầu lặp qua tất cả các giá trị và do đó, nó là

In [1]: from overhead_cythong import do_nothing

In [2]: %timeit do_nothing()
30 ns ± 0.0352 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: def py_do_nothing(): pass

In [4]: %timeit py_do_nothing()
62.5 ns ± 0.114 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
4. càng nhiều giá trị trong mảng thì càng mất nhiều thời gian. Và tính tổng một dãy số nguyên liên tiếp có thể được thực hiện theo cách tương tự

Hoặc chúng ta có thể tận dụng thực tế là nó diễn ra liên tiếp, trong trường hợp đó, nó có thể được thực hiện trong một khoảng thời gian cố định, ví dụ như sử dụng

In [1]: from overhead_cythong import do_nothing

In [2]: %timeit do_nothing()
30 ns ± 0.0352 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: def py_do_nothing(): pass

In [4]: %timeit py_do_nothing()
62.5 ns ± 0.114 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
5. Chuỗi công cụ biên dịch LLVM mà Rust sử dụng đủ thông minh để sử dụng phép tính thời gian cố định (hơi khác một chút). Bạn có thể xem kết quả lắp ráp tại đây, nếu bạn quan tâm. Hãy nhớ rằng điều này không thực sự cụ thể cho Rust;

Với sự tối ưu hóa này, không giống như triển khai tính tổng trong vòng lặp, kích thước của phạm vi không quan trọng lắm

In [4]: %timeit sum_range(0, 100)
188 ns ± 0.616 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [5]: %timeit sum_range(0, 100_000_000)
189 ns ± 0.132 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Chúng ta cũng có thể triển khai thuật toán nhanh hơn trong Python

In [1]: from overhead_cythong import do_nothing

In [2]: %timeit do_nothing()
30 ns ± 0.0352 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: def py_do_nothing(): pass

In [4]: %timeit py_do_nothing()
62.5 ns ± 0.114 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
0

Mã Rust của chúng tôi nhanh hơn triển khai Python này, nhưng chỉ một chút. Và việc triển khai Python của chúng tôi vẫn nhanh hơn rất nhiều so với các giải pháp Cython hoặc NumPy mà chúng tôi đã sử dụng ở trên. Bằng cách hạn chế vấn đề, chúng tôi đã đưa ra một giải pháp vượt trội về cơ bản và rất có thể nó đủ nhanh để không cần một tiện ích mở rộng đã biên dịch nào cả

Ghi chú. Việc có bất kỳ công cụ hoặc kỹ thuật cụ thể nào sẽ tăng tốc mọi thứ hay không tùy thuộc vào vị trí tắc nghẽn trong phần mềm của bạn

Cần xác định các tắc nghẽn về hiệu suất và bộ nhớ trong mã xử lý dữ liệu Python của riêng bạn?

Python được biên dịch nhanh như c?

Python được biên dịch nhanh như c?

Giảm chi phí mở rộng

Chúng ta đã học được gì về việc tối ưu hóa mã Python?

Bắt đầu bằng cách cố gắng xác định lại vấn đề theo cách ít tốn công sức hơn. Bạn có thể tăng tốc đáng kể bằng cách sử dụng Python cũ đơn giản

Nếu bạn cần phải viết một phần mở rộng

  1. Cố gắng chuyển càng nhiều tính toán càng tốt vào tiện ích mở rộng, để giảm chi phí chuẩn bị Python, chi phí tuần tự hóa và chi phí gọi hàm
  2. Nếu bạn đang xử lý số lượng lớn đối tượng, hãy giảm chi phí tuần tự hóa bằng cách có loại tiện ích mở rộng tùy chỉnh có thể lưu trữ dữ liệu với các chuyển đổi tối thiểu sang/từ Python, cách mà NumPy thực hiện với các đối tượng mảng của nó

Bài viết tiếp theo. Cython, Rust, v.v. chọn ngôn ngữ cho tiện ích mở rộng Python
bài báo trước. Tất cả Python đều chậm, nhưng một số nhanh hơn những con khác


Tìm các tắc nghẽn về hiệu suất và bộ nhớ trong mã xử lý dữ liệu của bạn với trình cấu hình Sciagraph

Các công việc chạy chậm sẽ lãng phí thời gian của bạn trong quá trình phát triển, cản trở người dùng của bạn và tăng chi phí tính toán của bạn. Tăng tốc mã của bạn và bạn sẽ lặp lại nhanh hơn, có người dùng hài lòng hơn và bám sát ngân sách của mình—nhưng trước tiên, bạn cần xác định nguyên nhân của sự cố

Tìm các nút thắt cổ chai về hiệu suất và bộ nhớ trong khoa học dữ liệu của bạn Công việc Python với trình hồ sơ Sciagraph. Cấu hình trong quá trình phát triển và sản xuất, với sự hỗ trợ đa xử lý và hơn thế nữa

Python được biên dịch nhanh như c?

Tìm hiểu các kỹ năng kỹ thuật phần mềm Python thực tế mà bạn có thể sử dụng trong công việc của mình

Đăng ký nhận bản tin của tôi và tham gia cùng hơn 6700 nhà phát triển Python và nhà khoa học dữ liệu học các công cụ và kỹ thuật thực tế, từ hiệu suất Python đến đóng gói Docker, với một bài viết mới miễn phí trong hộp thư đến của bạn mỗi tuần

Python được biên dịch nhanh hơn C?

Đó là 450 triệu vòng lặp trong một giây, nhanh hơn Python 45 lần . Hơn nữa, C có thể được biên dịch ở chế độ tối ưu hóa để có hiệu suất tốt hơn.

Python có chạy nhanh hơn khi được biên dịch không?

Biên dịch Python Mã máy mà bạn tạo khi biên dịch mã sẽ luôn chạy nhanh hơn mã byte được giải thích . Bạn sẽ tìm thấy một số trình biên dịch Python có sẵn bao gồm Numpa, Nuitka, pypi và Cython.

Python có thể hiệu quả như C không?

Do là ngôn ngữ được thông dịch và nhập động, Python cho phép tốc độ tạo mẫu cực nhanh nhưng không thể cạnh tranh với thời gian chạy của C++, C, Fortran, cũng như . .

Cython có nhanh bằng C không?

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