前言

這系列文章主要是在研究如何增進檔案寫入之速度,若寫檔之次數、大小均沒一定數量時,這系列文章並不會引起興趣,可跳過。但通常讀取次數增多時 (通常 > 50 MB),便會感覺用 fscanf 速度不夠快,於是有要加速之需求,此系列文章便是在探討是否有其它方法使讀寫檔案方式能增快。

本系列文章只探討數值之讀寫,避開了字串之讀寫,若檔案本身單純只為字串所組成,此系列文章並不適用;若檔案本身單純為數值 (整數及浮點數) 所組成,這系列文章便可適用;若字串、數值為參半,仍可做為相當之參考。

測試時,測試次數 (TIMES) 均設為 二千萬 次,測試方法為調用 rand() 並寫入檔案,也由於為確保每份code拿到之資料均為相同,於此便不再調用 srand(time(NULL)); 謹慎之人可直接再調用 srand(0); 便更能確保每份寫入之檔案資料均為相同。而時間測試方面,由於執行之時間均有一定耗時,故謹採用 clock() 函式,也降低對系統之負擔

 


 

 測試環境

由於測時與硬體設備和 compiler 優化與設定有極大關係,測試結果謹供參考,但相對之效能 ( 如 A 方法為 B 方法耗時之二倍) 應可做為相當之參照。於此附上吾人手邊之開發環境

作業系統:XP
Compiler:VC 2008
CPU      :AMD Athlon(tm) II X2 245, 2.91GHz
RAM      :2.75GB 實體延伸

 


 測試結果

 請看圖說故事。

EdisonX-068.png   

 

 


 

 

產生測試資料

為避免由於資料來源之不一,故先寫一段程式產生統一之測試資料,測試資料乃調用rand 進行,同時生成檔案分整數與浮點數部份,生成之數字共 200M 個,檔案大小均超過 1GB,耗時約 20 分鐘(floating 計算最為耗時,未經優化),原始碼如下

  
#include <stdio.h>
#include <stdlib.h>

#define N 200000000
#define NEW_LINE_N 20
#define FILE_INT "data_integer.txt"
#define FILE_FLOATING "data_floating.txt"
int main()
{
    const int new_line_n = NEW_LINE_N-1;
    int i=0;
    FILE *fp = fopen(FILE_INT, "w");        

    srand(0);
    for(i=0; i!=N; ++i) {
       fprintf(fp,"%d ", rand());
       if(i%NEW_LINE_N==new_line_n) fputc('\n', fp);
    }
    fclose(fp);

    srand(0);
    fp=fopen(FILE_FLOATING, "w");    
    for(i=0; i!=N; ++i){
       fprintf(fp, "%lf ", rand() + rand()/(double)RAND_MAX);
       if(i%NEW_LINE_N==new_line_n) fputc('\n', fp);
    }
    fclose(fp);
    return 0;
}

 


 

 1. fscanf

這是最常見、最方便的方法,但卻是最慢的方法。

fp=fopen(FILE_INT, "r");
while(fscanf(fp, "%d",&integer)!=EOF) 
    /* printf("%d\n", integer); */;
fclose(fp);

fp=fopen(FILE_FLOATING, "r");
while(fscanf(fp, "%lf", &floating)!=EOF)
    /* printf("%lf\n", flating); */;
fclose(fp);
 

 

2. fgets + strtok + atoi(atof)

這是最基本、簡單的加速法,約可節省 fscanf 23~28% 之時間

fp = fopen(FILE_INT,"r");
while(fgets(buffer, BUF_SIZE, fp)!=NULL) {
    ptr=strtok(buffer, " "), integer = atoi(ptr);
    while((ptr=strtok(NULL, " \n"))!=NULL ){
       integer=atoi(ptr);
       /* printf("%d\n", integer); */
    }
}
fclose(fp);

