+− THE DAILY DIFFdev & AI news
SHIP IT

Hiệu suất Python: vòng lặp NumPy gốc, callbacks và chi phí chuyển đổi

Một thử nghiệm số học chỉ dùng CPU được kiểm soát theo dõi các giá trị giống hệt nhau thông qua các vòng lặp CPython, số học NumPy, một trình bao callback Python và một vòng lặp Numba đã biên dịch.

Want to see our stories first on Google? Add us to your preferred sources — it helps us grow.

Một thử nghiệm số học chỉ dùng CPU được kiểm soát theo dõi các giá trị giống hệt nhau thông qua các vòng lặp CPython, số học NumPy, một trình bao callback Python và một vòng lặp Numba đã biên dịch. NumPy với đầu vào sẵn có một lợi thế lớn, trong khi việc chuyển đổi từ và sang danh sách Python đã giảm lợi thế đó xuống gần bằng. Tập này giải thích ranh giới thực thi và cách đo lường kết quả mà một ứng dụng cần.

Đọc phiên bản viết (tiếng Anh) ↗

Nội dung video này đề cập

  • CPython biên dịch mã nguồn thành bytecode. Công việc lặp đi lặp lại của trình thông dịch/đối tượng và một vòng lặp số học gốc được đánh máy là các sắp xếp thực thi khác nhau.
  • Số học NumPy có thể truyền các lô dữ liệu đã được đánh máy cho các hoạt động đã biên dịch; vòng lặp số học vẫn tồn tại và các "view" có thể có các bố cục không liên tục.
  • Đo lường chi phí chuyển đổi và biểu diễn đầu ra cần thiết. Trong lần chạy này, việc chuyển đổi danh sách Python thành mảng và ngược lại đã xóa bỏ lợi thế lớn của mảng sẵn sàng.
  • Các khối lượng công việc nhỏ có thể ưu tiên Python thông thường vì chi phí gọi mảng cố định quan trọng. Tập hợp tám giá trị đã thắng trong phép so sánh này.
  • np.vectorize là một trình bao tiện lợi gọi Python cho các phần tử kế tiếp; một giao diện theo lô không tự nó thiết lập thực thi số học gốc.
  • Các vòng lặp cú pháp Python đã biên dịch cũng có thể nhanh. Bao gồm chi phí biên dịch/thiết lập ban đầu khi chúng quan trọng đối với người gọi.
  • Thực thi gốc, CPU SIMD, đa luồng và các backend GPU là những ý tưởng riêng biệt. Các tính năng CPU có sẵn không theo dõi các lệnh được sử dụng trong một lần chạy cụ thể.

Bản ghi đã dịch

Được dịch từ lời tường thuật tiếng Anh gốc. Âm thanh và phụ đề có sẵn được điều khiển bởi YouTube.

Tại sao cùng một CPU lại có thể hoàn thành sớm hơn?

0:00 Bạn nghĩ Python cần một ngôn ngữ mới để chạy nhanh. Cùng một CPU đã chạy lô của chúng ta nhanh hơn sau khi vòng lặp di chuyển. Công việc đã đi đâu? Khi nào thì sự tăng tốc biến mất? Lối tắt nào đưa Python trở lại? Đây là The Daily Diff, dưới vỏ bọc. Hãy để mắt đến lối tắt đó. CPython là triển khai Python tiêu chuẩn.

0:17 Nó biên dịch mã nguồn của bạn thành bytecode, sau đó chạy các lệnh đó trong một trình thông dịch. NumPy là một thư viện số học có các mảng chứa các giá trị với một kiểu dữ liệu đã khai báo. Phép toán số học tích hợp của nó có thể giao toàn bộ lô cho các hoạt động đã biên dịch. Lấy một triệu giá trị dấu phẩy động, bình phương từng giá trị, sau đó cộng giá trị gốc. Mọi phiên bản đều nhận các giá trị giống hệt nhau và tạo ra các câu trả lời khớp nhau. Các giá trị đủ nhỏ để không tạo ra sự khác biệt làm tròn trong cuộc đua.

0:40 Trên máy này, việc hiểu hết mất khoảng ba mươi ba mili giây. NumPy mất chưa đến một. Cả hai đều chạy trên CPU. Đó là bằng chứng hữu ích cho khối lượng công việc này, và một lời cáo phó tồi tệ cho một ngôn ngữ lập trình. Bắt đầu với vòng lặp thông thường.

Một vòng lặp Python lặp lại công việc gì?

0:53 Python lấy giá trị tiếp theo, thực hiện phép toán số học và thêm câu trả lời. Sau đó nó làm lại điều đó. Phép nhân thì rẻ. Sắp xếp phép nhân tiếp theo, và xử lý các đối tượng Python xung quanh nó, thêm công việc mỗi lần. Đây là phân rã thực tế của hàm được kiểm tra. Lặp, nhân và cộng xuất hiện dưới dạng lệnh bytecode. Các phiên bản và trình thông dịch Python đưa ra các lựa chọn khác nhau.

