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 2 và Python 3.
Chỉ cần kiểm tra các nguồn !? –
@UlrichEckhardt, có đủ hệ thống chơi ở đây mà không phải ai cũng biết bắt đầu từ đâu. –