Đo điểm chuẩn và lập hồ sơ

Trong chương này, chúng ta sẽ tìm hiểu cách điểm chuẩn và lập hồ sơ giúp giải quyết các vấn đề về hiệu suất.

Giả sử chúng ta đã viết một đoạn mã và nó cũng cho kết quả mong muốn nhưng điều gì sẽ xảy ra nếu chúng ta muốn chạy đoạn mã này nhanh hơn một chút vì nhu cầu đã thay đổi. Trong trường hợp này, chúng ta cần tìm hiểu những phần mã của chúng ta đang làm chậm toàn bộ chương trình. Trong trường hợp này, điểm chuẩn và lập hồ sơ có thể hữu ích.

Điểm chuẩn là gì?

Đo điểm chuẩn nhằm mục đích đánh giá một cái gì đó bằng cách so sánh với một tiêu chuẩn. Tuy nhiên, câu hỏi đặt ra ở đây là điểm chuẩn sẽ là gì và tại sao chúng ta cần nó trong trường hợp lập trình phần mềm. Đo điểm chuẩn cho mã có nghĩa là mã đang thực thi tốc độ như thế nào và nút cổ chai ở đâu. Một lý do chính cho việc đo điểm chuẩn là nó tối ưu hóa mã.

Điểm chuẩn hoạt động như thế nào?

Nếu chúng ta nói về hoạt động của điểm chuẩn, chúng ta cần bắt đầu bằng cách đo điểm chuẩn của toàn bộ chương trình như một trạng thái hiện tại, sau đó chúng ta có thể kết hợp các điểm chuẩn vi mô và sau đó phân tách một chương trình thành các chương trình nhỏ hơn. Để tìm ra những điểm nghẽn trong chương trình của chúng tôi và tối ưu hóa nó. Nói cách khác, chúng ta có thể hiểu nó là việc chia bài toán lớn và hóc búa thành hàng loạt bài toán nhỏ hơn và dễ dàng hơn một chút để tối ưu hóa chúng.

Mô-đun Python để đo điểm chuẩn

Trong Python, chúng tôi có một mô-đun mặc định để đo điểm chuẩn được gọi là timeit. Với sự giúp đỡ củatimeit mô-đun, chúng ta có thể đo hiệu suất của một đoạn mã Python nhỏ trong chương trình chính của chúng ta.

Thí dụ

Trong tập lệnh Python sau, chúng tôi đang nhập timeit mô-đun, đo thêm thời gian thực hiện hai chức năng - functionAfunctionB -

import timeit
import time
def functionA():
   print("Function A starts the execution:")
   print("Function A completes the execution:")
def functionB():
   print("Function B starts the execution")
   print("Function B completes the execution")
start_time = timeit.default_timer()
functionA()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
functionB()
print(timeit.default_timer() - start_time)

Sau khi chạy đoạn script trên, chúng ta sẽ có được thời gian thực thi của cả 2 hàm như hình bên dưới.

Đầu ra

Function A starts the execution:
Function A completes the execution:
0.0014599495514175942
Function B starts the execution
Function B completes the execution
0.0017024724827479076

Viết bộ đếm thời gian của riêng chúng tôi bằng cách sử dụng chức năng trang trí

Trong Python, chúng ta có thể tạo bộ đếm thời gian của riêng mình, nó sẽ hoạt động giống như timeitmô-đun. Nó có thể được thực hiện với sự trợ giúp củadecoratorchức năng. Sau đây là một ví dụ về bộ hẹn giờ tùy chỉnh -

import random
import time

def timer_func(func):

   def function_timer(*args, **kwargs):
   start = time.time()
   value = func(*args, **kwargs)
   end = time.time()
   runtime = end - start
   msg = "{func} took {time} seconds to complete its execution."
      print(msg.format(func = func.__name__,time = runtime))
   return value
   return function_timer

@timer_func
def Myfunction():
   for x in range(5):
   sleep_time = random.choice(range(1,3))
   time.sleep(sleep_time)

if __name__ == '__main__':
   Myfunction()

Tập lệnh python ở trên giúp nhập các mô-đun thời gian ngẫu nhiên. Chúng tôi đã tạo hàm trang trí timer_func (). Điều này có hàm function_timer () bên trong nó. Bây giờ, hàm lồng nhau sẽ lấy thời gian trước khi gọi hàm được truyền vào. Sau đó, nó đợi hàm trả về và lấy thời gian kết thúc. Bằng cách này, cuối cùng chúng ta có thể làm cho tập lệnh python in thời gian thực thi. Tập lệnh sẽ tạo ra kết quả như hình dưới đây.

Đầu ra

Myfunction took 8.000457763671875 seconds to complete its execution.

Hồ sơ là gì?

Đôi khi lập trình viên muốn đo lường một số thuộc tính như việc sử dụng bộ nhớ, độ phức tạp về thời gian hoặc sử dụng các lệnh cụ thể về chương trình để đo khả năng thực của chương trình đó. Kiểu đo lường như vậy về chương trình được gọi là lược tả. Hồ sơ sử dụng phân tích chương trình động để thực hiện việc đo lường như vậy.

Trong các phần tiếp theo, chúng ta sẽ tìm hiểu về các Mô-đun Python khác nhau để lập hồ sơ.

cProfile - mô-đun có sẵn

cProfilelà một mô-đun tích hợp sẵn trong Python để lập hồ sơ. Mô-đun là một phần mở rộng C với chi phí hợp lý làm cho nó phù hợp để lập hồ sơ các chương trình chạy dài. Sau khi chạy, nó ghi lại tất cả các chức năng và thời gian thực thi. Nó rất mạnh mẽ nhưng đôi khi hơi khó để diễn giải và hành động. Trong ví dụ sau, chúng tôi đang sử dụng cProfile trên đoạn mã bên dưới:

Thí dụ

def increment_global():

   global x
   x += 1

def taskofThread(lock):

   for _ in range(50000):
   lock.acquire()
   increment_global()
   lock.release()

def main():
   global x
   x = 0

   lock = threading.Lock()

   t1 = threading.Thread(target=taskofThread, args=(lock,))
   t2 = threading.Thread(target= taskofThread, args=(lock,))

   t1.start()
   t2.start()

   t1.join()
   t2.join()

if __name__ == "__main__":
   for i in range(5):
      main()
   print("x = {1} after Iteration {0}".format(i,x))

Đoạn mã trên được lưu trong thread_increment.pytập tin. Bây giờ, thực thi mã với cProfile trên dòng lệnh như sau:

(base) D:\ProgramData>python -m cProfile thread_increment.py
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
      3577 function calls (3522 primitive calls) in 1.688 seconds

   Ordered by: standard name

   ncalls tottime percall cumtime percall filename:lineno(function)

   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:103(release)
   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:143(__init__)
   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:147(__enter__)
   … … … …

Từ kết quả trên, rõ ràng là cProfile in ra tất cả 3577 hàm được gọi, với thời gian dành cho mỗi và số lần chúng đã được gọi. Tiếp theo là các cột chúng tôi có trong đầu ra -

  • ncalls - Đó là số lượng cuộc gọi được thực hiện.

  • tottime - Là tổng thời gian dành cho một hàm đã cho.

  • percall - Nó đề cập đến thương số của tổng thời gian chia cho ncalls.

  • cumtime- Đó là thời gian tích lũy dành cho chức năng này và tất cả các chức năng con. Nó thậm chí còn chính xác đối với các hàm đệ quy.

  • percall - Là thương số của cumtime chia cho các lần gọi nguyên thủy.

  • filename:lineno(function) - Về cơ bản, nó cung cấp dữ liệu tương ứng của từng chức năng.