1:14 Các thời điểm này thuộc về phiên bản trên màn hình, đó là Python 3.10. Một danh sách Python lưu trữ tham chiếu đến các đối tượng, vì vậy các loại đối tượng khác nhau có thể chia sẻ một danh sách. Công việc số học của chúng ta sử dụng số trong suốt. Nó trả tiền cho một sắp xếp chung trong khi yêu cầu một dịch vụ lặp đi lặp lại. Hãy tưởng tượng bạn đặt cùng một loại cà phê một triệu lần và điền một biểu mẫu mới cho mỗi cốc. Máy pha cà phê vẫn ổn.

1:36 Giấy tờ đã tự phát triển sự nghiệp của riêng nó.

NumPy di chuyển vòng lặp đến đâu?

1:38 NumPy thay đổi hợp đồng. Mảng này chứa các giá trị dấu phẩy động kích thước cố định với một kiểu được khai báo. Mảng của chúng ta liên tục, vì vậy các giá trị của nó nằm liền kề trong bộ nhớ. Các "view" có thể có các bố cục khác. Phép toán số học biết loại dữ liệu mà nó đang nhận. Lệnh gọi giao lô cho một hoạt động gốc. Một vòng lặp vẫn duyệt qua các giá trị bên trong mã đã biên dịch. Nó có thể tái sử dụng kiến thức về kiểu trong suốt công việc đó.

2:00 Python vẫn là người giao việc. Itamar Turner Trauring giải thích sự khác biệt trong Python Speed. Giao diện theo lô, triển khai gốc và các lệnh vector của bộ xử lý là những thứ riêng biệt. Tên hàm cung cấp rất ít bằng chứng.

Nhanh có nghĩa là GPU hay nhiều lõi CPU?

2:13 Kết quả này sử dụng NumPy trên CPU. Một backend GPU như CuPy chọn một nơi khác cho các mảng và hoạt động. Mua một GPU vẫn khiến vòng lặp Python thông thường này chạy ở nơi nó đã chạy. Các lệnh vector của CPU có thể xử lý nhiều giá trị cùng lúc. NumPy có cơ chế để chọn các kernel được hỗ trợ. Thời gian chạy của chúng tôi báo cáo các tính năng có sẵn, nhưng không theo dõi các lệnh của biểu thức này. Gán toàn bộ lợi ích đo được cho các lệnh vector sẽ phóng đại bằng chứng.

2:40 Giỏ hàng GPU có thể vẫn đóng. Có một điều gì đó cực kỳ thỏa mãn về một bản sửa lỗi hiệu suất cũng hủy bỏ một lần mua. Thật không may, điểm chuẩn vẫn tiếp tục.

Khi nào thì sự tăng tốc ấn tượng biến mất?

2:49 Với tám giá trị, việc hiểu đã thắng. Việc gọi cơ chế mảng tốn kém hơn công việc nhỏ. Các mảng lớn hơn đã vượt lên. Chúng tôi đã sử dụng các thử nghiệm lặp lại và một quy trình đã được "làm nóng". Các bản dựng Python mới hơn có thể thay đổi điểm giao nhau. Sau đó thay đổi những gì ứng dụng cần. Bắt đầu với một danh sách Python và kết thúc bằng một danh sách Python mới. NumPy phải chuyển đổi đầu vào thành một mảng và chuyển đổi câu trả lời trở lại thành các đối tượng Python.

3:11 Những bước đó thuộc về hóa đơn nếu ứng dụng của bạn cần chúng. Với một triệu giá trị, con đường hoàn chỉnh đó mất khoảng hai mươi tám mili giây. Việc hiểu cũng mất khoảng hai mươi tám. Lợi thế đáng kể của chúng ta trở thành một kết quả gần như hòa. Phần nhanh là có thật. Lợi thế phụ thuộc vào việc giữ dữ liệu ở dạng số của nó.

Những ranh giới nào thuộc về điểm chuẩn?

3:28 Theo dõi dữ liệu. Nếu thao tác tiếp theo chấp nhận một mảng, hãy giữ nguyên mảng. Nếu bạn liên tục chuyển đổi giữa danh sách và mảng, hãy đo lường những lần chuyển đổi đó. Việc dừng lại trước một lần chuyển giao tốn kém sẽ cho các số chính xác cho một câu hỏi sai. Nhân mảng này tạo ra một kết quả tạm thời. Thêm các giá trị gốc tạo ra đầu ra. Thực thi gốc vẫn cần bộ nhớ và di chuyển dữ liệu.

