Thời gian lập hồ sơ Python

Công cụ phổ biến nhất để đo điểm chuẩn thời gian của mã Python, mô-đun timeit tích hợp cung cấp nhiều hơn những gì hầu hết Pythonistas biết

A manual stopwatch.

Mô-đun timeit là đồng hồ bấm giờ của Python. Ảnh của Tsvetoslav Hristov trên Bapt

So sánh điểm chuẩn hiếm khi được thực hiện vì niềm vui của nó, ngay cả khi nó thực sự rất thú vị. Ngoài niềm vui này, nó có thể giúp bạn trong

  • hiểu hành vi Python;
  • tối ưu hóa mã của bạn

Nếu bạn nghĩ rằng mình dành quá nhiều thời gian để đo điểm chuẩn một số đoạn mã ngẫu nhiên, đừng lo lắng. Tôi đã từng ở đó. Thành thật mà nói, tôi vẫn làm điều đó khá thường xuyên. Đừng xấu hổ về điều này. Điểm chuẩn giúp bạn hiểu được sự phức tạp của ngôn ngữ. Theo thời gian, bạn sẽ nhận thấy rằng bạn có thể đoán tốc độ nhanh hay chậm của một đoạn trích cụ thể. Tuy nhiên, đôi khi, ngay cả “cái mũi đo điểm chuẩn” của bạn cũng sẽ đánh lừa bạn, vì vậy việc đo điểm chuẩn thường được sử dụng hợp lý. Rốt cuộc thì lập trình rất thú vị phải không?

Có thể mô-đun phổ biến nhất để đo điểm chuẩn các đoạn mã trong Python, xét về thời gian thực hiện, là mô-đun timeit. Python cũng cung cấp các công cụ đo điểm chuẩn liên quan đến thời gian khác, nhưng timeit chắc chắn phải là bước đầu tiên của bạn, vì những lý do sau

  • nó là công cụ phổ biến nhất trong số các công cụ đo điểm chuẩn liên quan đến thời gian trong Python;
  • nó là một phần của thư viện tiêu chuẩn, vì vậy bạn không cần phải cài đặt nó;
  • các công cụ khác thường là các trình bao bọc xung quanh timeit

Vì vậy, tôi nghĩ rằng nếu bạn muốn sử dụng những công cụ khác này, trước tiên bạn nên học cách sử dụng timeit và giải thích kết quả của nó. Bài viết này nhằm mục đích giúp bạn với điều đó. Tôi sẽ chỉ cho bạn một tính năng ít được biết đến của mô-đun này. chức năng đo điểm chuẩn thay vì đoạn mã. Tôi cũng sẽ cho bạn thấy những tình huống trong đó kết quả của timeit có thể gây hiểu nhầm

Sử dụng thời gian

Hầu hết người dùng không biết rằng mô-đun timeit cung cấp hai API, đó là lý do tại sao bạn sẽ thấy chủ yếu một trong số chúng được sử dụng. Hai API như sau

  • API dựa trên đoạn mã. Hầu hết mọi người nhắc đến timeit đều nghĩ đến chính API này. Nó có hai lợi thế. nó tương đối dễ sử dụng và bạn có thể đánh giá hầu hết mọi thứ vì nó đánh giá các đoạn mã
  • API dựa trên có thể gọi được. Hầu hết mọi người thực sự không biết API này. Nó nhằm mục đích chuẩn hóa các cuộc gọi. Thật không may, cú pháp của nó kém tự nhiên hơn cú pháp của API dựa trên đoạn trích, nhưng nó có thể hữu ích trong một số trường hợp. Tôi sẽ chỉ ra một tình huống như vậy sau trong bài viết này

