簡體   English   中英

C ++低級I / O詳細信息,讀取少於一個塊

[英]c++ low level I/O details, reading less than one block

我正在處理大文件,並且我想改善讀寫操作。

我需要以順序方式(至少在開始時)讀取大於1GB的文件。 我想知道,計算正確的字節讀取量(以便讀取塊大小的倍數)是否有意義,或者由於讀取操作已優化,這是同一回事嗎?

我的意思是:如我所見(如果我錯了,請糾正我),如果我告訴SO讀取8個字節,它將讀取等於塊大小(假定為4KB)的字節數。 現在,當我告訴SO讀取后續的8個字節時,由於它以前讀取了一個完整的塊,因此它應該已經在緩存中了,對嗎? 因此,如果我按順序讀取文件(每次)8個字節或每次讀取4KB,應該沒有什么區別。 這樣對嗎?

您的直覺是正確的,您在用戶空間中所做的工作將在多個級別上得到優化。

在操作系統級別

首先,如果您告訴操作系統一次讀取8個字節,它將采用預讀機制,並將向設備發出較大塊的讀取請求。 每個請求都不會發生這種情況,因為這只會浪費資源,但是OS將采用算法來決定是否讀取更大的塊。

例如,在我的系統上,預讀大小為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數據,第二次調用甚至不需要發出syscall,因為您的數據在stdlib緩沖區中可用。 實際上,如果數據不可用,則將發出讀取的syscall,但可能會打入OS頁面緩存,從而避免了對設備的請求。

了解了這兩種緩存機制的工作原理

我建議一次讀取4KB,以減少即使是對C ++文件流的單次調用所帶來的開銷,但要知道OS和C ++ stdlib將優化對設備的訪問。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM