Hướng dẫn python cpu benchmark - tiêu chuẩn cpu python

Trong khi chuẩn bị viết loạt Python viết nhanh hơn, vấn đề đầu tiên tôi gặp phải là "Làm cách nào để điểm chuẩn một đoạn mã theo một cách khách quan nhưng không phức tạp".

Tôi có thể chạy python -m timeit <piece of code>, đây có lẽ là cách đơn giản nhất để đo thời gian để thực thi một số mã [1]. Nhưng có lẽ nó quá đơn giản, và tôi nợ độc giả của mình một số cách để điểm chuẩn sẽ không bị can thiệp bởi các cpu đột ngột trên máy tính của tôi?

Vì vậy, đây là một vài công cụ và kỹ thuật khác nhau tôi đã thử. Vào cuối bài viết, tôi sẽ cho bạn biết tôi đã chọn cái nào và tại sao. Thêm vào đó, tôi sẽ cung cấp cho bạn một số quy tắc ngón tay cái khi mỗi công cụ có thể tiện dụng.

Python -M Timeit

Cách dễ nhất để đo lường thời gian mất bao lâu để chạy một số mã là sử dụng mô -đun thời gian. Bạn có thể viết python -m timeit your_code() và Python sẽ in ra thời gian để chạy bất cứ điều gì your_code() làm. Tôi muốn đặt mã tôi muốn điểm chuẩn trong một hàm để rõ ràng hơn, nhưng bạn không cần phải làm điều này. Bạn có thể trực tiếp viết nhiều câu lệnh Python được phân tách bằng dấu chấm phẩy và điều đó sẽ hoạt động tốt. Ví dụ: để xem mất bao lâu để tổng hợp 1.000.000 số đầu tiên, chúng ta có thể chạy mã này:

python -m timeit "sum(range(1_000_001))"
20 loops, best of 5: 11.5 msec per loop

Tuy nhiên, cách tiếp cận python -m timeit có một nhược điểm lớn - nó không tách mã thiết lập khỏi mã bạn muốn để điểm chuẩn. Giả sử bạn có một tuyên bố nhập khẩu mất một thời gian tương đối dài để nhập so với thực hiện một hàm từ mô -đun đó. Một lần nhập như vậy có thể là import numpy. Nếu chúng ta đánh giá hai dòng mã đó:

import numpy
numpy.arange(10)

Việc nhập khẩu sẽ mất phần lớn thời gian trong quá trình chuẩn. Nhưng có lẽ bạn không muốn điểm chuẩn mất bao lâu để nhập các mô -đun. Bạn muốn xem mất bao lâu để thực hiện một số chức năng từ mô -đun đó.

Python -M Timeit -S "Cài đặt mã"

Để tách mã thiết lập khỏi điểm chuẩn, TimeIT hỗ trợ tham số

import numpy
numpy.arange(10)
0. Bất cứ mã nào bạn chuyển ở đây sẽ được thực thi nhưng sẽ không phải là một phần của điểm chuẩn. Vì vậy, chúng tôi có thể cải thiện mã trên và chạy nó như thế này:
import numpy
numpy.arange(10)
1.

Python -M Timeit -s "Cài đặt mã" -N 10000

Chúng tôi có thể nghiêm ngặt hơn một chút và quyết định thực hiện mã của chúng tôi cùng một số lần mỗi lần. Theo mặc định, nếu bạn không chỉ định tham số '-n' (hoặc-số), thời gian sẽ cố gắng chạy mã 1, 2, 5, 10, 20, ... cho đến khi tổng thời gian thực hiện vượt quá 0,2 giây . Một chức năng chậm sẽ được thực thi một lần, nhưng một chức năng rất nhanh sẽ chạy hàng ngàn lần. Nếu bạn nghĩ rằng việc thực thi các đoạn mã khác nhau một số lần khác nhau ảnh hưởng đến điểm chuẩn của bạn, bạn có thể đặt tham số này thành một số được xác định trước.

Docker

Một trong những vấn đề với điểm chuẩn chạy với python -m timeit là đôi khi các quy trình khác trên máy tính của bạn có thể ảnh hưởng đến quá trình Python và làm chậm ngẫu nhiên. Ví dụ, tôi đã nhận thấy rằng nếu tôi chạy các điểm chuẩn của mình với tất cả các ứng dụng thông thường sẽ mở (nhiều trường hợp chrome với nhiều tab, nhóm và các ứng dụng messenger khác, v.v.), tất cả chúng đều mất nhiều thời gian hơn khi tôi đóng tất cả các ứng dụng trên máy tính của tôi.