Cả hai API đều sử dụng các chức năng giống nhau, nhưng khác nhau. Hai chức năng bạn nên biết là timeit.timeit()timeit.repeat(). Sự thật là, tôi hầu như luôn sử dụng cái sau, vì nó chỉ chạy cái trước nhiều lần và cung cấp các kết quả riêng lẻ của timeit.timeit(), vì vậy bạn sẽ nhận được kết quả ổn định hơn. Tôi khuyên bạn nên làm như vậy. Vì lý do này, tôi sẽ thảo luận về chức năng timeit.repeat(); . thiếu đối số ____12

Cách sử dụng đơn giản nhất như sau

Và đó là nó. Nó sẽ đo thời gian thực thi của đoạn mã được cung cấp dưới dạng chuỗi. Trong trường hợp của chúng tôi, đó là timeit3, có nghĩa là chúng tôi sẽ đo lượng thời gian mà Python sử dụng để tạo danh sách bằng cách hiểu danh sách một triệu lần. Triệu lần này là giá trị mặc định của đối số timeit4. Hãy nhớ rằng mỗi triệu cuộc gọi của lệnh được thực hiện lần lượt, trong cùng một phiên;

Hãy phân tích chữ ký của chức năng

  • timeit5 là đoạn mã bạn muốn đánh giá chuẩn, được cung cấp dưới dạng một chuỗi;
  • timeit4 là số lần timeit5 sẽ được gọi, trong một phiên;, được cung cấp dưới dạng số nguyên;
  • timeit9 là mã bạn muốn chạy trước khi chạy timeit5 cho timeit4 lần, được cung cấp dưới dạng chuỗi;
  • timeit2 là bộ đếm thời gian được sử dụng;
  • timeit2 là số phiên chạy, mỗi phiên bao gồm timeit4 cuộc gọi của timeit5;
  • timeit7 là một từ điển toàn cầu sẽ được cung cấp;

Đối với các đoạn trích nhỏ và nhanh, không cần thay đổi timeit4 và timeit2, trừ khi bạn muốn điểm chuẩn của mình cung cấp kết quả rất ổn định. Nếu bạn làm như vậy, bạn nên tăng những con số này, tùy thuộc vào mức độ ổn định mà bạn muốn kết quả và thời gian bạn muốn điểm chuẩn chạy

Tuy nhiên, đối với các đoạn trích dài hơn, bạn có thể muốn giảm timeit4 và timeit2 hoặc cả hai, vì nếu không, điểm chuẩn có thể mất quá nhiều thời gian. Tuy nhiên, bạn sẽ nhận thấy rằng sau khi làm như vậy, tức là với các giá trị quá nhỏ của timeit4 và timeit2, kết quả có thể trở nên không ổn định

Ở trên, chúng tôi đã sử dụng API dựa trên đoạn trích. Đã đến lúc thảo luận về API dựa trên khả năng gọi hiếm khi được biết đến

Bạn có thể nghĩ rằng hai lệnh gọi timeit.repeat() ở trên sẽ cung cấp kết quả tương tự vì chúng đánh giá cùng một thứ, cụ thể là tạo danh sách bằng cách sử dụng khả năng hiểu danh sách từ đối tượng timeit6 có độ dài 10. Nhưng điều đó không đúng. cái đầu tiên thực sự đánh giá việc tạo danh sách theo cách đó, nhưng cái sau thì không, hay đúng hơn là không chỉ. Điều này là do cái sau bao gồm cả chi phí chạy hàm timeit7 và đôi khi chi phí này có thể khá đáng kể. Chúng ta có thể, thực sự, phân tích điều này

Cuộc gọi đầu tiên từ các cuộc gọi trên tương đương với timeit8, nhưng nó sử dụng cùng một API như cuộc gọi thứ hai. Vì vậy, nếu chúng ta thấy sự khác biệt giữa timeit9 và timeit0, thì đó là do chi phí gọi một hàm hàng triệu lần

Trên máy của tôi (32 GB, bốn lõi vật lý và tám lõi logic, chạy trong WSL 1), tôi nhận được các kết quả sau