fp = fopen(FILE_FLOATING,"r");
while(fgets(buffer, BUF_SIZE, fp)!=NULL) {
    ptr=strtok(buffer, " \n"), floating = atof(ptr);
    while((ptr=strtok(NULL, " \n"))!=NULL ){
       floating=atof(ptr);
       /* printf("%d\n", floating); */
    }
}
fclose(fp);

 


 

 

  3. fgets + strchr + atoi(atof)

這方法與 strtok 相似,但處理起來較為麻煩點,但處理之時間比 strtok 快!約可節省 fscanf 33%~48% 之時間。

fp = fopen(FILE_INT,"r");
while(fgets(buffer, BUF_SIZE, fp)!=NULL) {
    ptr = buffer; integer = atoi(ptr);
    while( (ptr = strchr(ptr, ' '))!=NULL && *(ptr+1)!='\n') {
       integer = atoi(ptr++);
       /* printf("%d", integer); */
    }
}
fclose(fp);

fp = fopen(FILE_FLOATING,"r");
while(fgets(buffer, BUF_SIZE, fp)!=NULL) {
    ptr = buffer, floating = atof(ptr);
    printf("%lf ", floating);
    while( (ptr=strchr(ptr, ' '))!=NULL && *(ptr+1)!='\n') {
       floating = atof(ptr++);
       /* printf("%lf ", floating); */
    }
}
fclose(fp);

  


 

 

 4. fgetc

這方法效果也很好,但若是浮點數在實做上有一定難度,約可節省 fscanf 33%~45% 之時間。首先要有一個 flag - is_integer 判斷目前之數字是否為整數部份;is_integer = 1代表為整數部份;is_integer=0 代表該數字為小數部份。步驟大致如下

(0) 初始化設定:is_integer = 1, integer=0 (整數部份), floating=0 (小數部吩), power=0.1 (乘幕部份)
說明:一開始讀到數字的話都是整數部份(is_integer=1),直到讀到小數點才切到小數部份(is_integer=0)
(1) 讀到字元為 '\n' : 直接跳過不處理(continue)
說明:若不處理的話,每次換行會誤判一次 0
(2) 若讀到的是字元 且 is_integer=1,integer = integer * 10 + (ch & 0x0f); 
說明:抓取整數部份

(3) 若讀到的是字元 且 is_integer=0,floating=(ch & 0x0f)*power, power*=0.1;
說明:抓取小數部份
(4) 若讀到的是小數點 ".",則將 is_integer 設成0
說明:從「整數部份」切換至「小數部份」
(5) 若讀到的分隔符號 (此處為空白鍵 " "),則視為這段資料結束,將 floating+=integer
說明:進行 floating 輸出,同時再次進行初始化。

 

fp = fopen(FILE_INT, "r");
integer=0;
while( (ch=fgetc(fp)) !=EOF){
    if(ch=='\n') integer=0;
    else if(isdigit(ch)) integer = integer*10 + (ch & 0x0f);
    else if(ch==' ') /*printf("%d ", integer),*/ integer=0;
    else NULL;       
}
fclose(fp);

fp = fopen(FILE_FLOATING, "r");
integer=0, floating=0, is_integer=1, power=0.1;
while( (ch=fgetc(fp)) !=EOF){
    if(ch=='\n') continue;
    else if(isdigit(ch) && is_integer) {
       integer = integer*10 + (ch & 0x0f);
    }
    else if(isdigit(ch) && !is_integer) {
       floating+= (ch & 0x0f)*power, power*=0.1;
    }
    else if(ch=='.') {
       is_integer=0;
    }
    else if(ch==' ') {
       floating+=integer;
       /*printf("%lf ",floating); */
       integer=0, floating=0, is_integer=1, power=0.1;
    }
    else NULL;
}
fclose(fp);

 


 

 最後聲明,如果真的是大檔案要多次進行讀取,建議先轉成 bin file, 以便日後做 seek/read 動作,速度會快很多!

 

edisonx 發表在 痞客邦 PIXNET 留言(0) 人氣()