3:47 Một số hoạt động chấp nhận bộ đệm đầu ra, giảm thiểu phân bổ khi mã của bạn có thể an toàn tái sử dụng nó. Một dòng lệnh đẹp có thể đặt hàng một số bộ đệm lớn. Python Speed cũng nêu ra vấn đề này. Bộ cấp phát hoàn toàn thờ ơ với tính thẩm mỹ của bạn.

Một vòng lặp Python đã biên dịch cũng có thể nhanh chứ?

4:00 Bạn cũng có thể giữ cú pháp vòng lặp và thay đổi cách thực thi của nó. Numba có thể biên dịch các hàm Python số học được hỗ trợ thành mã gốc. Đây là vòng lặp từ thử nghiệm của chúng ta với bộ trang trí trình biên dịch của nó. Nó vẫn nói for. Đường dẫn trình thông dịch tốn kém đã thay đổi. Sau khi biên dịch, vòng lặp này mất chưa đến nửa mili giây với các mảng sẵn sàng. Bản thân quá trình biên dịch mất khoảng một giây trong lần chạy này. Chi phí thiết lập đó đã bị loại trừ khỏi các lần đo lặp lại,

4:22 vì vậy một công việc nhỏ, chỉ một lần, xứng đáng được so sánh khác. Hướng dẫn của Numba rất sẵn lòng hiển thị các vòng lặp rõ ràng. Bắt đầu di chuyển ngôn ngữ trước khi tìm thấy đường dẫn nóng là một cách phức tạp để tránh mở một trình biên dịch. Đầu tiên hãy tìm xem điều gì tiêu tốn thời gian. Thời gian chờ mạng, phân tích đối tượng và tính toán số lượng lớn đòi hỏi các cách khắc phục khác nhau. Các hoạt động mảng gốc giúp ích khi phép tính và dữ liệu phù hợp.

4:43 Đo lường kết quả mà người gọi của bạn thực sự cần. Bây giờ đến lối tắt đó. Bạn có một hàm Python và muốn nó chấp nhận một mảng. Một trình bao tên là vectorize nghe có vẻ như là nút hiệu suất còn thiếu. Tài liệu của nó có một câu quan trọng đang chờ đợi.

np.vectorize thực sự thực thi điều gì?

4:56 NumPy nói rằng trình bao vectorize của nó được cung cấp chủ yếu để tiện lợi. Việc triển khai của nó về cơ bản là một vòng lặp for. Trình bao chấp nhận các lô, nhưng vẫn tiếp tục gọi hàm Python của bạn cho các phần tử kế tiếp. Giao diện đã ngắn hơn. Ranh giới thực thi vẫn bận rộn. Xem các giá trị giống hệt nhau trở lại thông qua callback Python. Với các mảng sẵn sàng, phiên bản này mất khoảng tám mươi bảy mili giây. Nó chậm hơn so với việc hiểu của chúng tôi.

5:18 Một trình bao broadcasting hữu ích có thể giữ lại công việc chính xác trên từng phần tử mà bạn đang cố gắng loại bỏ. Kiểm tra việc triển khai đằng sau một giao diện theo lô, giữ dữ liệu tương thích ở dạng hữu ích của nó và bao gồm chuyển đổi trong so sánh. Các vòng lặp gốc, callback và đã biên dịch có thể trông tương tự từ góc nhìn của người gọi. Tên vectorize đã đưa ra một lời hứa mà đồng hồ bấm giờ của bạn chưa bao giờ ký. Giữ nó khi tiện lợi là điều bạn cần. Để có hiệu suất, hãy kiểm tra vòng lặp đang chạy.

Bạn nên thay đổi điều gì trong mã của mình?

5:41 Phán quyết của tôi là SHIP IT cho việc xử lý theo lô một đường dẫn nóng số học đã được đo lường. Giữ các vòng lặp thông thường khi chúng đã hoạt động tốt. Kiểm tra xem công việc diễn ra ở đâu và người gọi tiếp theo cần gì. Đăng ký, nhấn chuông và cho tôi biết trong phần bình luận nếu bạn sẽ đóng dấu nó khác. Và đó là sự khác biệt cho ngày hôm nay. Tôi là Niko từ Axrisi. Hợp nhất một cách có trách nhiệm.

Nguồn

  1. Universal functions (ufunc) basics — NumPy 2.2NumPy
  2. The N-dimensional array — NumPy 2.2NumPy
  3. numpy.vectorize — convenience and performance notesNumPy
  4. CPU/SIMD optimizations — NumPy 2.2NumPy
  5. dis — Disassembler for Python bytecodePython Software Foundation
  6. CPython 3.10.12 list representationCPython contributors
  7. The limits of Python vectorization as a performance techniqueItamar Turner-Trauring / Python⇒Speed
  8. Performance TipsNumba
  9. Basics of CuPyCuPy

Video liên quan