Sự khác biệt là khá nhỏ, phải không? . Một lần nữa, timeit3 lớn hơn, với timeit4 so với timeit5 của timeit6. Tuy nhiên, bạn phải nhớ rằng đây không phải là những điểm chuẩn dài như vậy… Chúng mất tổng cộng 78 giây và lần sau khi tôi chạy chúng, tôi nhận được kết quả của timeit7 so với timeit8. Vì vậy, nếu bạn muốn chắc chắn về điểm chuẩn của mình, hãy sử dụng các giá trị lớn hơn nhiều của cả timeit4 và timeit2. Tuy nhiên, khi sự khác biệt về thời gian thực hiện của hai (hoặc nhiều) đoạn mã lớn, bạn không cần phải sử dụng các giá trị lớn

Đối với việc so sánh hai API, hãy nhớ

Hai API — dựa trên đoạn trích và dựa trên có thể gọi — có thể tạo ra các kết quả khác nhau ngay cả khi chúng đánh giá mã chuẩn thực hiện cùng một việc. Điều này là do kết quả của API dựa trên có thể gọi được bao gồm cả thời gian chung để gọi có thể gọi được

Cái nào để chọn? . Nếu bạn muốn so sánh thời gian thực hiện của hai chức năng, thì đây chính xác là mục đích mà API dựa trên có thể gọi được đã tạo ra. Hoặc, khi trong mã của bạn, bạn làm điều gì đó (e. g. , phân bổ một danh sách, như trên) trong một hàm, thì API dựa trên có thể gọi được sẽ phản ánh tình hình thực tế tốt hơn. Tuy nhiên, nếu bạn chỉ muốn so sánh hai đoạn mã, thì không cần sử dụng hàm cho điều đó. Tuy nhiên, hãy nhớ về phạm vi. Khi bạn đóng gói mã của mình bên trong một hàm, tất cả điều đó sẽ được thực hiện trong phạm vi cục bộ và không gian tên của hàm này. Như tôi sẽ trình bày sau, điều này có thể tạo nên sự khác biệt

Thí dụ. Tạo một từ điển trống

Bây giờ, hãy tưởng tượng chúng ta muốn đánh giá hai cách để tạo một từ điển trống. timeit1 và timeit2. Chúng ta có thể làm theo cách sau

dự đoán của bạn là gì?

Nếu bạn đã bình chọn cho nghĩa đen là nhanh hơn, bạn đã hiểu đúng. Trên máy của tôi, timeit3 cho timeit4 trong khi timeit5 cho timeit6, vì vậy có sự khác biệt khá lớn. Chúng ta có thể phân tích mã byte của cả hai phương pháp để xem sự khác biệt về hiệu suất này đến từ đâu

Như bạn thấy, timeit2 sử dụng thêm một thao tác mã byte, thao tác mà nó gọi là hàm timeit2; . Nếu bạn thấy một số điểm tương đồng với ví dụ trước, thì bạn đã đúng;

Thật thú vị, nếu bạn tạo chức năng của riêng mình như sau

bạn sẽ thấy nó nhanh hơn chính hàm timeit2. Tôi để bạn kiểm tra điều này như một bài tập. (Đây là niềm vui của việc đo điểm chuẩn mà tôi đã nói với bạn ở trên. )

Tại sao giá trị tối thiểu?

Bạn có thể thắc mắc tại sao tôi lại sử dụng giá trị nhỏ nhất, không phải giá trị trung bình, để so sánh hai kết quả điểm chuẩn (xem hàm timeit1 ở trên). Tại sao không phải là giá trị trung bình, với tư cách là thước đo xu hướng trung tâm, và phương sai (hoặc độ lệch chuẩn, hoặc vẫn là một thước đo khác) là thước đo biến thiên?

