Python JIT có được biên dịch không

Ngôn ngữ biên dịch được viết bằng mã có thể được thực thi trực tiếp trên bộ xử lý của máy tính. Trình biên dịch là một chương trình đặc biệt xử lý các câu lệnh được viết bằng một ngôn ngữ lập trình cụ thể và biến chúng thành ngôn ngữ máy hoặc "mã" mà bộ xử lý của máy tính sử dụng

Ngôn ngữ được giải thích là bất kỳ ngôn ngữ lập trình nào chưa có trong "mã máy" trước thời gian chạy. Không giống như các ngôn ngữ được biên dịch, bản dịch của ngôn ngữ được thông dịch không diễn ra trước. Quá trình dịch xảy ra cùng lúc với chương trình đang được thực thi

Python với tư cách là một ngôn ngữ lập trình không có gì để nói về việc đó là ngôn ngữ lập trình được biên dịch hay giải thích, chỉ có việc triển khai ngôn ngữ đó. Các thuật ngữ được giải thích hoặc biên dịch không phải là thuộc tính của ngôn ngữ mà là thuộc tính của việc triển khai. Chương trình Python chạy trực tiếp từ mã nguồn. vì vậy, Python sẽ thuộc mã byte được giải thích. Các. mã nguồn py lần đầu tiên được biên dịch thành mã byte dưới dạng. pyc. Mã byte này có thể được diễn giải [CPython chính thức] hoặc được biên dịch JIT [PyPy]. Mã nguồn Python [. py] cũng có thể được biên dịch thành mã byte khác như IronPython [. Net] hoặc Jython [JVM]. Có nhiều triển khai ngôn ngữ Python. Cái chính thức là một mã byte được giải thích một. Cũng có các triển khai biên dịch JIT mã byte

Như nhận xét kết luận, Python [Cpython] không phải là thời gian được biên dịch thực sự cũng như ngôn ngữ thông dịch thuần túy mà nó được gọi là ngôn ngữ thông dịch

Tìm kiếm một công việc Python?

Rất có thể bạn sẽ cần chứng minh rằng bạn biết cách làm việc với Python. Các câu hỏi phỏng vấn Python này được thiết kế đặc biệt để giúp bạn làm quen với bản chất của các câu hỏi mà bạn có thể gặp phải trong cuộc phỏng vấn về chủ đề Lập trình Python. Dưới đây là các câu hỏi Phỏng vấn Python mẫu mục tiêu hàng đầu và câu trả lời của chúng được cung cấp ngay bên dưới cho chúng. Những câu hỏi mẫu này được đóng khung bởi nhóm chuyên gia của chúng tôi, những người đào tạo về Python để cung cấp cho bạn ý tưởng về loại câu hỏi có thể được hỏi trong cuộc phỏng vấn

Tại Starschema, chúng tôi không ngừng tìm cách tăng tốc một số tác vụ tính toán chuyên sâu mà chúng tôi đang xử lý. Vì phần lớn công việc của chúng tôi liên quan đến xử lý hình ảnh, điều này có nghĩa là chúng tôi đặc biệt quan tâm đến bất kỳ thứ gì tạo ra phép tính ma trận — đôi khi trên các tenxơ khá lớn, e. g. hình ảnh vệ tinh hoặc y sinh có độ phân giải cao––dễ dàng hơn và nhanh hơn. Bởi vì hình ảnh thường có ở dạng đa kênh hoặc thậm chí là siêu phổ, nên bất kỳ thứ gì giúp xử lý chúng nhanh hơn đều là một lợi ích, việc loại bỏ những giây quý giá mà trên các tập dữ liệu lớn có thể dễ dàng tạo ra sự khác biệt trong nhiều ngày.

Cho đến gần đây, không có gì lạ khi viết mã phát triển bằng ngôn ngữ cấp cao với hỗ trợ máy học và khoa học dữ liệu tốt, như Python, nhưng viết lại và triển khai nó bằng C hoặc C ++, để có tốc độ thô [thực sự, một trong những động lực . ]. Python rất tốt để đưa ra các ý tưởng định lượng của bạn một cách rõ ràng và ngắn gọn, nhưng các vòng lặp bên trong Python luôn bị chậm do không có thông tin về loại. Hệ thống gõ vịt của Python thực sự trở nên tồi tệ khi việc không gõ này tạo ra mã và hướng dẫn không cần thiết, dẫn đến các vòng lặp bên trong tương đối chậm. Tuy nhiên, gần đây, các giải pháp đã được dự kiến ​​để giải quyết vấn đề này. Đầu tiên trong số này là Cython - đưa các loại C vào mã Python của bạn. Nhìn chung, đây là một phương pháp khá tốn công sức để tăng tốc mã của bạn, mặc dù có rất nhiều mã chuyên sâu về tính toán được viết bằng Cython, bao gồm cả mã mà bạn gần như chắc chắn đã sử dụng — chẳng hạn như phần lớn ngăn xếp SciPy và hầu hết tất cả

Vấn đề là 'Cythonising' mã của bạn có thể tốn thời gian và thường đầy thách thức đòi hỏi kiến ​​thức sâu về C để giải quyết. Điều gì sẽ xảy ra nếu chúng ta có một cách tốt hơn để lấy mã byte hiệu quả từ mã Python chậm nhưng dễ hiểu của mình?

Nhập Numba

Numba là cái được gọi là trình biên dịch JIT [just-in-time]. Nó nhận các hàm Python được chỉ định bởi các chú thích cụ thể [sẽ nói thêm về điều đó sau] và biến đổi nhiều nhất có thể — thông qua trình biên dịch LLVM [Máy ảo cấp thấp] — thành CPU và GPU hiệu quả [thông qua CUDA cho GPU Nvidia và HSA cho GPU AMD . Khi ở Cython, bạn có các công cụ để sử dụng trực tiếp các loại C, nhưng phải cố gắng hết sức để thực sự có thể làm như vậy, Numba thực hiện hầu hết các công việc nặng nhọc cho bạn

Cách đơn giản nhất để bắt đầu với Numba cũng dễ dàng như gắn công cụ trang trí

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
7 vào chức năng của bạn. Hãy xem xét hàm sau, thực hiện một phép tính thừa số LU đơn giản và khá vụng về

import numpy as npdef numpy_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]

Lưu ý rằng vì đây là hàm đo nên nó không trả về giá trị, nó chỉ tính toán sự phân tách. Như bạn có thể thấy, đối với ma trận vuông

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
8, thời gian chạy sẽ theo thứ tự n², do phép lặp lồng nhau. Cách tốt nhất để tăng tốc mã này là gì?

Tất nhiên, chúng ta có thể viết lại nó trong Cython. Mặt khác, Numba mang đến cho chúng ta sự tiện lợi khi chỉ cần áp đặt một người trang trí

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]

Thông qua trang trí đơn giản đó, mã đã chạy nhanh hơn đáng kể [tức là mã đã có cơ hội biên dịch trong lần chạy đầu tiên] — nhanh hơn khoảng 23 lần so với mã NumPy cho ma trận

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
9

OK, vậy nó hoạt động như thế nào?

Không giống như Cython, chúng tôi hoàn toàn không phải truyền lại mã của mình. Gần như là Numba biết chúng tôi muốn làm gì và tạo mã biên dịch trước hiệu quả. Hóa ra đó là phần lớn những gì nó làm. nó phân tích mã Python, biến nó thành LLVM IR [biểu diễn trung gian], sau đó tạo mã byte cho kiến ​​trúc đã chọn [theo mặc định, kiến ​​trúc mà thời gian chạy Python của máy chủ đang chạy trên đó]. Điều này cho phép các cải tiến bổ sung, chẳng hạn như song song hóa và biên dịch cho CUDA––với sự hỗ trợ gần như phổ biến cho LLVM, mã có thể được tạo để chạy trên nhiều loại kiến ​​trúc [x86, x86_64, PPC, ARMv7, ARMv8] và

Hạn chế là Numba theo định nghĩa chỉ thực hiện một tập hợp con nghiêm ngặt của Python. May mắn thay, Numba xử lý việc này theo hai cách

  • Numba hỗ trợ rất rộng rãi cho các hàm NumPy [xem danh sách tại đây] và các tính năng của Python [xem danh sách tại đây] — mặc dù đáng chú ý là nó không hỗ trợ trình xử lý ngữ cảnh [
    import numpy as np
    import numba
    @numba.jit[]
    def numba_LUdet[A: np.ndarray]:
    y = [1.0]
    n = A.shape[0]

    with np.errstate[invalid = 'ignore']:
    for i in range[n]:
    y[0] = y[0] * A[i, i]
    for j in range[i+1, n]:
    A[j][i] = A[j][i]/A[i][i]
    A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
    0 biểu thức] và xử lý ngoại lệ [
    import numpy as np
    import numba
    @numba.jit[]
    def numba_LUdet[A: np.ndarray]:
    y = [1.0]
    n = A.shape[0]

    with np.errstate[invalid = 'ignore']:
    for i in range[n]:
    y[0] = y[0] * A[i, i]
    for j in range[i+1, n]:
    A[j][i] = A[j][i]/A[i][i]
    A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
    1,
    import numpy as np
    import numba
    @numba.jit[]
    def numba_LUdet[A: np.ndarray]:
    y = [1.0]
    n = A.shape[0]

    with np.errstate[invalid = 'ignore']:
    for i in range[n]:
    y[0] = y[0] * A[i, i]
    for j in range[i+1, n]:
    A[j][i] = A[j][i]/A[i][i]
    A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
    0,
    import numpy as np
    import numba
    @numba.jit[]
    def numba_LUdet[A: np.ndarray]:
    y = [1.0]
    n = A.shape[0]

    with np.errstate[invalid = 'ignore']:
    for i in range[n]:
    y[0] = y[0] * A[i, i]
    for j in range[i+1, n]:
    A[j][i] = A[j][i]/A[i][i]
    A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
    1]
  • Trừ khi chạy ở chế độ
    import numpy as np
    import numba
    @numba.jit[]
    def numba_LUdet[A: np.ndarray]:
    y = [1.0]
    n = A.shape[0]

    with np.errstate[invalid = 'ignore']:
    for i in range[n]:
    y[0] = y[0] * A[i, i]
    for j in range[i+1, n]:
    A[j][i] = A[j][i]/A[i][i]
    A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
    2 [xem bên dưới], Numba sẽ cố gắng tạo mã byte được tối ưu hóa và nếu không làm như vậy, chỉ cần thử tạo một hàm Python [điều này được gọi là 'chế độ đối tượng' trong Numba]

Chế độ đối tượng so với chế độ nopython

Nói chung, lợi ích lớn nhất của Numba là không giống như Cython, bạn không cần phải viết lại toàn bộ chức năng của mình. Tất cả những gì bạn cần làm là thêm tiền tố trang trí

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
3, như đã thấy ở trên. Điều này đặt Numba ở chế độ lái tự động, cho phép nó xác định xem liệu nó có thể thực hiện điều gì đó với mã hay không và để nguyên chức năng như đã viết nếu không thể. Điều này được gọi là 'chế độ đối tượng' và có nghĩa là nếu quá trình biên dịch JIT không thành công do một số hoặc tất cả thân hàm không được Numba hỗ trợ, thì nó sẽ biên dịch hàm như một đối tượng Python thông thường. Tuy nhiên, rất có thể, kết quả vẫn sẽ nhanh hơn vì nó có thể tối ưu hóa một số vòng lặp bằng cách nâng vòng lặp, vì vậy, nó chắc chắn đáng để thử

Nhưng nơi Numba thực sự bắt đầu tỏa sáng là khi bạn biên dịch bằng chế độ

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
2, sử dụng trình trang trí
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
5 hoặc
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
6. Trong trường hợp này, Numba sẽ ngay lập tức cho rằng bạn biết bạn đang làm gì và cố gắng biên dịch mà không tạo mã đối tượng Python [và đưa ra một ngoại lệ nếu nó không thể làm như vậy]. Sự khác biệt về thời gian thực hiện giữa đối tượng và chế độ
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
2 có thể từ 20% đến 40 lần [. ]

