聲明:如需引用或者摘抄本博文源碼或者其文章的,請在顯著處注明,來源於本博文/作者,以示尊重勞動成果,助力開源精神。也歡迎大家一起探討,交流,以共同進步~ 0.0
題目:
1. 分別利用文件的系統調用read、write和文件的庫函數fread、fwrite實現文件復制功能,比較在每次讀取一個字節和1024字節時兩個程序的執行效率,並分析原因。
分析:
預先准備好一份已經存儲數據的普通文件(data.txt)
設置兩對照組:
對照組1(系統調用組):在執行系統調用實現文件拷貝功能時,分別對讀取一個字節和1024個字節記時,讀取的執行時間若分別記為t1和t1024,然后通過比較1024×t1與t1024二者的時間來比較執行效率(時間效率為主)。
對照組2(庫函數組):在執行庫函數實現文件拷貝功能時,分別對文件的系統調用與文件的庫函數的執行時間形成對照,比較二者執行效率。
即 本次實驗需要記錄4個變量:
1)系統調用的讀取1字節的時間:Syst1
2)系統調用的讀取1024字節的時間:Syst1024
3)庫函數的讀取1字節的時間:Lib1
4)庫函數的讀取1024字節:Lib1024
猜想:
系統調用的性能較庫函數高,所以,性能上:Syst > Lib;
讀取字節數小的時間效率將高於讀取字節數大的時間效率,所以,時間上:T(1) < T(1024)
則,猜想結論: Lib1024 > Syst1024 > Lib1 > Syst1 (耗時)
開始實施實驗,驗證猜想 Action!
實驗
【系統調用組】 gcc main-system.c -o main-system.out time ./main-system.out /* @author:johnny @description:系統調用 */ #include<stdio.h> #include<stdlib.h>//exit(num) #include<sys/types.h> #include<sys/stat.h> #include<string.h> #include<fcntl.h> #include<time.h> #include<unistd.h>//dependency:close/write/open method #define LENGTH_1 1 #define LENGTH_1024 1024 //統計系統調用的讀取1字節的時間效率,並實現文件復制功能 double read1Byte(){ double startTime; double endTime; double Syst1;//系統調用的讀取1字節的時間:Syst1 int len,fread,fwrite; off_t off;//記錄文件游標偏移位置 char readStr[LENGTH_1]; fread = open("data.txt",O_RDWR | O_CREAT,S_IRUSR | S_IWUSR);//打開數據源文件 fwrite = creat("copy-1-data.txt", 0700);//創建拷貝文件 if(fwrite == -1){ perror("\n[read1Byte] Fail to create file 'copy-1-data.txt'.\n\n"); exit(1); } else { printf("\n[read1Byte] Create file 'copy-1-data.txt' OK.\n\n"); } if(fwrite){//write data to file. off = lseek(fwrite,0,SEEK_SET); //獲取並重置游標位置為文件開頭處,SEEK_CUR當前讀寫位置后增加offset[0]個位移量 if(off == -1){ perror("\n[read1Byte] Fail to lseek.\n\n"); exit(1); } startTime = clock();//從打開文件后,讀取文件數據前開始記錄時間 len = read(fread, readStr, LENGTH_1);//從data.txt讀取數據 endTime = clock(); while(len){//判斷len是否為0,如果為0,則說明讀取到了文件尾,則停止 printf("%s", readStr); write(fwrite, readStr,strlen(readStr)); //一邊讀取源數據文件,一邊寫入新文件數據 len = read(fread, readStr, LENGTH_1); } } close(fread); close(fwrite); Syst1 = (double)(endTime - startTime) / 1000; //(單位:毫秒) printf("\n[read1Byte] 系統調用的讀取1字節的時間[Syst1]:%f ms\n\n", Syst1); return Syst1; } //統計系統調用的讀取1024字節的時間效率,並實現文件復制功能 double read1024Bytes(){ double startTime; double endTime; double Syst1024;//系統調用的讀取1024字節的時間:Syst1024 int len,fread,fwrite; off_t off;//記錄文件游標偏移位置 char readStr[LENGTH_1024]; fread = open("data.txt",O_RDWR | O_CREAT,S_IRUSR | S_IWUSR);//打開數據源文件 fwrite = creat("copy-1024-data.txt", 0700);//創建拷貝文件 if(fwrite == -1){ perror("\n[read1024Byte] Fail to create file 'copy-1024-data.txt'.\n\n"); exit(1); } else { printf("\n[read1024Bytes] Create file 'copy-1024-data.txt' OK.\n\n"); } if(fwrite){//write data to file. off = lseek(fwrite,0,SEEK_SET); //獲取並重置游標位置為文件開頭處,SEEK_CUR當前讀寫位置后增加offset[0]個位移量 if(off == -1){ perror("\n[read1024Bytes] Fail to lseek.\n"); exit(1); } startTime = clock();//從打開文件后,讀取文件數據前開始記錄時間 len = read(fread, readStr, LENGTH_1024);//從data.txt讀取數據 endTime = clock(); while(len){//判斷len是否為0,如果為0,則說明讀取到了文件尾,則停止 printf("%s", readStr); write(fwrite, readStr,strlen(readStr)); //一邊讀取源數據文件,一邊寫入新文件數據 len = read(fread, readStr, LENGTH_1024); } } close(fread); close(fwrite); Syst1024 = (double)(endTime - startTime) / 1000; //(單位:毫秒) printf("\n[read1024Bytes] 系統調用的讀取1024字節的時間[Syst1024]:%f ms\n\n", Syst1024); return Syst1024; } int main(){ read1Byte();//統計系統調用的讀取1字節的時間效率,並實現文件復制功能 read1024Bytes();//統計系統調用的讀取1024字節的時間效率,並實現文件復制功能 return 0; }
運行效果:
【庫函數調用組】 gcc main-lib.c -o main-lib.out time ./main-lib.out /* @author:johnny @description:庫函數 */ #include<stdio.h> #include<stdlib.h>//exit(num) #include<sys/types.h> #include<sys/stat.h> #include<string.h> #include<fcntl.h> #include<time.h> #include<unistd.h>//dependency:close/write/open method #define LENGTH_1 1 #define LENGTH_1024 1024 //統計庫函數的讀取1字節的時間效率,並實現文件復制功能 double read1Byte(){ double startTime; double endTime; double Lib1;//庫函數的讀取1字節的時間:Lib1 FILE * readStream;//讀取數據源文件data.txt的文件流 FILE * writeStream;//拷貝數據源文件data.txt的文件流 readStream = fopen("data.txt","r+");//只讀方式打開可讀寫的且必須已經存在的文件 writeStream = fopen("copy-1-data.txt","w+");//只寫方式打開文件。若文件存在則文件長度清為零,即該文件內容會消失。若文件不存在則建立該文件。 if(readStream == NULL){ fprintf(stderr,"\n[read1Byte] readStream can not open file 'data.txt'.\n\n"); return 0; } if(writeStream == NULL){ fprintf(stderr,"\n[read1Byte] writeStream can not create/open file 'copy-1-data.txt'.\n\n"); return 0; } else { printf("\n[read1024Bytes] writeStream create/open file 'copy-1-data.txt' OK.\n\n"); } size_t len;//記錄每次實際讀取到的緩沖數據長度,若為0,則可能是讀取錯誤或者已讀到文件尾。 int off;//記錄文件游標當前偏移位置 char buffer[LENGTH_1]; //write data to file. off = fseek(writeStream,0,SEEK_SET);//獲取並重置游標位置為文件開頭處,SEEK_CUR當前讀寫位置后增加offset[0]個位移量 if(off == -1){ perror("\n[read1Byte] Fail to fseek.\n\n"); exit(1); } startTime = clock();//從打開文件后,讀取文件數據前開始記錄時間 len = fread(buffer, LENGTH_1, LENGTH_1, readStream);//從data.txt讀取數據,len為返回實際寫入的nmemb數目 endTime = clock(); while(len){//判斷len是否為0,如果為0,則說明讀取到了文件尾,則停止 printf("%s", buffer); fwrite(fwrite, LENGTH_1, len, writeStream); //一邊讀取源數據文件,一邊寫入新文件數據 len = fread(buffer, LENGTH_1, LENGTH_1, readStream); } fclose(readStream); fclose(writeStream); Lib1 = (double)(endTime - startTime); printf("\n[read1Byte] 庫函數的讀取1字節的時間[Lib1]:%f ms\n\n", Lib1); return Lib1; } //統計庫函數的讀取1024字節的時間效率,並實現文件復制功能 double read1024Bytes(){ double startTime; double endTime; double Lib1024;//庫函數的讀取1024字節的時間:Lib1024 FILE * readStream;//讀取數據源文件data.txt的文件流 FILE * writeStream;//拷貝數據源文件data.txt的文件流 readStream = fopen("data.txt","r+");//只讀方式打開可讀寫的且必須已經存在的文件 writeStream = fopen("copy-1024-data.txt","w+");//只寫方式打開文件。若文件存在則文件長度清為零,即該文件內容會消失。若文件不存在則建立該文件。 if(readStream == NULL){ fprintf(stderr,"\n[read1024Bytes] readStream can not open file 'data.txt'.\n\n"); return 0; } if(writeStream == NULL){ fprintf(stderr,"\n[read1024Bytes] writeStream can not create/open file 'copy-1024-data.txt'.\n\n"); return 0; } else { printf("\n[read1024Bytes] writeStream create/open file 'copy-1024-data.txt' OK.\n\n"); } size_t len;//記錄每次實際讀取到的緩沖數據長度,若為0,則可能是讀取錯誤或者已讀到文件尾。 int off;//記錄文件游標當前偏移位置 char buffer[LENGTH_1024]; //write data to file. off = fseek(writeStream, 0, SEEK_SET);//獲取並重置游標位置為文件開頭處,SEEK_CUR當前讀寫位置后增加offset[0]個位移量 if(off == -1){ perror("\n[read1024Bytes] Fail to fseek.\n\n"); exit(1); } startTime = clock();//從打開文件后,讀取文件數據前開始記錄時間 len = fread(buffer, LENGTH_1, LENGTH_1024, readStream);//從data.txt讀取數據,len為返回實際寫入的nmemb數目 endTime = clock(); while(len){//判斷len是否為0,如果為0,則說明讀取到了文件尾,則停止 printf("%s", buffer); fwrite(fwrite, LENGTH_1, len, writeStream); //一邊讀取源數據文件,一邊寫入新文件數據 len = fread(buffer, LENGTH_1, LENGTH_1024, readStream); } fclose(readStream); fclose(writeStream); Lib1024 = (double)(endTime - startTime); printf("\n[read1024Bytes] 庫函數的讀取1字節的時間[Lib1024]:%f ms\n\n", Lib1024); return Lib1024; } int main(){ read1Byte();//統計庫函數的讀取1字節的時間效率,並實現文件復制功能 read1024Bytes();//統計庫函數的讀取1024字節的時間效率,並實現文件復制功能 return 0; }
運行效果:
結果:
變量 | 讀取字節數(byte) | API | 消耗時間(ms) |
Syst1 |
1 |
系統調用 | 0.061 |
Syst1024 | 1024 | 系統調用 | 0.002 |
Lib1 | 1 | 庫函數 | 46 |
Lib024 | 1024 | 庫函數 | 14 |
結論:
實驗后,回顧最初的猜想:
系統調用的性能較庫函數高,所以,性能上:Syst > Lib;
讀取字節數小的時間效率將高於讀取字節數大的時間效率,所以,時間上: T(1) < T(1024)
那么,猜想結論:
Lib1024 > Syst1024 > Lib1 > Syst1 (耗時)
然而,實際實驗結論是:
Lib1 > Lib1024 >> Syst1 > Syst1024(耗時)
發現,猜想僅僅正確一半。
字節數小的時間效率反而遠遠高於讀取字節數大的時間效率,且系統調用的效率遠遠高於之前對系統調用與庫函數調用的想象。
實驗數據顯示,分別比較讀取1字節和讀取1024字節在系統調用組,庫函數組的時間效率:
Lib1 ~= 754.098 * Syst1 (時間效率比較:754.098倍)
Lib1024 = 7000 * Syst1024 (時間效率比較:7000倍)
對於系統調用與庫函數之間的效率差距,在查閱相關文獻后,得知大致原因如下:
要實現文件拷貝,必須嵌入內核,從磁盤讀取文件內容,然后存儲到另一個文件。
實現文件拷貝最通常的做法是:
讀取文件用系統調用read()函數,讀取到一定長度的連續的用戶層緩沖區,然后使用write()函數將緩沖區內容寫入文件。也可以用標准庫函數fread()和fwrite(),但這兩個函數最終還是通過系統調用read()和write()實現拷貝的,因此可以歸為一類(不過效率肯定沒有直接進行系統調用的高)。一個更高級的做法是使用虛擬存儲映射技術進行,這種方法將源文件以共享方式映射到虛擬存儲器中,目的文件也以共享方式映射到虛擬地址空間中,然后使用memcpy高效地將源文件內容復制到目的文件中。
實驗數據顯示,分別比較系統調用組,庫函數組在讀取1字節和讀取1024字節的時間效率:
Syst1 = 30.5 * Syst1024 (時間效率比較:30.5倍)
Lib1 ~= 3.286 * Lib1024 (時間效率比較:約3.3倍)
對於單(低)字節與多(更多)字節之間的時間效率差距,查閱相關資料無效后,個人揣測原因如下:(仍存在疑問)
本來系統讀取數據的緩存區是較大的,可以讀取多個字節數據,這樣能夠解釋為何多(更多)字節數據的讀取高的原因。又由於讀取單(低)字節,需要系統額外地設置和讀取緩存區,導致比讀取更多字節更高的時間開銷,所以單(低)字節的效率便低於多字節的時間效率了。
參考文獻:
[原創]