Điều này là do điểm chuẩn không phải là một tình huống bình thường, vì vậy chúng ta không nên áp dụng phương pháp thống kê điển hình để phân tích kết quả của nó. Trong điều kiện tốt nhất, tất cả các lần chạy của cùng một mã (không bao gồm tính ngẫu nhiên) sẽ mất cùng thời gian. Tuy nhiên, hệ điều hành đang thực hiện nhiều tác vụ khác nhau trong quá trình đo điểm chuẩn, vì vậy các lần đo điểm chuẩn tiếp theo sẽ mất nhiều thời gian khác nhau. Hãy nhớ rằng điều này không phải do bản thân mã mà do các quy trình của hệ điều hành

Do đó, chúng ta nên lấy giá trị tối thiểu, vì nó là giá trị gần nhất với thời gian thực hiện thực tế, không có bất kỳ sự gián đoạn nào. Tương tự như vậy, chúng ta không nên chú ý đến sự thay đổi trong kết quả trong các lần chạy. Chúng không đo lường mức độ biến đổi của thời gian thực thi thực tế của đoạn mã cụ thể này; . Thay vào đó, sự thay đổi như vậy thay vì đo mức độ thay đổi của các quy trình khác, những quy trình được điều hành bởi hệ điều hành; . Đây là lý do tại sao phân tích điểm chuẩn khác rất nhiều so với phân tích các loại dữ liệu khác và đó là lý do tại sao giá trị tối thiểu là thước đo tốt hơn để thể hiện kết quả của điểm chuẩn so với bất kỳ thước đo xu hướng trung tâm nào

Một vi dụ khac

Hãy xem xét một kịch bản phức tạp hơn. Lần này, chúng tôi sẽ sử dụng timeit9 để thiết lập môi trường

Một lần nữa, trước khi chạy mã, hãy phân tích mã và thử đoán chức năng nào sẽ nhanh hơn. (Lần này không đơn giản như vậy, vì bạn cần biết mô-đun timeit3 hoạt động như thế nào. )

Lưu ý rằng bạn có thể đơn giản hóa đoạn mã trên một chút nhưng. Đơn giản hóa như vậy có ý nghĩa đặc biệt là khi bạn có nhiều đoạn để so sánh

Ở đây, timeit4 xảy ra chậm hơn gần 5 lần so với timeit5, một sự khác biệt khá lớn

Trước đây, tôi đã hứa với bạn sẽ hiển thị một ví dụ khi API dựa trên có thể gọi được có ý nghĩa hơn so với API dựa trên đoạn trích. Đây là một tình huống như vậy. Lưu ý rằng lần này việc sử dụng các hàm trông tự nhiên hơn so với việc sử dụng các đoạn mã, bởi vì các đoạn mã trên chỉ đơn giản là gọi các hàm

Các hàm lấy một đối số, điều này làm cho việc sử dụng API dựa trên có thể gọi trở nên phức tạp hơn một chút. Chúng tôi cần sử dụng timeit6, đây là một nhược điểm nhỏ của API này

Chúng tôi không cần sử dụng timeit9, vì chúng tôi đã nhập mô-đun timeit3. Khi chúng tôi sử dụng API dựa trên có thể gọi được, chúng tôi sẽ chạy các điểm chuẩn trong môi trường hiện tại, trừ khi bạn tác động đến nó bằng cách thay đổi đối số timeit7 của timeit.repeat(). Vì vậy, nếu môi trường có nhiều vật thể lớn, điểm chuẩn thực sự có thể cho thấy hiệu suất kém hơn so với điểm chuẩn trong môi trường gần như trống rỗng

Bạn nên biết giá trị mà timeit.repeat() trả về là gì. Độ dài của danh sách bằng timeit4, mỗi giá trị biểu thị tổng thời gian, tính bằng giây, chạy các lệnh gọi timeit4 của đoạn mã/có thể gọi được. Bạn có thể tính toán thời gian chạy đoạn mã trung bình;

Coi chừng

Bạn phải nhớ những gì tôi đã đề cập trước đây. Các hàm timeit lần lượt chạy cùng một lệnh trong cùng một phiên. Điều này đặc biệt quan trọng khi bạn làm việc với các đối tượng có thể thay đổi, nhưng không chỉ

