Linux編程實踐——檔案I/O緩衝區測試及cat簡單實現

來源:互聯網
上載者:User

cat命令簡單實現

cat工具實現起來比較簡單,下面代碼採用基本的open、read、printf、close函數,基本可以實現cat命令的功能:

 1 #include <stdio.h>
2 #include <unistd.h>
3 #include <fcntl.h>
4
5 #define READSIZE 4096
6
7 int main(int ac, char* av[]){
8 int rfd=-1,rlen=-1,ret=-1; //被檔案描述符、讀取內容長度、程式傳回值
9 char rbuf[READSIZE]; //讀取內容緩衝
10 memset(rbuf,0,READSIZE);
11 if(ac == 2){
12 if((rfd= open(av[1],O_RDONLY))== -1)  
13 {
14 perror("ccat:");
15 return -1;
16 }
17 while((rlen = read(rfd,rbuf,READSIZE)) > 0){
18 printf("%s",rbuf);
19 memset(rbuf,0,READSIZE);
20 }
21 ret = close(rfd);
22 if(ret == -1)
23 perror("ccat:");
24 }
25 return ret;
26 }

值得注意的是第5行的定義READSIZE定義為4096,這裡是參考《Unix\Linux編程實踐教程》一書中2.7節對於‘提高檔案I/O效率的方法:使用緩衝’的討論。

檔案操作兩大效率開銷

傳輸資料量和模式切換。而每次傳輸資料量的緩衝區大小,直接影響到模式切換的次數。

什麼是模式切換?解釋,再叨叨。

圖中最上方的read、write所在白色空間為“使用者空間”,大框底部的灰色空間為“核心空間”,訪問任何硬體裝置包括磁碟都要經過“核心空間”這一層,但是在“使用者模式”下只能訪問“使用者空間”,要訪問“核心空間”需要從“使用者模式”切換到“管理員模式”,書中還對這個過程做了個形象的比喻:

肯特要到電話亭從“使用者模式”切換到“管理員模式”才能變成超人,完成任務在切回記者身份,賺錢糊口(畢竟拯救地球也是義務的,填不了肚子),如果任務多了,就算是超人,找電話亭切來切去,也是很低效的。

緩衝區的設定原理,像是cpu與硬碟之間的記憶體的作用,也是這麼個原理,都知道記憶體過大是浪費,太小則低效,那麼是否存在一個剛剛好的量?我們姑且把這個量稱為臨界點,Linux核心對於檔案I/O互動緩衝區是否做了控制?

本書對於緩衝區大小測試的量是4096,測試的方法是讀一個5M大小的檔案將內容寫到另一個檔案裡,測試結果見下表,似乎緩衝區大小設到4096以上測試結果都沒有變化,難道這個臨界值真的存在?

 

緩衝大小                    執行時間/s
      1                            50.29
    4                              12.81
 16                               3.28
 32                               0.96
    ...                                ...
 4096        0.18
 8192        0.18
 16384                          0.18

改造cat

書中例子是複製操作,整個過程是核心從磁碟提取資料,傳給使用者空間,使用者將資料寫入核心空間,再寫入磁碟。資料的來處和去處都是外設磁碟,一次讀寫經過兩次“使用者模式”和“管理員模式”的切換。

對於權威,我們不迷信,改造上面的ccat.c代碼,測試緩衝區大小設定對於模式切換時效的影響,不同的是資料的來處是磁碟,但去處是顯示裝置,也經過了兩次模式切換。

改造思路是——我們只需要在open和close的地方添加兩個時間記錄點,最後作差就是整個過程(open、read、print、close)的時間,不過,就書中的結論列表來看,取時間起碼要取到毫秒的精度,我們可以使用gettimeofday(...)函數,它可以取到時間的微秒(10^6)層級,代碼如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>

#define READSIZE 16384 //一次性讀取檔案的大小