Trong thực tế, tôi đã tìm thấy cách tiếp cận tốt nhất là tái cấu trúc và trích xuất mã hoàn toàn có thể tối ưu hóa và tối ưu hóa mã đó ở chế độ

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
2. Phần còn lại có thể được giữ dưới dạng các hàm Python thuần túy. Điều này tối đa hóa lợi ích tối ưu hóa tổng thể mà không tốn quá nhiều chi phí biên dịch [thêm về chi phí này trong phần tiếp theo] một cách không cần thiết

Khi mã đối tượng được tạo, Numba vẫn có khả năng 'nâng vòng lặp'. Điều này có nghĩa là tự động 'rút ra' một vòng lặp từ một mã không phải là JITtable, JIT biên dịch nó và xử lý nó như thể nó là một hàm

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
2 JITted riêng biệt. Mặc dù đây là một thủ thuật hữu ích, nhưng tốt nhất là bạn nên tự mình làm điều đó một cách rõ ràng

tổng hợp chi phí

Vì trình biên dịch JIT của Numba phải biên dịch hàm thành mã byte, nên sẽ có một chi phí không thể tránh khỏi — thường được biểu thị bằng lần chạy đầu tiên khá chậm, sau đó là các lần chạy tiếp theo nhanh hơn rất nhiều. Đây là chi phí thời gian của JIT biên dịch một chức năng. Mặc dù việc biên dịch hầu như luôn đáng giá và chỉ cần được thực hiện một lần, nhưng trong các ứng dụng quan trọng về hiệu suất, việc giảm chi phí biên dịch là điều hợp lý. Có hai cách chính để thực hiện nó với Numba. bộ nhớ đệm và biên dịch háo hức

Trình trang trí

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
20 chấp nhận một đối số boolean
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
21. Nếu được đặt thành
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
22, nó sẽ lưu trữ chức năng được biên dịch thành bộ đệm ẩn dựa trên tệp. Nói chung, mỗi khi bạn mở và chạy tập lệnh Python, mọi thứ cần được Numba biên dịch sẽ được biên dịch tại thời điểm đó. Tuy nhiên, nếu bạn lưu trữ kết quả biên dịch, các lần chạy tiếp theo sẽ có thể đọc mã byte từ tệp bộ đệm. Về lý thuyết, bạn cũng có thể phân phối tệp bộ đệm, nhưng vì Numba tối ưu hóa cho kiến ​​trúc cụ thể của bạn [và hỗ trợ một loạt kiến ​​trúc khó hiểu, như được mô tả ở trên], nên nó có thể không hoạt động liên tục. Tuy nhiên, vẫn nên lưu trữ các chức năng trong bộ đệm, biên dịch chúng một lần và sử dụng chúng mọi lúc

Biên soạn háo hức là một cách khác để giải quyết cùng một vấn đề. Phải thừa nhận rằng cách đặt tên hơi gây hiểu nhầm — hầu hết thời gian, các thuật ngữ này được sử dụng để chỉ ra thời điểm một nội dung nào đó được biên dịch [tại thời điểm gọi, tôi. e. lười vs. tốt trước, tôi. e. hăng hái]. Trong trường hợp này, nó đề cập đến một khái niệm có liên quan, nhưng cũng là một khái niệm trải dài trên những gì đang được biên soạn. Xem xét ví dụ sau

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
2

Đây là quá trình biên dịch lười biếng bởi vì––mặc dù có các chú thích gõ Python––chúng tôi chưa cung cấp bất kỳ thông tin nào cho Numba về các đối số có thể có của hàm và do đó, nó sẽ biên dịch mã tại thời điểm gọi tùy thuộc vào loại giá trị của

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
23 và
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
24 là . Mặt khác, quá trình biên soạn háo hức dựa trên việc nói trước với Numba những loại nào sẽ xảy ra

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
8