Hãy xem xét một ví dụ. Hãy tưởng tượng bạn muốn so sánh một biểu thức trình tạo với danh sách tương ứng. Giả sử, bạn sẽ tiêu thụ hai vật phẩm trong một vòng lặp timeit.timeit()7, nhưng không làm gì cả — theo cách đó, bạn muốn so sánh chi phí sử dụng của hai loại vật thể. Ví dụ,

Và chúng tôi nhận được timeit.timeit()8 cho biểu thức trình tạo so với timeit.timeit()9 cho danh sách. Đó là một kết quả tuyệt vời. Tạo danh sách 100 phần tử chậm hơn 42 lần so với biểu thức trình tạo?

Hoặc là nó?

Bất cứ khi nào bạn thấy kết quả điên rồ như vậy, hãy kiểm tra kỹ mã. Tôi không nói rằng những kết quả như vậy không bao giờ xảy ra;

Bạn có thấy vấn đề trong mã này không? . Có gì đó không ổn với nó

Vấn đề là kết quả của việc sử dụng một máy phát điện. Bạn chỉ có thể sử dụng trình tạo một lần và sau đó trình tạo sẽ trống. Vì vậy, trong mã này, nó chỉ được lặp lại một lần và sau đó nó không được lặp lại nữa vì lý do đơn giản là nó trống. Tuy nhiên, danh sách được lặp đi lặp lại mỗi lần

Đó là lý do tại sao đoạn mã tương ứng mất rất nhiều thời gian. Vòng lặp timeit.timeit()7 chỉ lặp qua timeit.repeat()1 một lần, trong lần gọi đầu tiên và sau đó không thực hiện gì vì timeit.repeat()1 trống. Tuy nhiên, với danh sách, nó sẽ lặp lại mỗi khi đoạn trích được gọi. Do đó sự khác biệt

Chúng ta có thể giải nó một cách dễ dàng, sử dụng timeit.repeat(). Chúng ta có thể sử dụng timeit.repeat()4 và một giá trị tuyệt vời cho timeit2. Theo cách đó, mỗi lần lặp lại (phiên) sẽ thực sự lặp qua biểu thức trình tạo, vì nó sẽ được tạo lại trong mỗi phiên tiếp theo

Tôi đã nhân kết quả với timeit2 vì nếu không thì chúng sẽ rất nhỏ, thể hiện thời gian lặp qua một vòng lặp timeit.timeit()7

Bây giờ chúng ta có timeit.repeat()8 cho biểu thức trình tạo so với timeit.repeat()9 cho danh sách. Lần này - khi chúng tôi sử dụng một cách tiếp cận đúng - danh sách nhanh hơn khoảng 17 lần so với biểu thức trình tạo

Tình huống tương tự có thể xảy ra khi bạn thao tác trên một đối tượng có thể thay đổi. Nếu nó bị ảnh hưởng trong mọi cuộc gọi, thì cuộc gọi tiếp theo sẽ hoạt động với phiên bản cập nhật này của đối tượng, không phải phiên bản gốc. Do đó, mỗi cuộc gọi hoạt động với một đối tượng khác nhau. Một ví dụ có thể là đánh giá cách thức hoạt động của timeit.timeit()0 đối với danh sách. Mỗi khi bạn thêm một mục vào danh sách, danh sách sẽ dài ra và do đó, các lần thêm tiếp theo là không thể so sánh được. Chơi với cái này để xem nó hoạt động như thế nào và vì niềm vui thuần túy của việc đo điểm chuẩn

lựa chọn thay thế

Python cung cấp các lựa chọn thay thế điểm chuẩn thời gian khác nhau. Ba trong số đó mà tôi muốn chỉ ra là

  • timeit.timeit()1, một trình lược tả Python tích hợp sẵn;
  • timeit.timeit()2, một gói để kiểm tra hiệu suất, về thời gian thực hiện và mức sử dụng bộ nhớ, cũng cung cấp các công cụ đo điểm chuẩn và lập hồ sơ;
  • timeit.timeit()3, một gói để gỡ lỗi và đo điểm chuẩn cho mã Python

