Cấu hình mã Python bằng timeit và cProfile - KDnuggets

Cấu hình mã Python bằng timeit và cProfile – KDnuggets

Nút nguồn: 2874649

Cấu hình mã Python bằng timeit và cProfile
Hình ảnh của Tác giả
 

Là một nhà phát triển phần mềm, bạn có thể đã từng nghe câu trích dẫn “Tối ưu hóa sớm là gốc rễ của mọi tội lỗi”—hơn một lần—trong sự nghiệp của bạn. Mặc dù việc tối ưu hóa có thể không quá hữu ích (hoặc thực sự cần thiết) đối với các dự án nhỏ, nhưng việc lập hồ sơ thường rất hữu ích. 

Sau khi mã hóa xong một mô-đun, bạn nên lập cấu hình mã của mình để đo xem mỗi phần cần bao lâu để thực thi. Điều này có thể giúp xác định mùi mã và hướng dẫn tối ưu hóa để cải thiện chất lượng mã. Vì vậy, hãy luôn lập hồ sơ mã của bạn trước khi tối ưu hóa!

Để thực hiện các bước đầu tiên, hướng dẫn này sẽ giúp bạn bắt đầu lập hồ sơ bằng Python—sử dụng công cụ tích hợp sẵn thời gianhồ sơ c mô-đun. Bạn sẽ học cách sử dụng cả giao diện dòng lệnh và các lệnh gọi tương đương bên trong các tập lệnh Python.

Mô-đun timeit là một phần của thư viện chuẩn Python và cung cấp một số hàm tiện lợi có thể được sử dụng để tính thời gian cho các đoạn mã ngắn.

Hãy lấy một ví dụ đơn giản về việc đảo ngược danh sách Python. Chúng tôi sẽ đo thời gian thực hiện để có được bản sao đảo ngược của danh sách bằng cách sử dụng:

  • các reversed() chức năng và
  • cắt lát danh sách. 
>>> nums=[6,9,2,3,7]
>>> list(reversed(nums))
[7, 3, 2, 9, 6]
>>> nums[::-1]
[7, 3, 2, 9, 6]

 

Chạy timeit tại dòng lệnh

Bạn có thể chạy timeit tại dòng lệnh sử dụng cú pháp:

$ python -m timeit -s 'setup-code' -n 'number' -r 'repeat' 'stmt'

 

Bạn được yêu cầu cung cấp tuyên bố stmt thời gian thực hiện của nó sẽ được đo. 

Bạn có thể chỉ định setup mã khi cần—sử dụng tùy chọn ngắn -s hoặc tùy chọn dài –setup. Mã thiết lập sẽ chỉ được chạy một lần.

Sản phẩm number số lần chạy câu lệnh: tùy chọn ngắn -n hoặc tùy chọn dài –number là tùy chọn. Và số lần lặp lại chu trình này: tùy chọn ngắn -r hoặc tùy chọn dài –repeat cũng là tùy chọn.

Hãy xem ví dụ trên hoạt động như thế nào:

Ở đây việc tạo danh sách là setup mã và đảo ngược danh sách là câu lệnh được tính thời gian:

$ python -m timeit -s 'nums=[6,9,2,3,7]' 'list(reversed(nums))'
500000 loops, best of 5: 695 nsec per loop

 

Khi bạn không chỉ định giá trị cho repeat, giá trị mặc định là 5 được sử dụng. Và khi bạn không chỉ định number, mã sẽ được chạy nhiều lần nếu cần để đạt tổng thời gian là ít nhất 0.2 giây.

Ví dụ này đặt rõ ràng số lần thực hiện câu lệnh:

$ python -m timeit -s 'nums=[6,9,2,3,7]' -n 100Bu000 'list(reversed(nums))'
100000 loops, best of 5: 540 nsec per loop

 

Giá trị mặc định của repeat là 5, nhưng chúng ta có thể đặt nó thành bất kỳ giá trị phù hợp nào:

$ python3 -m timeit -s 'nums=[6,9,2,3,7]' -r 3 'list(reversed(nums))'
500000 loops, best of 3: 663 nsec per loop

 