Vì vậy, trong khi cố gắng tìm ra cách tránh tình huống này, tôi quyết định cố gắng chạy các điểm chuẩn của mình ở Docker. Tôi đã đưa ra giải pháp sau:

import numpy
numpy.arange(10)
3

Mã trên sẽ:

  1. Chạy container Python Alpine Docker (một hình ảnh nhỏ, Barebones với Python).
  2. Gắn thư mục hiện tại bên trong thùng chứa Docker (vì vậy chúng tôi có thể truy cập các tệp chúng tôi muốn điểm chuẩn).
  3. Chạy cùng một lệnh thời gian như trước.

Và kết quả có vẻ phù hợp hơn so với không sử dụng Docker. Đánh dấu lại điểm chuẩn nhiều lần, tôi đã nhận được kết quả với độ lệch nhỏ hơn. Tôi vẫn có một sự sai lệch - một số lần chạy chậm hơn một chút và một số chạy nhanh hơn một chút. Tuy nhiên, đó là trường hợp cho các ví dụ mã ngắn (chạy dưới 1 giây). Đối với các ví dụ mã dài hơn (chạy ít nhất một vài giây), sự khác biệt giữa các lần chạy thậm chí là khoảng 5% (tôi đã thử nghiệm Docker với ví dụ về loại bong bóng của tôi từ việc nâng cấp bài viết phiên bản Python của bạn). Vì vậy, như một người bình luận cảnh giác đề nghị, Docker không thực sự giúp đỡ nhiều ở đây.

Thư viện điểm chuẩn Python

Tại một số điểm, bạn có thể quyết định rằng việc nhận được số "tốt nhất 5" mà thời gian trả về theo mặc định là không đủ. Điều gì sẽ xảy ra nếu tôi cần biết kịch bản bi quan nhất là gì (thời gian tối đa cần phải chạy mã của tôi)? Hoặc sự khác biệt giữa chạy chậm nhất và nhanh nhất? Điều này có phải là sự khác biệt lớn, và chức năng của tôi chạy trong một khoảng thời gian hoàn toàn không thể đoán trước? Hay nó nhỏ đến mức gần như không đáng kể?

Có các công cụ điểm chuẩn tốt hơn cung cấp nhiều số liệu thống kê về mã của bạn.

Ghế đóng băng phong phú

Công cụ đầu tiên tôi kiểm tra là gói băng ghế phong phú được tạo bởi Anthony Shaw cùng với kho lưu trữ chống mẫu của anh ấy cho một cuộc nói chuyện Pycon. Công cụ nhỏ này có thể điểm chuẩn một tập hợp các tệp với các ví dụ mã khác nhau và trình bày kết quả trong một bảng được định dạng độc đáo. Mỗi điểm chuẩn sẽ so sánh hai hàm khác nhau và trình bày giá trị trung bình, tối thiểu và tối đa của kết quả, do đó bạn có thể dễ dàng thấy sự lây lan giữa các kết quả.

Hướng dẫn python cpu benchmark - tiêu chuẩn cpu python

pyperf

Nếu bạn cần một công cụ điểm chuẩn nâng cao hơn, có lẽ bạn không thể sai nếu bạn chọn công cụ chính thức được sử dụng bởi bộ điểm chuẩn Python Performance - một nguồn điểm chuẩn có thẩm quyền cho tất cả các triển khai Python. PYPERF là một công cụ đầy đủ với nhiều tính năng khác nhau, bao gồm hiệu chuẩn tự động, phát hiện kết quả không ổn định, theo dõi việc sử dụng bộ nhớ và các chế độ công việc khác nhau, tùy thuộc vào việc bạn muốn so sánh các đoạn mã khác nhau hoặc nhận một loạt các chỉ số cho một hàm.

Hãy xem một ví dụ. Đối với các điểm chuẩn, tôi sẽ sử dụng một hàm đơn giản nhưng không hiệu quả để tính một khoản tiền của 1.000.000 số đầu tiên:

import numpy
numpy.arange(10)
4.

Đây là đầu ra từ mô -đun thời gian:

$ python -m timeit "sum(n * n for n in range(1_000_001))"
5 loops, best of 5: 41 msec per loop

Và đây là đầu ra của

