2015-10-02 12 views
5

Tôi thử nghiệm này:Tại sao Python chia tách chức năng đọc thành nhiều syscalls?

strace python -c "fp = open('/dev/urandom', 'rb'); ans = fp.read(65600); fp.close()" 

Với sản lượng phần sau:

read(3, "\211^\250\202P\32\344\262\373\332\241y\226\340\16\16!<\354\250\221\261\331\242\304\375\24\36\253!\345\311"..., 65536) = 65536 
read(3, "\7\220-\344\365\245\240\346\241>Z\330\266^Gy\320\275\231\30^\266\364\253\256\263\214\310\345\217\221\300"..., 4096) = 4096 

Có hai cuộc gọi để đọc syscall với số khác nhau của byte yêu cầu.

Khi tôi lặp lại tương tự sử dụng lệnh dd,

dd if=/dev/urandom bs=65600 count=1 of=/dev/null 

chỉ là một đọc syscall được kích hoạt bằng cách sử dụng chính xác số lượng byte được yêu cầu.

read(0, "P.i\246!\356o\10A\307\376\2332\365=\262r`\273\"\370\4\n!\364J\316Q1\346\26\317"..., 65600) = 65600 

Tôi đã googled điều này mà không có bất kỳ lời giải thích nào có thể. Điều này có liên quan đến kích thước trang hoặc bất kỳ quản lý bộ nhớ Python nào không?

Tại sao điều này lại xảy ra?

+0

Chỉ cần kiểm tra các nguồn !? –

+1

@UlrichEckhardt, có đủ hệ thống chơi ở đây mà không phải ai cũng biết bắt đầu từ đâu. –

Trả lời

9

Tôi đã thực hiện một số nghiên cứu về chính xác lý do tại sao điều này xảy ra.

Lưu ý: Tôi đã thực hiện các thử nghiệm của mình bằng Python 3.5. Python 2 có một hệ thống I/O khác với cùng một dấu nháy đơn cho một lý do tương tự, nhưng điều này dễ hiểu hơn với hệ thống IO mới trong Python 3.

Khi nó xuất hiện, điều này là do BufferedReader của Python, không phải bất cứ điều gì về các cuộc gọi hệ thống thực tế.

Bạn có thể thử mã này:

fp = open('/dev/urandom', 'rb') 
fp = fp.detach() 
ans = fp.read(65600) 
fp.close() 

Nếu bạn cố gắng strace mã này, bạn sẽ tìm thấy:

read(3, "]\"\34\277V\21\223$l\361\234\16:\306V\323\266M\215\331\3bdU\265C\213\227\225pWV"..., 65600) = 65600 

đối tượng tập tin ban đầu của chúng tôi là một BufferedReader:

>>> open("/dev/urandom", "rb") 
<_io.BufferedReader name='/dev/urandom'> 

Nếu chúng tôi gọi detach() về vấn đề này, thì chúng tôi sẽ xóa phần BufferedReader và chỉ nhận được Fi leIO, đó là những gì nói chuyện với hạt nhân. Ở lớp này, nó sẽ đọc mọi thứ cùng một lúc.

Vì vậy, hành vi mà chúng tôi đang tìm kiếm nằm trong BufferedReader. Chúng ta có thể tìm trong Modules/_io/bufferedio.c trong nguồn Python, cụ thể là hàm _io__Buffered_read_impl. Trong trường hợp của chúng tôi, nơi tệp chưa được đọc từ thời điểm này, chúng tôi gửi đến _bufferedreader_read_generic.

Bây giờ, đây là nơi mà những đứa chúng ta thấy xuất phát từ:

