Ví dụ: giả sử bạn đang xây dựng một máy chủ phương tiện để truyền phát chương trình truyền hình hoặc phim qua mạng tới người dùng để họ có thể xem mà không phải tải xuống dữ liệu video trước. Một trong những tính năng chính của hệ thống như vậy là khả năng người dùng tiến hoặc lùi khi phát lại video để họ có thể bỏ qua hoặc lặp lại các phần. Trong chương trình máy khách, tôi có thể thực hiện điều này bằng cách yêu cầu một đoạn dữ liệu từ máy chủ tương ứng với chỉ số thời gian mới do người dùng chọn
def timecode_to_index[video_id, timecode]: ... # Returns the byte offset in the video data def request_chunk[video_id, byte_offset, size]: ... # Returns size bytes of video_id's data from the offset video_id = ... timecode = '01:09:14:28' byte_offset = timecode_to_index[video_id, timecode] size = 20 * 1024 * 1024 video_data = request_chunk[video_id, byte_offset, size]
Bạn sẽ triển khai trình xử lý phía máy chủ như thế nào để nhận yêu cầu
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']1 và trả về đoạn dữ liệu video 20MB tương ứng? . “Biết cách chuyển I/O theo luồng sang
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']2” cho những gì yêu cầu]. Tôi sẽ tập trung vào các bước cuối cùng trong đó đoạn được yêu cầu được trích xuất từ hàng gigabyte dữ liệu video được lưu trong bộ nhớ đệm và sau đó được gửi qua một ổ cắm trở lại máy khách. Đây là những gì việc thực hiện sẽ như thế nào.
socket = ... # socket connection to client video_data = ... # bytes containing data for video_id byte_offset = ... # Requested starting position size = 20 * 1024 * 1024 # Requested chunk size chunk = video_data[byte_offset:byte_offset + size] socket.send[chunk]
Độ trễ và thông lượng của mã này sẽ phụ thuộc vào hai yếu tố. Mất bao nhiêu thời gian để cắt 20 MB video
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']3 từ
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']4 và ổ cắm mất bao nhiêu thời gian để truyền dữ liệu đó đến máy khách. Nếu tôi cho rằng ổ cắm có tốc độ cực nhanh, thì tôi có thể chạy điểm chuẩn vi mô bằng cách sử dụng mô-đun tích hợp sẵn
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']5 để hiểu các đặc điểm hiệu suất của việc cắt các phiên bản
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']6 theo cách này để tạo các khối [xem Mục 11. “Biết cách cắt chuỗi” để làm nền].
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']
>>> 0.004925669 seconds
Mất khoảng 5 mili giây để trích xuất 20 MB phần dữ liệu để truyền tới máy khách. Điều đó có nghĩa là thông lượng tổng thể của máy chủ của tôi bị giới hạn ở mức tối đa theo lý thuyết là 20 MB / 5 mili giây = 7. 3GB / giây, vì đó là tốc độ nhanh nhất mà tôi có thể trích xuất dữ liệu video từ bộ nhớ. Máy chủ của tôi cũng sẽ bị giới hạn ở 1 CPU -giây / 5 mili giây = 200 máy khách yêu cầu các khối mới song song, con số này rất nhỏ so với hàng chục nghìn . Vấn đề là việc cắt một phiên bản
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']6 khiến dữ liệu cơ bản bị sao chép, điều này làm mất CPU thời gian.
Một cách tốt hơn để viết mã này là sử dụng loại
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']9 tích hợp sẵn của Python, hiển thị giao thức bộ đệm hiệu năng cao của CPython cho các chương trình. Giao thức bộ đệm là một C API cấp thấp cho phép thời gian chạy Python và phần mở rộng C truy cập vào bộ đệm dữ liệu cơ bản đằng sau các đối tượng như phiên bản
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']6. Phần hay nhất về các phiên bản
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']9 là việc cắt chúng dẫn đến một phiên bản
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']9 khác mà không cần sao chép dữ liệu cơ bản. Ở đây, tôi tạo một
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']9 bao bọc một phiên bản
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']6 và kiểm tra một phần của nó.
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']8
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']9
Bằng cách kích hoạt các hoạt động không sao chép,
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']9 có thể cung cấp khả năng tăng tốc rất lớn cho mã cần xử lý nhanh lượng bộ nhớ lớn, chẳng hạn như các phần mở rộng C số như NumPy và các chương trình liên kết I/O như thế này. Ở đây, tôi thay thế cách cắt
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']6 đơn giản ở trên bằng cách cắt
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']9 và lặp lại cùng một điểm chuẩn vi mô
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']3
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']4
Kết quả là 250 nano giây. Hiện tại, thông lượng tối đa theo lý thuyết của máy chủ của tôi là 20 MB / 250 nano giây = 164 TB/second. For parallel clients, I can theoretically support up to 1 CPU -giây / 250 nano giây = 4 triệu. Tốt hơn rồi đấy. Điều này có nghĩa là bây giờ chương trình của tôi hoàn toàn bị ràng buộc bởi hiệu suất cơ bản của kết nối ổ cắm với máy khách, chứ không phải bởi CPU ràng buộc.
Bây giờ, hãy tưởng tượng rằng dữ liệu phải truyền theo hướng khác, nơi một số máy khách đang gửi các luồng video trực tiếp đến máy chủ để phát chúng cho những người dùng khác. Để thực hiện việc này, tôi cần lưu trữ dữ liệu video mới nhất từ người dùng trong bộ đệm mà các ứng dụng khách khác có thể đọc từ đó. Đây là cách triển khai đọc 1MB dữ liệu mới từ ứng dụng khách sắp tới.
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']5
Phương thức
>>> 0.004925669 seconds8 sẽ trả về một thể hiện
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']6. Tôi có thể ghép dữ liệu mới với bộ đệm hiện có tại
import timeit def run_test[]: chunk = video_data[byte_offset:byte_offset + size] # Call socket.send[chunk], but ignoring for benchmark result = timeit.timeit[ stmt='run_test[]', globals=globals[], number=100] / 100 print[f'{result:0.9f} seconds']80 hiện tại bằng cách sử dụng các thao tác cắt đơn giản và 'byte'. phương thức tham gia. Để hiểu hiệu suất của điều này, tôi có thể chạy một điểm chuẩn vi mô khác để so sánh hiệu suất của phương pháp này với ví dụ trước đó đã sử dụng
>>> 0.004925669 seconds8