Trong phần này của hướng dẫn, chúng tôi sẽ điều tra cách tăng tốc một số chức năng hoạt động trên gấu trúc bằng ba kỹ thuật khác nhau. Cython, Numba và. Chúng ta sẽ thấy tốc độ cải thiện ~200 khi chúng ta sử dụng Cython và Numba trên chức năng kiểm tra hoạt động theo hàng trên. Sử dụng, chúng tôi sẽ tăng tốc một khoản tiền theo thứ tự ~2 Show
Ghi chú Ngoài việc làm theo các bước trong hướng dẫn này, người dùng quan tâm đến việc nâng cao hiệu suất được khuyến khích cài đặt for pandas. Các phụ thuộc này thường không được cài đặt theo mặc định, nhưng sẽ cải thiện tốc độ nếu có Cython (viết phần mở rộng C cho gấu trúc)Đối với nhiều trường hợp sử dụng, viết pandas bằng Python và NumPy thuần túy là đủ. Tuy nhiên, trong một số ứng dụng nặng tính toán, có thể đạt được tốc độ tăng đáng kể bằng cách giảm tải công việc cho cython Hướng dẫn này giả định rằng bạn đã tái cấu trúc càng nhiều càng tốt trong Python, chẳng hạn bằng cách cố gắng loại bỏ các vòng lặp for và sử dụng vector hóa NumPy. Trước tiên, luôn đáng để tối ưu hóa trong Python Hướng dẫn này trình bày một quy trình “điển hình” của việc tạo cython cho một tính toán chậm. Chúng tôi sử dụng một ví dụ từ tài liệu Cython nhưng trong bối cảnh gấu trúc. Giải pháp cythonized cuối cùng của chúng tôi nhanh hơn khoảng 100 lần so với giải pháp Python thuần túy Trăn tinhChúng tôi có một cái mà chúng tôi muốn áp dụng một chức năng theo hàng In [1]: df = pd.DataFrame( ...: { ...: "a": np.random.randn(1000), ...: "b": np.random.randn(1000), ...: "N": np.random.randint(100, 1000, (1000)), ...: "x": "x", ...: } ...: ) ...: In [2]: df Out[2]: a b N x 0 0.469112 -0.218470 585 x 1 -0.282863 -0.061645 841 x 2 -1.509059 -0.723780 251 x 3 -1.135632 0.551225 972 x 4 1.212112 -0.497767 181 x . .. .. .. .. 995 -1.512743 0.874737 374 x 996 0.933753 1.120790 246 x 997 -0.308013 0.198768 157 x 998 -0.079915 1.757555 977 x 999 -1.010589 -1.115680 770 x [1000 rows x 4 columns] Đây là chức năng trong Python thuần túy In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...: Chúng tôi đạt được kết quả của mình bằng cách sử dụng (hàng khôn ngoan) In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each) Nhưng rõ ràng điều này không đủ nhanh đối với chúng tôi. Hãy xem và xem thời gian được sử dụng ở đâu trong quá trình thao tác này (giới hạn ở bốn cuộc gọi tốn nhiều thời gian nhất) bằng cách sử dụng In [6]: %prun -l 4 df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) # noqa E999 621327 function calls (621307 primitive calls) in 0.168 seconds Ordered by: internal time List reduced from 225 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.093 0.000 0.143 0.000 <ipython-input-4-c2a74e076cf0>:1(integrate_f) 552423 0.050 0.000 0.050 0.000 <ipython-input-3-c138bdd570e3>:1(f) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value) Cho đến nay, phần lớn thời gian được sử dụng bên trong In [8]: %%cython ...: def f_plain(x): ...: return x * (x - 1) ...: def integrate_f_plain(a, b, N): ...: s = 0 ...: dx = (b - a) / N ...: for i in range(N): ...: s += f_plain(a + i * dx) ...: return s * dx ...:2 hoặc In [8]: %%cython ...: def f_plain(x): ...: return x * (x - 1) ...: def integrate_f_plain(a, b, N): ...: s = 0 ...: dx = (b - a) / N ...: for i in range(N): ...: s += f_plain(a + i * dx) ...: return s * dx ...:3, do đó chúng tôi sẽ tập trung nỗ lực của mình vào việc mã hóa hai chức năng này Đồng bằng CythonTrước tiên, chúng ta sẽ cần nhập hàm ma thuật Cython vào IPython In [7]: %load_ext Cython Bây giờ, chúng ta chỉ cần sao chép các chức năng của chúng ta sang Cython (hậu tố ở đây để phân biệt giữa các phiên bản chức năng) In [8]: %%cython ...: def f_plain(x): ...: return x * (x - 1) ...: def integrate_f_plain(a, b, N): ...: s = 0 ...: dx = (b - a) / N ...: for i in range(N): ...: s += f_plain(a + i * dx) ...: return s * dx ...: Ghi chú Nếu bạn gặp sự cố khi dán phần trên vào ipython của mình, bạn có thể cần phải sử dụng IPython cạnh chảy máu để dán để chơi tốt với ma thuật di động ________số 8_______ Điều này đã loại bỏ một phần ba, không quá tệ đối với một bản sao và dán đơn giản Thêm loạiChúng tôi nhận được một cải tiến lớn khác chỉ đơn giản bằng cách cung cấp thông tin loại In [10]: %%cython ....: cdef double f_typed(double x) except? -2: ....: return x * (x - 1) ....: cpdef double integrate_f_typed(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_typed(a + i * dx) ....: return s * dx ....: In [11]: %timeit df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 9.47 ms +- 279 us per loop (mean +- std. dev. of 7 runs, 100 loops each) Bây giờ chúng ta nói chuyện. Nó hiện nhanh hơn mười lần so với triển khai Python ban đầu và chúng tôi chưa thực sự sửa đổi mã. Chúng ta hãy có một cái nhìn khác về những gì đang ăn hết thời gian In [12]: %prun -l 4 df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 68904 function calls (68884 primitive calls) in 0.026 seconds Ordered by: internal time List reduced from 224 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value) 16174 0.002 0.000 0.003 0.000 {built-in method builtins.isinstance} 3000 0.002 0.000 0.003 0.000 base.py:3754(get_loc) sử dụng ndarrayNó gọi hàng loạt rất nhiều. Nó đang tạo một từ mỗi hàng và gọi get từ cả chỉ mục và chuỗi (ba lần cho mỗi hàng). Các cuộc gọi hàm rất tốn kém trong Python, vì vậy có lẽ chúng ta có thể giảm thiểu chúng bằng cách cythonizing phần áp dụng Ghi chú Chúng tôi hiện đang chuyển ndarrays vào hàm Cython, may mắn thay Cython chơi rất tốt với NumPy In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:0 Việc triển khai rất đơn giản, nó tạo ra một mảng các số không và lặp qua các hàng, áp dụng In [8]: %%cython ...: def f_plain(x): ...: return x * (x - 1) ...: def integrate_f_plain(a, b, N): ...: s = 0 ...: dx = (b - a) / N ...: for i in range(N): ...: s += f_plain(a + i * dx) ...: return s * dx ...:5 của chúng tôi và đặt số này vào mảng số không Cảnh báo Bạn không thể chuyển trực tiếp dưới dạng tham số đã nhập In [8]: %%cython ...: def f_plain(x): ...: return x * (x - 1) ...: def integrate_f_plain(a, b, N): ...: s = 0 ...: dx = (b - a) / N ...: for i in range(N): ...: s += f_plain(a + i * dx) ...: return s * dx ...:7 cho hàm Cython. Thay vào đó, vượt qua In [8]: %%cython ...: def f_plain(x): ...: return x * (x - 1) ...: def integrate_f_plain(a, b, N): ...: s = 0 ...: dx = (b - a) / N ...: for i in range(N): ...: s += f_plain(a + i * dx) ...: return s * dx ...:7 thực tế bằng cách sử dụng. Lý do là định nghĩa Cython dành riêng cho ndarray chứ không phải định nghĩa đã qua Vì vậy, không làm điều này In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:1 Nhưng thay vào đó, sử dụng để lấy In [8]: %%cython ...: def f_plain(x): ...: return x * (x - 1) ...: def integrate_f_plain(a, b, N): ...: s = 0 ...: dx = (b - a) / N ...: for i in range(N): ...: s += f_plain(a + i * dx) ...: return s * dx ...:7 cơ bản In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:2 Ghi chú Các vòng lặp như thế này sẽ cực kỳ chậm trong Python, nhưng trong Cython, việc lặp qua các mảng NumPy rất nhanh In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:3 Chúng tôi đã nhận được một cải tiến lớn khác. Hãy kiểm tra lại xem thời gian đã được sử dụng ở đâu In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:4 Như mọi người có thể mong đợi, phần lớn thời gian hiện được dành cho In [9]: %timeit df.apply(lambda x: integrate_f_plain(x["a"], x["b"], x["N"]), axis=1) 50.9 ms +- 160 us per loop (mean +- std. dev. of 7 runs, 10 loops each)3, vì vậy nếu chúng tôi muốn đạt được hiệu quả cao hơn nữa, chúng tôi phải tiếp tục tập trung nỗ lực vào đây Các kỹ thuật nâng cao hơnVẫn còn hy vọng cải thiện. Đây là một ví dụ về việc sử dụng một số kỹ thuật Cython nâng cao hơn In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:5 In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:6 Thậm chí còn nhanh hơn, với lời cảnh báo rằng một lỗi trong mã Cython của chúng tôi (ví dụ: lỗi từng lỗi một) có thể gây ra lỗi phân tách vì quyền truy cập bộ nhớ không được kiểm tra. Để biết thêm về In [9]: %timeit df.apply(lambda x: integrate_f_plain(x["a"], x["b"], x["N"]), axis=1) 50.9 ms +- 160 us per loop (mean +- std. dev. of 7 runs, 10 loops each)4 và In [9]: %timeit df.apply(lambda x: integrate_f_plain(x["a"], x["b"], x["N"]), axis=1) 50.9 ms +- 160 us per loop (mean +- std. dev. of 7 runs, 10 loops each)5, hãy xem tài liệu Cython trên Numba (biên dịch JIT)Một giải pháp thay thế cho mã Cython biên dịch tĩnh là sử dụng trình biên dịch động just-in-time (JIT) với Numba Numba cho phép bạn viết một hàm Python thuần túy có thể được biên dịch JIT thành các hướng dẫn máy gốc, có hiệu suất tương tự như C, C++ và Fortran, bằng cách tô điểm cho hàm của bạn bằng In [9]: %timeit df.apply(lambda x: integrate_f_plain(x["a"], x["b"], x["N"]), axis=1) 50.9 ms +- 160 us per loop (mean +- std. dev. of 7 runs, 10 loops each)6 Numba hoạt động bằng cách tạo mã máy được tối ưu hóa bằng cơ sở hạ tầng trình biên dịch LLVM tại thời điểm nhập, thời gian chạy hoặc tĩnh (sử dụng công cụ pycc đi kèm). Numba hỗ trợ biên dịch Python để chạy trên phần cứng CPU hoặc GPU và được thiết kế để tích hợp với ngăn xếp phần mềm khoa học Python Ghi chú Quá trình biên dịch In [9]: %timeit df.apply(lambda x: integrate_f_plain(x["a"], x["b"], x["N"]), axis=1) 50.9 ms +- 160 us per loop (mean +- std. dev. of 7 runs, 10 loops each)6 sẽ thêm chi phí hoạt động vào thời gian chạy của hàm, do đó, lợi ích về hiệu suất có thể không được nhận ra, đặc biệt là khi sử dụng các tập dữ liệu nhỏ. Xem xét lưu trữ chức năng của bạn vào bộ đệm để tránh chi phí biên dịch mỗi khi chức năng của bạn được chạy Numba có thể được sử dụng theo 2 cách với gấu trúc
gấu trúc Numba EngineNếu Numba được cài đặt, người ta có thể chỉ định In [9]: %timeit df.apply(lambda x: integrate_f_plain(x["a"], x["b"], x["N"]), axis=1) 50.9 ms +- 160 us per loop (mean +- std. dev. of 7 runs, 10 loops each)8 trong các phương thức gấu trúc đã chọn để thực thi phương thức bằng Numba. Các phương thức hỗ trợ In [9]: %timeit df.apply(lambda x: integrate_f_plain(x["a"], x["b"], x["N"]), axis=1) 50.9 ms +- 160 us per loop (mean +- std. dev. of 7 runs, 10 loops each)8 cũng sẽ có một từ khóa In [10]: %%cython ....: cdef double f_typed(double x) except? -2: ....: return x * (x - 1) ....: cpdef double integrate_f_typed(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_typed(a + i * dx) ....: return s * dx ....:5 chấp nhận một từ điển cho phép một người chỉ định các khóa In [10]: %%cython ....: cdef double f_typed(double x) except? -2: ....: return x * (x - 1) ....: cpdef double integrate_f_typed(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_typed(a + i * dx) ....: return s * dx ....:6, In [10]: %%cython ....: cdef double f_typed(double x) except? -2: ....: return x * (x - 1) ....: cpdef double integrate_f_typed(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_typed(a + i * dx) ....: return s * dx ....:7 và In [10]: %%cython ....: cdef double f_typed(double x) except? -2: ....: return x * (x - 1) ....: cpdef double integrate_f_typed(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_typed(a + i * dx) ....: return s * dx ....:8 với các giá trị boolean để chuyển vào trình trang trí In [9]: %timeit df.apply(lambda x: integrate_f_plain(x["a"], x["b"], x["N"]), axis=1) 50.9 ms +- 160 us per loop (mean +- std. dev. of 7 runs, 10 loops each)6. Nếu In [10]: %%cython ....: cdef double f_typed(double x) except? -2: ....: return x * (x - 1) ....: cpdef double integrate_f_typed(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_typed(a + i * dx) ....: return s * dx ....:5 không được chỉ định, nó sẽ mặc định là In [11]: %timeit df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 9.47 ms +- 279 us per loop (mean +- std. dev. of 7 runs, 100 loops each)1 trừ khi được chỉ định khác Về mặt hiệu suất, lần đầu tiên một chức năng được chạy bằng công cụ Numba sẽ chậm vì Numba sẽ có một số chi phí biên dịch chức năng. Tuy nhiên, các chức năng được biên dịch JIT được lưu vào bộ đệm và các cuộc gọi tiếp theo sẽ nhanh chóng. Nói chung, công cụ Numba hoạt động hiệu quả với số lượng điểm dữ liệu lớn hơn (e. g. hơn 1 triệu) In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:7 Nếu phần cứng máy tính của bạn chứa nhiều CPU, bạn có thể đạt được mức tăng hiệu suất lớn nhất bằng cách đặt In [11]: %timeit df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 9.47 ms +- 279 us per loop (mean +- std. dev. of 7 runs, 100 loops each)2 thành In [11]: %timeit df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 9.47 ms +- 279 us per loop (mean +- std. dev. of 7 runs, 100 loops each)3 để tận dụng nhiều hơn 1 CPU. Trong nội bộ, gấu trúc tận dụng tê để song song hóa các phép tính trên các cột của a ; In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:8 Ví dụ về chức năng tùy chỉnhCó thể sử dụng một hàm Python tùy chỉnh được trang trí bằng In [9]: %timeit df.apply(lambda x: integrate_f_plain(x["a"], x["b"], x["N"]), axis=1) 50.9 ms +- 160 us per loop (mean +- std. dev. of 7 runs, 10 loops each)6 với các đối tượng pandas bằng cách chuyển các biểu diễn mảng NumPy của chúng với In [10]: %%cython ....: cdef double f_typed(double x) except? -2: ....: return x * (x - 1) ....: cpdef double integrate_f_typed(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_typed(a + i * dx) ....: return s * dx ....:2 In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:9 In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)0 Trong ví dụ này, sử dụng Numba nhanh hơn Cython Numba cũng có thể được sử dụng để viết các hàm vector hóa không yêu cầu người dùng lặp lại một cách rõ ràng các quan sát của một vector; . Xem xét ví dụ sau về việc nhân đôi mỗi quan sát In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)1 In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)2 Hãy cẩn thậnNumba là cách tốt nhất để tăng tốc các hàm áp dụng các hàm số cho mảng NumPy. Nếu bạn cố gắng In [9]: %timeit df.apply(lambda x: integrate_f_plain(x["a"], x["b"], x["N"]), axis=1) 50.9 ms +- 160 us per loop (mean +- std. dev. of 7 runs, 10 loops each)6 một hàm chứa mã Python hoặc NumPy không được hỗ trợ, quá trình biên dịch sẽ hoàn nguyên, điều này hầu như sẽ không tăng tốc chức năng của bạn. Nếu bạn muốn Numba đưa ra lỗi nếu nó không thể biên dịch một hàm theo cách tăng tốc mã của bạn, hãy chuyển cho Numba đối số In [11]: %timeit df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 9.47 ms +- 279 us per loop (mean +- std. dev. of 7 runs, 100 loops each)9 (e. g. In [12]: %prun -l 4 df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 68904 function calls (68884 primitive calls) in 0.026 seconds Ordered by: internal time List reduced from 224 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value) 16174 0.002 0.000 0.003 0.000 {built-in method builtins.isinstance} 3000 0.002 0.000 0.003 0.000 base.py:3754(get_loc)0). Để biết thêm về khắc phục sự cố chế độ Numba, hãy xem phần Sử dụng In [12]: %prun -l 4 df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 68904 function calls (68884 primitive calls) in 0.026 seconds Ordered by: internal time List reduced from 224 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value) 16174 0.002 0.000 0.003 0.000 {built-in method builtins.isinstance} 3000 0.002 0.000 0.003 0.000 base.py:3754(get_loc)1 (e. g. In [12]: %prun -l 4 df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 68904 function calls (68884 primitive calls) in 0.026 seconds Ordered by: internal time List reduced from 224 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value) 16174 0.002 0.000 0.003 0.000 {built-in method builtins.isinstance} 3000 0.002 0.000 0.003 0.000 base.py:3754(get_loc)2) có thể dẫn đến In [12]: %prun -l 4 df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 68904 function calls (68884 primitive calls) in 0.026 seconds Ordered by: internal time List reduced from 224 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value) 16174 0.002 0.000 0.003 0.000 {built-in method builtins.isinstance} 3000 0.002 0.000 0.003 0.000 base.py:3754(get_loc)3 nếu lớp luồng dẫn đến hành vi không an toàn. Trước tiên, bạn có thể chạy một chức năng JIT với In [12]: %prun -l 4 df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 68904 function calls (68884 primitive calls) in 0.026 seconds Ordered by: internal time List reduced from 224 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value) 16174 0.002 0.000 0.003 0.000 {built-in method builtins.isinstance} 3000 0.002 0.000 0.003 0.000 base.py:3754(get_loc)1 Nói chung, nếu bạn gặp phải một segfault ( In [12]: %prun -l 4 df.apply(lambda x: integrate_f_typed(x["a"], x["b"], x["N"]), axis=1) 68904 function calls (68884 primitive calls) in 0.026 seconds Ordered by: internal time List reduced from 224 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value) 16174 0.002 0.000 0.003 0.000 {built-in method builtins.isinstance} 3000 0.002 0.000 0.003 0.000 base.py:3754(get_loc)5) trong khi sử dụng Numba, vui lòng báo cáo sự cố cho trình theo dõi sự cố Numba Đánh giá biểu hiện thông quaHàm cấp cao nhất thực hiện đánh giá biểu thức của và các đối tượng Ghi chú Để hưởng lợi từ việc sử dụng, bạn cần cài đặt In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:01. Xem để biết thêm chi tiết Điểm sử dụng để đánh giá biểu thức thay vì Python đơn giản là hai lần. 1) các đối tượng lớn được đánh giá hiệu quả hơn và 2) các biểu thức số học và boolean lớn được đánh giá tất cả cùng một lúc bởi công cụ bên dưới (theo mặc định ____________01 được sử dụng để đánh giá) Ghi chú Bạn không nên sử dụng cho các biểu thức đơn giản hoặc cho các biểu thức liên quan đến DataFrames nhỏ. Trên thực tế, chậm hơn nhiều bậc đối với các biểu thức/đối tượng nhỏ hơn so với Python đơn giản. Một nguyên tắc nhỏ là chỉ sử dụng khi bạn có hơn 10.000 hàng hỗ trợ tất cả các biểu thức số học được công cụ hỗ trợ ngoài một số tiện ích mở rộng chỉ có trong gấu trúc Ghi chú Khung càng lớn và biểu thức càng lớn, bạn sẽ thấy tốc độ càng nhanh khi sử dụng Cú pháp được hỗ trợCác hoạt động này được hỗ trợ bởi
Cú pháp Python này không được phép
ví dụhoạt động tốt với các biểu thức chứa mảng lớn Trước tiên, hãy tạo một vài mảng có kích thước phù hợp để chơi với In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)3 Bây giờ, hãy so sánh việc thêm chúng lại với nhau bằng cách sử dụng Python thuần túy so với In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)4 In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)5 Bây giờ hãy làm điều tương tự nhưng với sự so sánh In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)6 In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)7 cũng hoạt động với các đối tượng gấu trúc chưa được phân bổ In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)8 In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)9 Ghi chú Các thao tác như In [6]: %prun -l 4 df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) # noqa E999 621327 function calls (621307 primitive calls) in 0.168 seconds Ordered by: internal time List reduced from 225 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.093 0.000 0.143 0.000 <ipython-input-4-c2a74e076cf0>:1(integrate_f) 552423 0.050 0.000 0.050 0.000 <ipython-input-3-c138bdd570e3>:1(f) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value)0 nên được thực hiện bằng Python. Một ngoại lệ sẽ được đưa ra nếu bạn cố gắng thực hiện bất kỳ thao tác boolean/bitwise nào với toán hạng vô hướng không thuộc loại In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:60 hoặc In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:61. Một lần nữa, bạn nên thực hiện các loại thao tác này bằng Python thuần túy phương phápNgoài chức năng cấp cao nhất, bạn cũng có thể đánh giá một biểu thức trong “ngữ cảnh” của một In [6]: %prun -l 4 df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) # noqa E999 621327 function calls (621307 primitive calls) in 0.168 seconds Ordered by: internal time List reduced from 225 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.093 0.000 0.143 0.000 <ipython-input-4-c2a74e076cf0>:1(integrate_f) 552423 0.050 0.000 0.050 0.000 <ipython-input-3-c138bdd570e3>:1(f) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value)1 Bất kỳ biểu thức nào là biểu thức hợp lệ cũng là biểu thức hợp lệ, với lợi ích bổ sung là bạn không phải thêm tiền tố vào tên của (các) cột mà bạn muốn đánh giá Ngoài ra, bạn có thể thực hiện gán các cột trong một biểu thức. Điều này cho phép đánh giá công thức. Mục tiêu gán có thể là tên cột mới hoặc tên cột hiện có và nó phải là mã định danh Python hợp lệ Từ khóa In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:68 xác định xem nhiệm vụ này sẽ được thực hiện trên bản gốc hay trả về một bản sao với cột mới In [6]: %prun -l 4 df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) # noqa E999 621327 function calls (621307 primitive calls) in 0.168 seconds Ordered by: internal time List reduced from 225 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.093 0.000 0.143 0.000 <ipython-input-4-c2a74e076cf0>:1(integrate_f) 552423 0.050 0.000 0.050 0.000 <ipython-input-3-c138bdd570e3>:1(f) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value)2 Khi In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:68 được đặt thành In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:71, theo mặc định, một bản sao của cột có cột mới hoặc cột đã sửa đổi được trả về và khung ban đầu không thay đổi In [6]: %prun -l 4 df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) # noqa E999 621327 function calls (621307 primitive calls) in 0.168 seconds Ordered by: internal time List reduced from 225 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.093 0.000 0.143 0.000 <ipython-input-4-c2a74e076cf0>:1(integrate_f) 552423 0.050 0.000 0.050 0.000 <ipython-input-3-c138bdd570e3>:1(f) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value)3 Để thuận tiện, có thể thực hiện nhiều nhiệm vụ bằng cách sử dụng chuỗi nhiều dòng In [6]: %prun -l 4 df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) # noqa E999 621327 function calls (621307 primitive calls) in 0.168 seconds Ordered by: internal time List reduced from 225 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.093 0.000 0.143 0.000 <ipython-input-4-c2a74e076cf0>:1(integrate_f) 552423 0.050 0.000 0.050 0.000 <ipython-input-3-c138bdd570e3>:1(f) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value)4 Tương đương trong tiêu chuẩn Python sẽ là In [6]: %prun -l 4 df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) # noqa E999 621327 function calls (621307 primitive calls) in 0.168 seconds Ordered by: internal time List reduced from 225 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.093 0.000 0.143 0.000 <ipython-input-4-c2a74e076cf0>:1(integrate_f) 552423 0.050 0.000 0.050 0.000 <ipython-input-3-c138bdd570e3>:1(f) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value)5 Phương thức này có từ khóa In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:68 xác định xem truy vấn có sửa đổi khung ban đầu hay không In [6]: %prun -l 4 df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) # noqa E999 621327 function calls (621307 primitive calls) in 0.168 seconds Ordered by: internal time List reduced from 225 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.093 0.000 0.143 0.000 <ipython-input-4-c2a74e076cf0>:1(integrate_f) 552423 0.050 0.000 0.050 0.000 <ipython-input-3-c138bdd570e3>:1(f) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value)6 Biến cục bộBạn phải tham chiếu rõ ràng bất kỳ biến cục bộ nào mà bạn muốn sử dụng trong một biểu thức bằng cách đặt ký tự In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:75 trước tên. Ví dụ, In [6]: %prun -l 4 df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) # noqa E999 621327 function calls (621307 primitive calls) in 0.168 seconds Ordered by: internal time List reduced from 225 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.093 0.000 0.143 0.000 <ipython-input-4-c2a74e076cf0>:1(integrate_f) 552423 0.050 0.000 0.050 0.000 <ipython-input-3-c138bdd570e3>:1(f) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value)7 Nếu bạn không đặt tiền tố cho biến cục bộ bằng In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:75, gấu trúc sẽ đưa ra một ngoại lệ cho bạn biết biến đó là không xác định Khi sử dụng and , điều này cho phép bạn có một biến cục bộ và một cột có cùng tên trong một biểu thức In [6]: %prun -l 4 df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) # noqa E999 621327 function calls (621307 primitive calls) in 0.168 seconds Ordered by: internal time List reduced from 225 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.093 0.000 0.143 0.000 <ipython-input-4-c2a74e076cf0>:1(integrate_f) 552423 0.050 0.000 0.050 0.000 <ipython-input-3-c138bdd570e3>:1(f) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value)8 Với bạn hoàn toàn không thể sử dụng tiền tố In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:75 vì nó không được định nghĩa trong ngữ cảnh đó. gấu trúc sẽ cho bạn biết điều này nếu bạn cố gắng sử dụng In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:75 trong lệnh gọi cấp cao nhất tới. Ví dụ, In [6]: %prun -l 4 df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) # noqa E999 621327 function calls (621307 primitive calls) in 0.168 seconds Ordered by: internal time List reduced from 225 to 4 due to restriction <4> ncalls tottime percall cumtime percall filename:lineno(function) 1000 0.093 0.000 0.143 0.000 <ipython-input-4-c2a74e076cf0>:1(integrate_f) 552423 0.050 0.000 0.050 0.000 <ipython-input-3-c138bdd570e3>:1(f) 3000 0.004 0.000 0.018 0.000 series.py:966(__getitem__) 3000 0.002 0.000 0.009 0.000 series.py:1072(_get_value)9 Trong trường hợp này, bạn chỉ nên tham khảo các biến giống như trong Python chuẩn In [7]: %load_ext Cython0 trình phân tích cú phápCó hai trình phân tích cú pháp khác nhau và hai công cụ khác nhau mà bạn có thể sử dụng làm phụ trợ Trình phân tích cú pháp In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:85 mặc định cho phép một cú pháp trực quan hơn để thể hiện các hoạt động giống như truy vấn (so sánh, liên từ và phân tách). Cụ thể, mức độ ưu tiên của các toán tử In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:86 và In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:87 được tạo bằng với mức độ ưu tiên của các phép toán boolean tương ứng In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:88 và In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:89 Ví dụ, liên từ trên có thể được viết mà không có dấu ngoặc đơn. Ngoài ra, bạn có thể sử dụng trình phân tích cú pháp In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:90 để thực thi ngữ nghĩa Python nghiêm ngặt In [7]: %load_ext Cython1 Biểu thức tương tự cũng có thể được "anded" cùng với từ In [7]: %load_ext Cython2 Các toán tử In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:88 và In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:89 ở đây có cùng mức độ ưu tiên như trong vanilla Python phụ trợNgoài ra còn có tùy chọn để làm cho hoạt động giống hệt với Python thuần túy Ghi chú Sử dụng công cụ In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:90 thường không hữu ích, ngoại trừ việc thử nghiệm các công cụ đánh giá khác chống lại nó. Bạn sẽ không đạt được lợi ích về hiệu suất khi sử dụng với In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:98 và trên thực tế có thể bị ảnh hưởng về hiệu suất Bạn có thể thấy điều này bằng cách sử dụng với công cụ In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:90. Nó chậm hơn một chút (không nhiều) so với việc đánh giá cùng một biểu thức trong Python In [7]: %load_ext Cython3 In [7]: %load_ext Cython4 hiệu suấtđược dự định để tăng tốc một số loại hoạt động. Đặc biệt, những hoạt động liên quan đến các biểu thức phức tạp với các đối tượng / lớn sẽ thấy lợi ích hiệu suất đáng kể. Đây là một biểu đồ hiển thị thời gian chạy dưới dạng hàm của kích thước của khung liên quan đến tính toán. Hai dòng là hai động cơ khác nhau Ghi chú Các thao tác với các đối tượng nhỏ (khoảng 15k-20k hàng) nhanh hơn khi sử dụng Python đơn giản Biểu đồ này được tạo bằng cách sử dụng a với 3 cột, mỗi cột chứa các giá trị dấu phẩy động được tạo bằng cách sử dụng In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)07 Chi tiết kỹ thuật liên quan đến đánh giá biểu thứcCác biểu thức dẫn đến một đối tượng dtype hoặc liên quan đến các thao tác ngày giờ (vì In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)08) phải được đánh giá trong không gian Python. Lý do chính cho hành vi này là để duy trì khả năng tương thích ngược với các phiên bản NumPy < 1. 7. Trong các phiên bản NumPy đó, lệnh gọi tới ____2_______09 sẽ cắt bớt bất kỳ chuỗi nào dài hơn 60 ký tự. Thứ hai, chúng ta không thể chuyển mảng In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)10 sang In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:01, do đó, phép so sánh chuỗi phải được đánh giá trong không gian Python Kết quả là điều này chỉ áp dụng cho các biểu thức object-dtype. Vì vậy, nếu bạn có một biểu thức-ví dụ In [7]: %load_ext Cython5 phần số của phép so sánh ( In [5]: %timeit df.apply(lambda x: integrate_f(x["a"], x["b"], x["N"]), axis=1) 86 ms +- 1.44 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)12) sẽ được đánh giá bởi In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:01 Nói chung, / sẽ đánh giá các biểu thức con có thể được đánh giá bởi In [3]: def f(x): ...: return x * (x - 1) ...: In [4]: 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 ...:01 và những biểu thức con phải được đánh giá trong không gian Python một cách minh bạch đối với người dùng. Điều này được thực hiện bằng cách suy ra kiểu kết quả của một biểu thức từ các đối số và toán tử của nó |