while (remaining > 0) { 
    /* We want to read a whole block at the end into buffer. 
     If we had readv() we could do this in one pass. */ 
    Py_ssize_t r = MINUS_LAST_BLOCK(self, remaining); 
    if (r == 0) 
     break; 
    r = _bufferedreader_raw_read(self, out + written, r); 

Về cơ bản, điều này sẽ đọc càng nhiều đầy đủ "khối" càng tốt trực tiếp vào bộ đệm đầu ra. Kích thước khối được dựa trên tham số truyền cho constructor BufferedReader, trong đó có một mặc định bởi một vài thông số lựa chọn:

 * Binary files are buffered in fixed-size chunks; the size of the buffer 
     is chosen using a heuristic trying to determine the underlying device's 
     "block size" and falling back on `io.DEFAULT_BUFFER_SIZE`. 
     On many systems, the buffer will typically be 4096 or 8192 bytes long. 

Vì vậy, mã này sẽ đọc càng nhiều càng tốt mà không cần phải bắt đầu điền đệm của nó. Điều này sẽ là 65536 byte trong trường hợp này, bởi vì nó là bội số lớn nhất của 4096 byte nhỏ hơn hoặc bằng 65600.Bằng cách này, nó có thể đọc dữ liệu trực tiếp vào đầu ra và tránh làm đầy và đổ bộ đệm riêng của nó, mà sẽ chậm hơn.

Sau khi thực hiện xong, có thể cần đọc thêm một chút. Trong trường hợp của chúng tôi, 65600 - 65536 == 64, vì vậy nó cần phải đọc ít nhất 64 byte nữa. Nhưng nó vẫn đọc 4096! Đưa cái gì? Vâng, chìa khóa ở đây là điểm của một BufferedReader là để giảm thiểu số lượng hạt nhân đọc chúng tôi thực sự phải làm, như mỗi đọc có chi phí đáng kể trong và của chính nó. Vì vậy, nó chỉ đơn giản là đọc một khối để điền vào bộ đệm của nó (vì vậy 4096 byte) và cung cấp cho bạn 64 đầu tiên trong số này.

Hy vọng rằng, điều đó có ý nghĩa trong việc giải thích lý do tại sao nó xảy ra như thế này.

Như một cuộc biểu tình, chúng tôi có thể thử chương trình này:

import _io 
fp = _io.BufferedReader(_io.FileIO("/dev/urandom", "rb"), 30000) 
ans = fp.read(65600) 
fp.close() 

Với điều này, strace cho chúng ta biết:

read(3, "\357\202{u'\364\6R\fr\20\f~\254\372\3705\2\332JF\n\210\341\2s\365]\270\r\306B"..., 60000) = 60000 
read(3, "\266_ \323\346\302}\32\334Yl\ry\215\326\222\363O\303\367\353\340\303\234\0\370Y_\3232\21\36"..., 30000) = 30000 

đủ Chắc chắn, điều này sau cùng một khuôn mẫu: khối như nhiều càng tốt, và rồi thêm một lần nữa.

dd, trong một nhiệm vụ cho hiệu quả cao của các lô sao chép và nhiều dữ liệu, sẽ cố gắng đọc lên đến số tiền lớn hơn nhiều cùng một lúc, đó là lý do tại sao nó chỉ sử dụng một lần đọc. Hãy dùng thử với bộ dữ liệu lớn hơn và tôi nghi ngờ bạn có thể tìm thấy nhiều cuộc gọi để đọc.

TL; DR: BufferedReader đọc càng nhiều khối càng tốt (64 * 4096) và sau đó thêm một khối 4096 để lấp đầy bộ đệm của nó.

EDIT:

Cách đơn giản để thay đổi kích thước bộ đệm, như @fcatho chỉ ra, là để thay đổi lập luận buffering trên open:

open(name[, mode[, buffering]]) 

(...)

Đối số đệm tùy chọn xác định kích thước bộ đệm mong muốn của tập tin: 0 có nghĩa là không bị chặn, 1 có nghĩa là dòng đệm, bất kỳ giá trị dương nào khác có nghĩa là sử dụng bộ đệm (xấp xỉ) kích thước đó (i n byte). Đệm âm có nghĩa là sử dụng mặc định hệ thống, thường là dòng đệm cho các thiết bị tty và được đệm hoàn toàn cho các tệp khác. Nếu bỏ qua, mặc định hệ thống được sử dụng.

Điều này hoạt động trên cả hai Python 2Python 3.

+1

Giải thích tuyệt vời! Tuy nhiên, tôi đã tìm thấy một số tài liệu liên quan về hành vi này trong tài liệu Python https://docs.python.org/2/library/functions.html#open Có một tham số thứ ba trong hàm mở xử lý vùng đệm. – fcatho

+0

@fcatho Tốt bắt. Tôi quên về điều đó. Tôi đã thêm một tham chiếu đến tài liệu. –

Các vấn đề liên quan