import numpy
numpy.arange(10)
5:

$ python -m pyperf timeit "sum(n * n for n in range(1_000_001))" -o bench.json
.....................
Mean +- std dev: 41.5 ms +- 1.1 ms

Kết quả rất giống nhau, nhưng với tham số

import numpy
numpy.arange(10)
6, chúng tôi đã nói với PYPERF lưu trữ kết quả điểm chuẩn trong tệp JSON, vì vậy bây giờ chúng tôi có thể phân tích chúng và nhận được nhiều thông tin hơn:

$ python -m pyperf stats bench.json
Total duration: 14.5 sec
Start date: 2022-11-09 18:19:37
End date: 2022-11-09 18:19:53
Raw value minimum: 163 ms
Raw value maximum: 198 ms

Number of calibration run: 1
Number of run with values: 20
Total number of run: 21

Number of warmup per run: 1
Number of value per run: 3
Loop iterations per value: 4
Total number of values: 60

Minimum: 40.8 ms
Median +- MAD: 41.3 ms +- 0.2 ms
Mean +- std dev: 41.5 ms +- 1.1 ms
Maximum: 49.6 ms

0th percentile: 40.8 ms (-2% of the mean) -- minimum
5th percentile: 40.9 ms (-1% of the mean)
25th percentile: 41.2 ms (-1% of the mean) -- Q1
50th percentile: 41.3 ms (-0% of the mean) -- median
75th percentile: 41.5 ms (+0% of the mean) -- Q3
95th percentile: 41.9 ms (+1% of the mean)
100th percentile: 49.6 ms (+20% of the mean) -- maximum

Number of outlier (out of 40.7 ms..41.9 ms): 3

Hyperfine

Và trong trường hợp bạn muốn điểm chuẩn một số mã không phải là mã Python, luôn có siêu nhân có thể được sử dụng để đánh giá bất kỳ lệnh CLI nào. Hyperfine có một tập hợp các tính năng tương tự như PYPERF. Nó tự động thực hiện khởi động, xóa bộ đệm và phát hiện các ngoại lệ thống kê. Và tất cả những điều đó, với các thanh và màu sắc tiến bộ đẹp, chỉ làm cho đầu ra trông đẹp.

Bạn có thể chạy nó cho một lệnh và nó sẽ trả về thông tin thông thường như giá trị trung bình, tối thiểu và thời gian tối đa, độ lệch chuẩn, số lần chạy, v.v. Một là nhanh hơn:

Hướng dẫn python cpu benchmark - tiêu chuẩn cpu python

thời gian là tốt ... đối với tôi

Cuối cùng, tôi đã chọn một cách điểm chuẩn rất đơn giản:

import numpy
numpy.arange(10)
7. Tôi không phải sử dụng phương pháp điểm chuẩn hoàn hảo (nếu nó thậm chí tồn tại). . Điều đó là cần thiết nếu tôi là điểm chuẩn một đoạn mã và chia sẻ kết quả với thế giới. Tôi không thể sử dụng một phương pháp đo lường ngẫu nhiên, không hiệu quả để đo lường và cho bạn biết "đoạn mã này là xấu vì nó chạy trong 15 giây". Bạn có thể sử dụng một công cụ điểm chuẩn tốt hơn, chạy nó trên một máy tính mạnh mẽ và kết thúc với cùng một mã chạy trong 1,5 giây.

So sánh hai đoạn mã là một câu chuyện khác nhau. Chắc chắn, một phương pháp điểm chuẩn tốt, đáng tin cậy là quan trọng. Nhưng cuối cùng, chúng tôi quan tâm đến sự khác biệt về tốc độ tương đối giữa các ví dụ mã. Nếu máy tính của tôi chạy "Ví dụ A" trong 10 giây và "Ví dụ B" trong 20 giây, nhưng máy tính của bạn sẽ chạy chúng trong 5 và 10 giây, cả hai chúng tôi đều có thể kết luận rằng "Ví dụ B" chậm gấp đôi.

Sử dụng

import numpy
numpy.arange(10)
8 là đủ tốt. Nó cho phép tôi tách mã thiết lập khỏi mã thực tế mà tôi muốn điểm chuẩn. Và nếu bạn muốn chạy các điểm chuẩn tương tự trên máy tính của bạn, bạn có thể làm điều này ngay lập tức. Bạn đã cài đặt
import numpy
numpy.arange(10)
8 với phân phối Python. Bạn không phải cài đặt bất kỳ thư viện bổ sung nào hoặc thiết lập Docker.

