c ++低级I / O详细信息,读取少于一个块

时间:2014-11-11 11:33:41

标签: c++ io fread

我正在处理大文件,我想改进写作和阅读操作。

我需要按顺序方式读取大于1GB的文件(至少在开头)。我想知道,计算要读取的正确字节数(为了读取块大小的倍数)是否有意义,或者由于读取操作已经优化,它是否相同?

我的意思是:正如我所看到的(如果我错了,请纠正我),如果我告诉SO读取8个字节,它将读取一个等于块大小的字节数(大概是4KB)。现在,当我告诉SO读取后续的8个字节时,因为它先前读取了一个完整的块,所以它应该已经在缓存中,对吧?因此,如果我每次读取一个文件(以顺序方式)8个字节或每次4KB,则应该没有区别。是不是?

1 个答案:

答案 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将优化对设备的访问。