Chúng ta cũng hãy tính thời gian cho phương pháp cắt danh sách:

$ python3 -m timeit -s 'nums=[6,9,2,3,7]' 'nums[::-1]'
1000000 loops, best of 5: 142 nsec per loop

 

Cách tiếp cận cắt danh sách có vẻ nhanh hơn (tất cả các ví dụ đều có trong Python 3.10 trên Ubuntu 22.04).

Chạy timeit trong Tập lệnh Python

Đây là tương đương với việc chạy timeit bên trong tập lệnh Python:

import timeit setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]' t1 = timeit.timeit(setup=setup,stmt=stmt1,number=number)
t2 = timeit.timeit(setup=setup,stmt=stmt2,number=number) print(f"Using reversed() fn.: {t1}")
print(f"Using list slicing: {t2}")

 

Sản phẩm timeit() có thể gọi được trả về thời gian thực hiện của stmt cho number thời gian. Lưu ý rằng chúng ta có thể đề cập rõ ràng đến số lần chạy hoặc thực hiện number lấy giá trị mặc định là 1000000.

Output >>
Using reversed() fn.: 0.08982690000000002
Using list slicing: 0.015550800000000004

 

Thao tác này sẽ chạy câu lệnh—không lặp lại chức năng hẹn giờ—cho thời gian đã chỉ định. number lần và trả về thời gian thực hiện. Nó cũng khá phổ biến để sử dụng time.repeat() và lấy thời gian tối thiểu như được hiển thị:

import timeit setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]' t1 = min(timeit.repeat(setup=setup,stmt=stmt1,number=number))
t2 = min(timeit.repeat(setup=setup,stmt=stmt2,number=number)) print(f"Using reversed() fn.: {t1}")
print(f"Using list slicing: {t2}")

 

Điều này sẽ lặp lại quá trình chạy mã number thời gian repeat số lần và trả về thời gian thực hiện tối thiểu. Ở đây chúng ta có 5 lần lặp lại, mỗi lần 100000 lần.

Output >>
Using reversed() fn.: 0.055375300000000016
Using list slicing: 0.015101400000000043

Chúng ta đã thấy timeit có thể được sử dụng như thế nào để đo thời gian thực thi của các đoạn mã ngắn. Tuy nhiên, trong thực tế, sẽ hữu ích hơn nếu lập cấu hình toàn bộ tập lệnh Python. 

Điều này sẽ cung cấp cho chúng ta thời gian thực hiện của tất cả các hàm và lệnh gọi phương thức—bao gồm cả các hàm và phương thức tích hợp sẵn. Vì vậy, chúng ta có thể hiểu rõ hơn về các lệnh gọi hàm đắt tiền hơn và xác định các cơ hội để tối ưu hóa. Ví dụ: có thể có lệnh gọi API quá chậm. Hoặc một hàm có thể có một vòng lặp có thể được thay thế bằng một biểu thức hiểu Pythonic hơn. 

Hãy tìm hiểu cách cấu hình các tập lệnh Python bằng mô-đun cProfile (cũng là một phần của thư viện chuẩn Python). 

Hãy xem xét tập lệnh Python sau:

# main.py
import time def func(num): for i in range(num): print(i) def another_func(num): time.sleep(num) print(f"Slept for {num} seconds") def useful_func(nums, target): if target in nums: return nums.index(target) if __name__ == "__main__": func(1000) another_func(20) useful_func([2, 8, 12, 4], 12)

 

Ở đây chúng tôi có ba chức năng:

  • func() lặp qua một dãy số và in chúng ra.
  • another func() trong đó có một cuộc gọi đến sleep() chức năng.
  • useful_func() trả về chỉ mục của số mục tiêu trong danh sách (nếu mục tiêu có trong danh sách).

Các hàm được liệt kê ở trên sẽ được gọi mỗi khi bạn chạy tập lệnh main.py.

Chạy cProfile tại dòng lệnh

Chạy cProfile tại dòng lệnh bằng cách sử dụng:

python3 -m file-name.py

 

Ở đây chúng tôi đã đặt tên tệp main.py:

python3 -m main.py

 

Chạy cái này sẽ cho bạn kết quả đầu ra sau:

 Output >> 0 ... 999 Slept for 20 seconds

 

Và hồ sơ sau:

 

Cấu hình mã Python bằng timeit và cProfile
 

Ở đây, ncalls đề cập đến số lượng cuộc gọi đến chức năng và percall đề cập đến thời gian cho mỗi cuộc gọi chức năng. Nếu giá trị của ncalls lớn hơn một thì percall là thời gian trung bình của tất cả các cuộc gọi.

Thời gian thực thi của tập lệnh bị chi phối bởi another_func sử dụng tính năng tích hợp sẵn sleep gọi hàm (ngủ trong 20 giây). Chúng ta thấy rằng print các cuộc gọi chức năng cũng khá tốn kém. 

Sử dụng cProfile trong Python Script

Trong khi chạy cProfile ở dòng lệnh hoạt động tốt, bạn cũng có thể thêm chức năng lập hồ sơ vào tập lệnh Python. Bạn có thể sử dụng cProfile kết hợp với mô-đun pstat để lập hồ sơ và truy cập số liệu thống kê.

Cách tốt nhất để xử lý việc thiết lập và phân chia tài nguyên tốt hơn là sử dụng câu lệnh with và tạo một đối tượng hồ sơ được sử dụng làm trình quản lý bối cảnh:

# main.py
import pstats
import time
import cProfile def func(num): for i in range(num): print(i) def another_func(num): time.sleep(num) print(f"Slept for {num} seconds") def useful_func(nums, target): if target in nums: return nums.index(target) if __name__ == "__main__": with cProfile.Profile() as profile: func(1000) another_func(20) useful_func([2, 8, 12, 4], 12) profile_result = pstats.Stats(profile) profile_result.print_stats()

 

Chúng ta hãy xem xét kỹ hơn hồ sơ đầu ra được tạo:

 

Cấu hình mã Python bằng timeit và cProfile
 

Khi bạn lập hồ sơ cho một tập lệnh lớn, sẽ rất hữu ích nếu bạn sắp xếp kết quả theo thời gian thực hiện. Để làm như vậy bạn có thể gọi sort_stats trên đối tượng hồ sơ và sắp xếp dựa trên thời gian thực hiện: 

...
if __name__ == "__main__": with cProfile.Profile() as profile: func(1000) another_func(20) useful_func([2, 8, 12, 4], 12) profile_result = pstats.Stats(profile) profile_result.sort_stats(pstats.SortKey.TIME) profile_result.print_stats()

 

Khi bạn chạy tập lệnh, bạn sẽ có thể xem kết quả được sắp xếp theo thời gian:

 

Cấu hình mã Python bằng timeit và cProfile

Tôi hy vọng hướng dẫn này sẽ giúp bạn bắt đầu lập hồ sơ bằng Python. Hãy luôn nhớ rằng việc tối ưu hóa không bao giờ phải đánh đổi bằng khả năng đọc được. Nếu bạn muốn tìm hiểu về các trình lược tả khác, bao gồm các gói Python của bên thứ ba, hãy xem phần này bài viết về trình biên dịch Python.
 
 
Bala Priya C là một nhà phát triển và nhà văn kỹ thuật đến từ Ấn Độ. Cô ấy thích làm việc ở nơi giao thoa giữa toán học, lập trình, khoa học dữ liệu và sáng tạo nội dung. Các lĩnh vực chuyên môn và sở thích của cô bao gồm DevOps, khoa học dữ liệu và xử lý ngôn ngữ tự nhiên. Cô ấy thích đọc, viết, mã hóa và cà phê! Hiện tại, cô ấy đang nỗ lực học hỏi và chia sẻ kiến ​​thức của mình với cộng đồng nhà phát triển bằng cách viết các hướng dẫn, hướng dẫn cách thực hiện, các ý kiến, v.v.
 

Dấu thời gian:

Thêm từ Xe đẩy