void showtime(char*,struct timeval);//列印時間
long filesize(char*); //取檔案大小(bytes)
int main(int ac, char* av[]){
int rfd=-1,rlen=-1,ret=-1,i=1; //檔案描述符、讀取內容長度、程式傳回值
int nfblocks;//檔案分割塊數
char rbuf[READSIZE]; //讀取內容緩衝
struct timeval times[2]; //open和close時間
long int fsize; //檔案大小
float sub; //時間差
memset(rbuf,0,READSIZE);
if(ac == 2){
if((fsize=filesize(av[1]))==0)
{
printf("file size unknown\n");
return ret;
}
//計算檔案分割數(讀取次數)
nfblocks = fsize/READSIZE;
if(fsize%READSIZE > 0) nfblocks++;
if((rfd= open(av[1],O_RDONLY))== -1)
{
perror("ccat:");
return -1;
}
gettimeofday(&times[0],NULL); //open時間
while((rlen = read(rfd,rbuf,READSIZE)) > 0){
printf("%s",rbuf);
memset(rbuf,0,READSIZE);
i++;
}
ret = close(rfd); //close時間
gettimeofday(&times[1],NULL);
if(ret == -1)
perror("ccat:");
//計算時間差
sub = (times[1].tv_sec-times[0].tv_sec)
+(times[1].tv_usec-times[0].tv_usec)/1000000.0;
printf(">>>file size:%ld,read size:%d,blocks num:%d\n",fsize,READSIZE,nfblocks);
printf(">>>open at:%ld(s):%ld(us)\n",(long)times[0].tv_sec,(long)times[0].tv_usec);
printf(">>>close at:%ld(s):%ld(us)\n",(long)times[1].tv_sec,(long)times[1].tv_usec);
printf(">>>sub=%f(s)\n",sub);

}
return ret;
}



long filesize(char* filename)
{
struct stat pstat;
if(stat(filename,&pstat) < 0)
return 0;
return (long)pstat.st_size;
}

 

檔案大小是5292841bytes,下面是運行結果(read size為讀取大小,blocks num為資料區塊數量,sub是整個過程所用的時間,是我們關注的結果):

當READSIZE=256時:

>>>file size:5292841,read size:256,blocks num:20676
>>>open at:1320222334(s):183692(us)
>>>close at:1320222347(s):258245(us)
>>>sub=13.074553(s)

當READSIZE=512時:

>>>file size:5292841,read size:512,blocks num:10338
>>>open at:1320222371(s):143244(us)
>>>close at:1320222383(s):451112(us)
>>>sub=12.307868(s)

當READSIZE=1024時:

>>>file size:5292841,read size:1024,blocks num:5169
>>>open at:1320222411(s):239208(us)
>>>close at:1320222422(s):288572(us)
>>>sub=11.049364(s)

當READSIZE=2048時:

>>>file size:5292841,read size:2048,blocks num:2585
>>>open at:1320222448(s):259733(us)
>>>close at:1320222459(s):147523(us)
>>>sub=10.887790(s)

當READSIZE=4096時:

>>>file size:5292841,read size:4096,blocks num:1293
>>>open at:1320222490(s):639213(us)
>>>close at:1320222501(s):419869(us)
>>>sub=10.780656(s)

當READSIZE=8192時:

>>>file size:5292841,read size:8192,blocks num:647
>>>open at:1320222535(s):491225(us)
>>>close at:1320222545(s):707764(us)
>>>sub=10.216539(s)

當READSIZE=16384時:

>>>file size:5292841,read size:16384,blocks num:324
>>>open at:1320222638(s):119740(us)
>>>close at:1320222648(s):66890(us)
>>>sub=9.947150(s)

結果分析

運行結果計算效率呈遞增趨勢,4096似乎也不是不可再最佳化的瓶頸,看這個趨勢如果繼續不吝惜記憶體,繼續增大緩衝區大小,仍可提高存取的時間效率。這裡要說明的是這個運行結果是一開機時的運行結果,於是我繼續做實驗,開啟一些軟體,cpu被佔用到40%左右,再運行,結果值sub幾乎是上面的兩倍,如果CPU佔用率有波動,那麼測試結果也直接受其影響,很難得到一個穩定的結果,很明顯的是Linux核心未對緩衝區大小做什麼控制。

那麼,書中的測試結果就比較奇怪了,緩衝區大小4096後存取效率不變化(保持在0.18s),如此穩定的測試結果,CPU使用率應該比較平穩,如果這樣,也應該與我的測試結果一樣呈遞增趨勢才對。

綜上,臨界量是不存在的,也就是說在CPU和記憶體足夠富裕的情況下,每次內容交換量越大,那麼檔案I/O時效越高,而瓶頸值是存在的,緩衝區越大,檔案讀寫時間效率越高,但也意味著對記憶體的需求也就越大。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.