我正在处理大文件,我想改进写作和阅读操作。
我需要按顺序方式读取大于1GB的文件(至少在开头)。我想知道,计算要读取的正确字节数(为了读取块大小的倍数)是否有意义,或者由于读取操作已经优化,它是否相同?
我的意思是:正如我所看到的(如果我错了,请纠正我),如果我告诉SO读取8个字节,它将读取一个等于块大小的字节数(大概是4KB)。现在,当我告诉SO读取后续的8个字节时,因为它先前读取了一个完整的块,所以它应该已经在缓存中,对吧?因此,如果我每次读取一个文件(以顺序方式)8个字节或每次4KB,则应该没有区别。是不是?
答案 0 :(得分:3)
你的直觉是正确的,你在用户空间做的事情将在几个层面上进行优化。
在操作系统级别
首先,如果你告诉操作系统一次读取8个字节,它将采用预读机制,并以较大的块发出设备读取请求。这不会发生在每个请求中,因为它只会浪费资源,但操作系统将使用算法来决定是否读取更大的块。
例如,在我的系统上,预读大小为256个扇区,128KB:
➜ ~ [3] at 21:41:06 [Mon 1] $ sudo blockdev --getra /dev/sda
256
因此,操作系统可能会决定读取128KB的块。例如,考虑使用dd顺序读取文件,一次一个扇区:
➜ ~ [3] at 21:43:23 [Mon 1] $ dd if=bigfile of=/dev/null bs=512
并使用iostat检查I / O统计信息:
➜ ~ [3] at 21:44:42 [Mon 1] $ iostat -cxth /dev/sda 1 1000
每秒对I / O统计信息进行1000次采样。在检查iostat输出之前,值得验证dd实际上一次读取512个字节。
mguerri-dell ~ [3] at 21:58:11 [Mon 1] $ strace dd if=bigfile of=/dev/null bs=512 count=32
[...]
read(0, "hb\342J\300\371\346\321i\326v\223Ykd\320\211\345X-\202\245\26/K\250\244O?3\346N"..., 512) = 512
[...]
这确认了dd正以512字节的块读取。 iostat的输出如下:
12/01/2014 09:46:07 PM
avg-cpu: %user %nice %system %iowait %steal %idle
24.50 0.00 10.75 0.00 0.00 64.75
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda
0.00 0.00 418.00 0.00 53504.00 0.00 256.00 0.11 0.26 0.26 0.00 0.25 10.60
12/01/2014 09:46:08 PM
avg-cpu: %user %nice %system %iowait %steal %idle
23.29 0.00 11.14 0.00 0.00 65.57
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda
0.00 0.00 420.00 0.00 53760.00 0.00 256.00 0.11 0.25 0.25 0.00 0.25 10.60
12/01/2014 09:46:09 PM
avg-cpu: %user %nice %system %iowait %steal %idle
24.13 0.00 11.94 0.00 0.00 63.93
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda
0.00 0.00 410.00 0.00 52480.00 0.00 256.00 0.10 0.25 0.25 0.00 0.25 10.30
最重要的字段的含义如下:
r/s Number of read requests per second
rkB/s KBs read per second
avgrq-sz Average size (in 512 bytes sectors) of the requests sent to the device,
considering both read and write operations. Since here I am doing
mostly read operations, we can ignore the contribution of write operations.
您可以检查每秒KB read / Number requests
是否为128KB,即256个扇区
由avgrq-sz
显示。因此,操作系统从设备读取128KB块。
操作系统不会总是采用先行技术。考虑从您的文件中读取几个KB(之前我刷新了页面缓存,确保我没有直接从OS页面缓存中读取):
dd if=bigfile of=/dev/null bs=512 count=8
这是我得到的结果:
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda
0.00 0.00 1.00 0.00 16.00 0.00 32.00 0.00 2.00 2.00 0.00 2.00 0.20
使用iostat仅显示单个进程的请求是不可能的,但您可能只能捕获其活动。在这种情况下,我从文件中读取了4KB,并在那一刻发出了两次读取操作,其中avgrq-sz为16KB。操作系统仍在缓存文件中的某些页面,但它不是以128KB的块读取。
在C ++ stdlib级别
在您的情况下,由于您正在编写C ++代码,因此在操作系统和代码之间有一个额外的层,即C ++ stdlib。
考虑以下示例:
#include <iostream>
#include <fstream>
#define BUFF_SIZE 100
#define RD_SZ 8
using namespace std;
int main() {
char buff[BUFF_SIZE];
fstream f;
f.open("bigfile", ios::in | ios::binary );
f.read(buff, RD_SZ);
cout << f.gcount() << endl;
f.read(buff, RD_SZ);
cout << f.gcount() << endl;
f.close();
}
输出当然是:
➜ mguerri-dell ~ [3] at 22:32:03 [Mon 1] $ g++ io.cpp -o io
➜ mguerri-dell ~ [3] at 22:32:04 [Mon 1] $ ./io
8
8
但strace显示只发出一个读取系统调用,读取8191个字节。
➜ mguerri-dell ~ [3] at 22:33:22 [Mon 1] $ strace ./io
[...]
open("bigfile", O_RDONLY) = 3
read(3, "hb\342J\300\371\346\321i\326v\223Ykd\320\211\345X-\202\245\26/K\250\244O?3\346N"..., 8191) = 8191
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 16), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7faecc811000
write(1, "8\n", 28) = 2
write(1, "8\n", 28) = 2
close(3)
[...]
第一次读取后,C ++ stdlib已经缓存了8KB的数据,第二次调用甚至不需要发出系统调用,因为您的数据在stdlib缓冲区中可用。实际上,如果数据不可用,则会发出读取系统调用,但它可能会触发操作系统页面缓存,从而避免向设备发出请求。
看过这两种缓存机制的工作原理
我建议一次读取4KB,以减少即使只是在C ++文件流上进行一次调用所产生的开销,因为知道操作系统和C ++ stdlib将优化对设备的访问。