Đôi khi bạn cần phải viết mã numpy không thành ngữ nếu bạn thực sự muốn tăng tốc tính toán mà bạn không thể làm với numpy bản địa.
numba
biên dịch mã python của bạn ở mức độ thấp C. Vì rất nhiều numpy thường nhanh như C, nên điều này chủ yếu là hữu ích nếu vấn đề của bạn không cho vay để vector hóa tự nhiên với numpy. Đây là một ví dụ (trong đó tôi giả sử rằng các chỉ số tiếp giáp và được sắp xếp, cũng được phản ánh trong dữ liệu mẫu):
import numpy as np
import numba
# use the inflated example of roganjosh https://stackoverflow.com/a/58788534
data = [1.00, 1.05, 1.30, 1.20, 1.06, 1.54, 1.33, 1.87, 1.67]
index = [0, 0, 1, 1, 1, 1, 2, 3, 3]
data = np.array(data * 500) # using arrays is important for numba!
index = np.sort(np.random.randint(0, 30, 4500))
# jit-decorate; original is available as .py_func attribute
@numba.njit('f8[:](f8[:], i8[:])') # explicit signature implies ahead-of-time compile
def diffmedian_jit(data, index):
res = np.empty_like(data)
i_start = 0
for i in range(1, index.size):
if index[i] == index[i_start]:
continue
# here: i is the first _next_ index
inds = slice(i_start, i) # i_start:i slice
res[inds] = data[inds] - np.median(data[inds])
i_start = i
# also fix last label
res[i_start:] = data[i_start:] - np.median(data[i_start:])
return res
Và đây là một số thời gian sử dụng %timeit
phép thuật của IPython :
>>> %timeit diffmedian_jit.py_func(data, index) # non-jitted function
... %timeit diffmedian_jit(data, index) # jitted function
...
4.27 ms ± 109 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
65.2 µs ± 1.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Sử dụng dữ liệu ví dụ được cập nhật trong câu hỏi những con số này (tức là thời gian chạy của hàm python so với thời gian chạy của funcio được tăng tốc JIT) là
>>> %timeit diffmedian_jit.py_func(data, groups)
... %timeit diffmedian_jit(data, groups)
2.45 s ± 34.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
93.6 ms ± 518 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Điều này lên tới tốc độ tăng tốc 65 lần trong trường hợp nhỏ hơn và tăng tốc 26 lần trong trường hợp lớn hơn (tất nhiên là so với mã vòng lặp chậm) bằng cách sử dụng mã tăng tốc. Một nhược điểm khác là (không giống như vector hóa thông thường với numpy bản địa), chúng tôi không cần thêm bộ nhớ để đạt được tốc độ này, tất cả là về mã cấp thấp được tối ưu hóa và biên dịch cuối cùng được chạy.
Hàm trên giả định rằng mảng int numpy là int64
theo mặc định, đây không thực sự là trường hợp trên Windows. Vì vậy, một giải pháp thay thế là xóa chữ ký khỏi lệnh gọi đến numba.njit
, kích hoạt quá trình biên dịch đúng lúc. Nhưng điều này có nghĩa là hàm sẽ được biên dịch trong lần thực hiện đầu tiên, có thể can thiệp vào kết quả thời gian (chúng ta có thể thực hiện chức năng một lần theo cách thủ công, sử dụng các loại dữ liệu đại diện hoặc chỉ chấp nhận rằng việc thực hiện thời gian đầu tiên sẽ chậm hơn nhiều, nên được bỏ qua). Đây chính xác là những gì tôi đã cố gắng ngăn chặn bằng cách chỉ định một chữ ký, kích hoạt việc biên dịch trước thời hạn.
Dù sao, trong trường hợp JIT đúng cách, người trang trí chúng ta cần chỉ là
@numba.njit
def diffmedian_jit(...):
Lưu ý rằng các thời gian trên mà tôi đã hiển thị cho chức năng biên dịch jit chỉ áp dụng khi chức năng đã được biên dịch. Điều này xảy ra ở định nghĩa (với phần tổng hợp háo hức, khi chữ ký rõ ràng được chuyển đếnnumba.njit
) hoặc trong khi gọi hàm đầu tiên (với biên dịch lười, khi không có chữ ký nào được chuyển đến numba.njit
). Nếu hàm chỉ được thực hiện một lần thì thời gian biên dịch cũng cần được xem xét cho tốc độ của phương thức này. Nó thường chỉ có giá trị biên dịch các hàm nếu tổng thời gian biên dịch + thực thi ít hơn thời gian chạy không biên dịch (điều này thực sự đúng trong trường hợp trên, trong đó hàm python gốc rất chậm). Điều này chủ yếu xảy ra khi bạn đang gọi chức năng biên dịch của bạn rất nhiều lần.
Như max9111 lưu ý trong một nhận xét, một tính năng quan trọng của numba
là cache
từ khóa để jit
. Chuyển cache=True
đến numba.jit
sẽ lưu trữ chức năng đã biên dịch vào đĩa, để trong lần thực hiện tiếp theo của mô-đun python đã cho, chức năng sẽ được tải từ đó thay vì được biên dịch lại, điều này một lần nữa có thể giúp bạn chạy trong thời gian dài.
scipy.ndimage.median
gợi ý trong câu trả lời liên kết? Dường như với tôi rằng nó cần một số lượng bằng nhau cho mỗi nhãn. Hay tôi đã bỏ lỡ điều gì?