Định dạng

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
25 [hoặc tương đương với
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
5 của nó] sẽ cho phép trình biên dịch Numba JIT xác định các loại [] và dựa vào đó, tạo mã byte được biên dịch trước. Lưu ý rằng nếu bạn có một hàm được biên dịch háo hức và các đối số của bạn không thể bị ép buộc theo định dạng bạn chỉ định, thì hàm đó sẽ đưa ra một kết quả
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
27

Gọi các chức năng JITted khác

Theo nguyên tắc chung, Numba sẽ không tối ưu hóa đệ quy cho bạn. Nói cách khác, nếu bạn gọi các hàm khác mà chính bạn đã xác định từ một hàm JITted, bạn phải đánh dấu riêng các hàm đó để JITting — Numba sẽ không JIT chúng chỉ vì chúng được gọi trong một hàm JITted. Xem xét ví dụ sau

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
2

Trong trường hợp này, hàm thứ hai không tốn kém về mặt tính toán sẽ được hưởng lợi từ JIT, nhưng tất cả những gì nó làm là một phép lũy thừa đơn giản. Hàm đầu tiên đắt hơn về mặt tính toán chưa được chú thích và do đó sẽ được chạy dưới dạng hàm Python - nghĩa là chậm hơn nhiều. Để tận dụng tối đa Numba, hàm

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
28 cũng phải được cung cấp một bộ trang trí JIT [tốt nhất là
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
5, vì
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
80 của NumPy được triển khai bởi Numba]

Nó nhanh như thế nào?

Để chứng minh những lợi ích của JIT, tôi đã chạy một điểm chuẩn, trong đó tôi đã sử dụng phép phân tách LU hơi vụng về của ma trận vuông từ

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
9 đến
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
82. Như bạn có thể thấy, mã NumPy được tối ưu hóa bởi Numba luôn nhanh hơn ít nhất một bậc so với mã NumPy ngây thơ và nhanh hơn tới hai bậc so với mã Python gốc. Mã
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
83 được gọi trực tiếp, được viết bằng FORTRAN 90 [thông qua SciPy's
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
84, một trình bao bọc xung quanh quy trình
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
85 trong
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
83], nổi lên như người chiến thắng rõ ràng ở kích thước ma trận lớn hơn và hiệu suất của Cython hóa ra chỉ kém hơn một chút so với mã NumPy được tối ưu hóa

Điểm chuẩn phân tách LU cho mã Python, NumPy, mã NumPy, LAPACK và Cython được tối ưu hóa. LAPACK đã được gọi bằng trình bao bọc SciPy. Mã NumPy được tối ưu hóa nhanh hơn khoảng một bậc so với mã NumPy thông thường, trong khi nhanh hơn tới hai bậc so với Python gốc. Mặt khác, mã Cython không nhanh hơn đáng kể, trong khi mã FORTRAN chỉ bắt đầu chạy NumPy được tối ưu hóa ở kích thước ma trận tương đối lớn. Hệ số tối ưu hóa với Numba rõ ràng là cao nhất — mã NumPy [màu cam] và mã NumPy được tối ưu hóa [màu đỏ thẫm] chỉ khác nhau bởi ứng dụng của một công cụ trang trí duy nhất