Phần kết luận

Mô-đun timeit có lẽ là cách dễ nhất để đánh giá mã. Đó là một giải pháp tích hợp sẵn và API của nó tương đối dễ sử dụng. Tôi chắc chắn khuyên dùng nó, nhưng hãy cân nhắc sử dụng hàm timeit.repeat() thay vì timeit.timeit()

Theo thời gian, bạn sẽ nhận thấy rằng thường thì mã được tối ưu hóa đến từng chi tiết không thực sự quan trọng. Ví dụ: có thực sự quan trọng nếu bạn tìm cách tiết kiệm 10 mili giây khi mã kéo dài 10 giờ không?

Để tóm tắt

  • Tối ưu hóa hiệu suất khi nó thực sự quan trọng. Nếu không, hãy cố gắng để mã có thể đọc được
  • Cố gắng tìm sự cân bằng phù hợp giữa độ phức tạp của mã và hiệu suất
  • Tối ưu hóa mã cần có thời gian. Có đáng để dành 10 giờ tối ưu hóa mã để tiết kiệm một giây mỗi tháng không?
  • Mô-đun timeit cung cấp các phương thức tích hợp để đo điểm chuẩn thời gian thực hiện. Nó khá đơn giản để sử dụng, nhưng bạn nên coi nó là một công cụ đo điểm chuẩn, không phải là một công cụ lập hồ sơ
  • Khi bạn biết rằng một chức năng này nhanh hơn một chức năng khác, có thể đọc được như chức năng kia và sử dụng nó trong mã không mất nhiều thời gian hơn — hãy sử dụng nó. Tại sao bạn lại sử dụng một chức năng/phương pháp chậm hơn trong tình huống như vậy?
  • Nếu bạn muốn tìm hiểu những điều phức tạp khác nhau của Python, thì timeit có thể giúp ích rất nhiều. Kết hợp với các công cụ khác, nó có thể giúp bạn hiểu cách thức hoạt động của Python
  • Nếu bạn muốn tìm hiểu thêm về tối ưu hóa mã, lập hồ sơ và các chủ đề liên quan trong Python, cuốn sách của Gorelick và Ozsvald (2020) là bạn của bạn

Cảm ơn vì đã đọc. Tôi hy vọng bạn thích bài viết và mô-đun timeit. Nếu bạn đã làm, hãy lưu ý rằng đó không phải là kết thúc của câu chuyện. Trong các bài viết sau, tôi sẽ thảo luận về các công cụ đo điểm chuẩn khác

Làm cách nào để sử dụng Timeit trong Python?

Đây là một hàm dựng sẵn trong Python dùng để đo thời gian thực thi một đoạn mã Python nhỏ. .
Syntax. timeit.timeit(setup = , stmt = , number = , .. .
Thông số. → thiết lập. mã cần chạy trước stmt. Giá trị mặc định là 'vượt qua'. → hẹn giờ. thời gian. .
Thí dụ. Mã số. # kiểm tra thời gian()

Làm cách nào để kiểm tra thời gian thực thi Python?

Để tính thời gian thực thi mã, mô-đun thời gian có thể được sử dụng. .
Lưu dấu thời gian ở đầu mã bắt đầu sử dụng time()
Lưu dấu thời gian ở cuối mã
Tìm sự khác biệt giữa kết thúc và bắt đầu, cho biết thời gian thực hiện

Timeit có được bao gồm trong Python không?

time(). Vì vậy nó không chính xác trong khi so sánh các chức năng. Để giải quyết vấn đề này, Python có một mô-đun timeit dùng để đo thời gian thực thi của các đoạn mã lớn và nhỏ. Ở đây trong bài viết này, trước tiên chúng ta hãy xem thư viện timeit trong python bằng một số ví dụ.