Điều quan trọng hơn nhiều so với công cụ chính xác nhất là cách bạn thiết lập điểm chuẩn của mình.

Cẩn thận với cách bạn cấu trúc mã của mình

Điểm chuẩn chạy là phần dễ dàng. Phần khó khăn là nhớ viết mã của bạn theo cách không "gian lận". Khi tôi lần đầu tiên viết bài viết danh sách sắp xếp, tôi rất vui khi thấy

$ python -m timeit "sum(n * n for n in range(1_000_001))"
5 loops, best of 5: 41 msec per loop
0 nhanh hơn rất nhiều so với
$ python -m timeit "sum(n * n for n in range(1_000_001))"
5 loops, best of 5: 41 msec per loop
1. "OMG, tôi đã tìm thấy Chén Thánh sắp xếp trong Python" - tôi nghĩ. Sau đó, ai đó chỉ ra rằng
$ python -m timeit "sum(n * n for n in range(1_000_001))"
5 loops, best of 5: 41 msec per loop
2 sắp xếp danh sách tại chỗ. Vì vậy, nếu tôi chạy điểm chuẩn của mình, lần lặp đầu tiên sẽ sắp xếp danh sách (chậm) và mỗi lần lặp tiếp theo sẽ sắp xếp một danh sách đã được sắp xếp (nhanh hơn nhiều). Tôi đã phải cập nhật bài viết của mình và bắt đầu chú ý nhiều hơn đến cách tôi tổ chức các điểm chuẩn của mình.

Sự kết luận

Tùy thuộc vào trường hợp sử dụng của bạn, bạn có thể đạt được một công cụ khác để đánh giá mã của bạn:

  • $ python -m timeit "sum(n * n for n in range(1_000_001))"
    5 loops, best of 5: 41 msec per loop
    3 cho các điểm chuẩn đơn giản nhất, dễ chạy nhất, nơi bạn chỉ muốn nhận "một số".
  • $ python -m timeit "sum(n * n for n in range(1_000_001))"
    5 loops, best of 5: 41 msec per loop
    4 là phiên bản hữu ích hơn nhiều nếu bạn muốn tách một số mã thiết lập khỏi điểm chuẩn thực tế.
  • $ python -m timeit "sum(n * n for n in range(1_000_001))"
    5 loops, best of 5: 41 msec per loop
    5 - Mặc dù có vẻ như nó đã thực hiện một công việc tốt hơn khi tách các điểm chuẩn của tôi với các quy trình khác, do đó làm giảm độ lệch giữa các lần chạy, sau khi kiểm tra kỹ lưỡng, đó dường như là trường hợp cho các ví dụ rất ngắn. Đối với những người dài hơn, nó không thực sự thay đổi nhiều.
  • $ python -m timeit "sum(n * n for n in range(1_000_001))"
    5 loops, best of 5: 41 msec per loop
    6 trông giống như một giải pháp tốt nếu bạn cần một công cụ chuyên dụng với các số liệu thống kê bổ sung như Min, Max, Median và Định dạng đầu ra đẹp. Nhưng bạn sẽ cần thiết lập điểm chuẩn của mình trong một cấu trúc cụ thể mà băng ghế phong phú yêu cầu.
  • import numpy
    numpy.arange(10)
    5 cung cấp cho bạn bộ thống kê nâng cao nhất về mã của bạn. Và nó được sử dụng bởi các điểm chuẩn Python chính thức, vì vậy đây là một công cụ tuyệt vời cho các điểm chuẩn nâng cao.
  • $ python -m timeit "sum(n * n for n in range(1_000_001))"
    5 loops, best of 5: 41 msec per loop
    8 là một công cụ tuyệt vời để đánh giá bất kỳ lệnh nào, không chỉ mã Python.Hoặc để so sánh hai lệnh khác nhau.

  1. OK, về mặt kỹ thuật, tôi có thể in thời gian hiện tại bằng

    $ python -m timeit "sum(n * n for n in range(1_000_001))"
    5 loops, best of 5: 41 msec per loop
    9, chạy mã của tôi, in lại
    $ python -m timeit "sum(n * n for n in range(1_000_001))"
    5 loops, best of 5: 41 msec per loop
    9 và trừ hai giá trị đó.Nhưng, thôi nào, không đơn giản, đó là thô sơ.↩︎