Tất nhiên, Numba có những hạn chế của nó. Điều quan trọng là, nó chỉ giúp tối ưu hóa một loại vấn đề cụ thể — cụ thể là các quy trình bao gồm các vòng lặp hoặc các cấu trúc lặp đi lặp lại khác. Đối với các phép toán tensor và các khối lượng công việc có độ phức tạp vòng lặp lồng nhau/chu kỳ cao khác, nó sẽ tạo ra sự khác biệt đáng kể. Ngay cả khi bạn cần cơ cấu lại mã của mình để phù hợp với các yêu cầu của Numba, thì việc tái cơ cấu như vậy theo kinh nghiệm của tôi dễ dàng hơn rất nhiều so với việc phải viết lại toàn bộ trong Cython. Hoạt động đồng thời như một giao diện để nhanh chóng tạo ra không chỉ mã CPU nhanh hơn mà cả mã hỗ trợ GPU [thông qua PyCuda] cho một tập hợp con chức năng hạn chế hơn một chút [các hàm toán học mảng NumPy không được hỗ trợ trên CUDA, cũng như các hàm toán học NumPy trong . Để viết mã số, thuật toán xử lý hình ảnh và một số hoạt động nhất định liên quan đến mạng thần kinh, nó nhanh chóng trở thành công cụ được tôi lựa chọn để viết mã nhanh, được tối ưu hóa nhiều

Có nhiều điều về Numba hơn là tốc độ

Tất nhiên, công việc chính của Numba là tăng tốc các chức năng. Nhưng nó cũng làm một công việc tuyệt vời ở một số thứ khác. Có lẽ yêu thích của tôi trong số này là trình trang trí

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
87, có thể biến bất kỳ hàm cũ nào thành hàm phổ dụng NumPy [thường được gọi là '
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
88']. Nếu bạn có kiến ​​thức cơ bản về R, đôi khi bạn có thể thấy mình đang hồi tưởng một cách bâng khuâng về khả năng véc tơ hóa các hàm của R mà không cần phải bận tâm nhiều.
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
88 là một trình bao bọc được véc tơ hóa, tổng quát hóa một hàm để hoạt động trên các tenxơ được biểu diễn dưới dạng mảng NumPy n chiều [
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
20s], hỗ trợ logic tenxơ như phát sóng, bộ đệm bên trong và truyền kiểu bên trong. Một ví dụ là hàm
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
21, tổng quát hóa hàm cộng [được gọi thông qua toán tử cộng,
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
22] cho các tenxơ có kích thước bất kỳ - bao gồm cả các tenxơ không cùng kích thước, trong đó được sử dụng để điều chỉnh các tenxơ có kích thước khác nhau

Phép thuật với công cụ trang trí véc tơ của Numba. một hàm theo nguyên tố đơn giản có thể được khái quát hóa thành các tenxơ bậc cao hơn không gì khác hơn là gói nó trong một trình trang trí. Tất nhiên, điều này khá kém hiệu quả, vì việc không chỉ định chữ ký cho các vectơ có thể có nghĩa là không thể thực hiện một số tối ưu hóa theo loại cụ thể. Để biết chi tiết về cách viết mã có thể vector hóa tốt trong Numba, vui lòng tham khảo chương tài liệu về trình trang trí vector hóa

Ví dụ, hãy xem xét hàm

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
23. Đây là một hàm chưa được kiểm tra, nhằm mục đích hoạt động trên các giá trị đơn lẻ [mảng kích thước 1, như thông báo lỗi]. Nhưng chỉ bằng cách đơn giản thêm vào trình trang trí
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
24 của Numba, chúng ta có thể khái quát hàm
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
23 thành một hàm hoạt động theo từng phần tử trên các
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
20 của NumPy đại diện cho các tenxơ theo bất kỳ thứ tự nào [số chiều]

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
27 tiến thêm một bước này, cho phép véc tơ hóa mà không cần phải thông minh về phần tử. trong khi
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
24 là tuyệt vời để dễ dàng khái quát hóa một hàm nhận một đầu vào vô hướng duy nhất và trả về một đầu ra vô hướng duy nhất cho các tenxơ bậc cao hơn, thì
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
27 tổng quát hóa điều này hơn nữa bằng cách cho phép các hàm hoạt động trên các tenxơ bậc thấp hoạt động trên các tenxơ bậc cao hơn. Một ví dụ sẽ là mở rộng đường trung bình động có trọng số 1 chiều để hoạt động trên các tenxơ bậc cao hơn []. Bạn có thể đọc thêm về
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
27. Không giống như
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
24,
import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
27 hoạt động thông qua phân bổ trước tenxơ, do đó lấy một đối số của một tenxơ có kích thước mong muốn, sau đó nó sẽ 'lấp đầy' kết quả, thay vì trả về kết quả ở cuối

Khung song song GPU của Numba biên dịch mã Python thành biểu diễn trung gian NVVM [tương đương với LLVM] [IR] và PTX [mã được biên dịch]. Cả hai đều được tuần tự hóa và gửi đến từng nút cụm. Nếu máy khách và nút cụm có cùng kiến ​​trúc CUDA [i. e. cùng loại bộ đa xử lý phát trực tuyến [SM] trên GPU], mã PTX đã giải tuần tự hóa được dịch sang tệp nhị phân CUDA. Nếu kiến ​​trúc khác, NVVM IR được biên dịch lại thành PTX và cuối cùng là nhị phân CUDA

Numba cũng có các tính năng song song hóa, bao gồm song song hóa quy trình với các kiến ​​trúc CUDA khác nhau. Mặc dù vẫn còn đang thử nghiệm, đây là một trong những tính năng thú vị nhất của Numba và dựa trên một khung thông minh để xây dựng một cụm và truyền các chức năng hỗ trợ GPU tới các nhân viên cụm và hỗ trợ rõ ràng của Numba cho tuần tự hóa [sử dụng

import numpy as np
import numba
@numba.jit[]
def numba_LUdet[A: np.ndarray]:
y = [1.0]
n = A.shape[0]

with np.errstate[invalid = 'ignore']:
for i in range[n]:
y[0] = y[0] * A[i, i]
for j in range[i+1, n]:
A[j][i] = A[j][i]/A[i][i]
A[j][i+1:] = A[j][i+1:] - [A[j][i] * A[i][i+1:]]
73] các chức năng. Mặc dù tính năng này thiếu sự trưởng thành của trình biên dịch JIT của Numba và các tính năng cốt lõi khác, nhưng chắc chắn đây là một trong những ứng dụng tương lai thú vị nhất của nó nói chung và hướng tới Numba là nền tảng của kiến ​​trúc hỗ trợ GPU phân tán

coda

Cho dù bạn làm việc trên thị giác máy tính, dự báo tài chính, mô hình thời tiết hay mạng lưới thần kinh sâu. nếu công việc của bạn trong Python liên quan đến tenxơ và hoạt động của tenxơ, rất có thể, trình biên dịch JIT của Numba có thể tạo ra sự khác biệt cho hoạt động của bạn chỉ bằng một trình trang trí duy nhất. Nhưng Numba cũng là một hệ sinh thái nguyên thủy đang phát triển nhanh chóng cho các ứng dụng điện toán hiệu suất cao trong Python có thể được triển khai trong CUDA hoặc một loạt kiến ​​trúc bộ xử lý và hệ điều hành. Với ý nghĩ đó, chắc chắn đây là một trong những dự án thú vị nhất cho đến nay. Cho dù bạn đến để tăng tốc và ở lại để tận hưởng niềm vui của HPC hay chỉ ở đó để xem cách tăng tốc mã Python trong các ứng dụng định lượng, đặc biệt là khi có liên quan đến tenxơ, mà không cần chi phí mã hóa lớn, Numba là thứ mà mọi nhà khoa học dữ liệu làm việc với Python

Python có trình biên dịch JIT không?

Có hai cách phổ biến để biên dịch mã Python - sử dụng trình biên dịch Just-In-Time [JIT] và sử dụng Cython for Ahead of . Sổ ghi chép này chủ yếu minh họa cách tiếp cận JIT.

Tại sao Python JIT không được biên dịch?

Python là một ngôn ngữ có thể có n số lần triển khai. Quay trở lại câu hỏi của chúng ta, có một vài lý do giải thích tại sao Python không/không thể hỗ trợ Biên dịch JIT. Thông thường, các ngôn ngữ được biên dịch như Python khá nhanh so với các ngôn ngữ được dịch .

Python biên dịch JIT là gì?

Trong điện toán, biên dịch just-in-time [JIT] [cũng là biên dịch động hoặc biên dịch trong thời gian chạy] là một cách thực thi mã máy tính bao gồm biên dịch trong khi thực thi một . .

Python có phải là JIT hay trình thông dịch không?

Phần lớn, Python là ngôn ngữ được thông dịch và không phải là ngôn ngữ được biên dịch, mặc dù biên dịch là một bước. Mã Python, được viết bằng. py trước tiên được biên dịch thành mã byte [được thảo luận chi tiết hơn] được lưu trữ với một. pyc hoặc.

Chủ Đề