前言
這系列文章主要是在研究如何增進檔案寫入之速度,若寫檔之次數、大小均沒一定數量時,這系列文章並不會引起興趣,可跳過。但通常寫入次數增多時 (通常 > 50 MB),便會感覺用 fprintf 速度不夠快,於是有要加速之需求,此系列文章便是在探討是否有其它方法使讀寫檔案方式能增快。
本系列文章只探討數值之讀寫,避開了字串之讀寫,若檔案本身單純只為字串所組成,此系列文章並不適用;若檔案本身單純為數值 (整數及浮點數) 所組成,這系列文章便可適用;若字串、數值為參半,仍可做為相當之參考。
測試時,測試次數 (TIMES) 均設為 二千萬 次,測試方法為調用 rand() 並寫入檔案,也由於為確保每份code拿到之資料均為相同,於此便不再調用 srand(time(NULL)); 謹慎之人可直接再調用 srand(0); 便更能確保每份寫入之檔案資料均為相同。而時間測試方面,由於測試時間多為 1~15 secs,故謹採用 clock() 函式,也降低對系統之負擔。
測試環境
由於測時與硬體設備和 compiler 優化與設定有極大關係,測試結果謹供參考,但相對之效能 ( 如 A 方法為 B 方法耗時之二倍) 應可做為相當之參照。於此附上吾人手邊之開發環境
作業系統:XP
Compiler:VC 2008
CPU :AMD Athlon(tm) II X2 245, 2.91GHz
RAM :2.75GB 實體延伸
1. fprintf (110 MB 13.202 secs)
使用 fprintf 為最直覺、簡易之方式,但由於寫入資料量一大時,速度便拖慢,吾人用手邊電腦寫為二億次隨機亂數,共費時近 150 secs,2分半鐘,於是便開始有人討論如何增快檔案寫入速度之議題。
2. sprintf + fprintf + memcpy (110 MB 14.304 secs)
此法「理論上」似乎應為較快些。這方法主要是將每次要寫入之資料用 sprintf 寫入一 buffer 內,等 buffer 長度(通常設2000或 4000)快滿時,再一次寫入檔案之中。這種做法明顯是利用在 memory 之讀寫速度遠比硬碟之讀寫速度還快之特性,雖「謠言」指出這種方式絕對會較快,但吾人實測結果卻是比單純用 fprintf 還要慢 7 % 左右。初步判定應為在 sprintf 及 memcpy 上花費太多時間 (特別是 sprintf)。若對於此部份之程式碼認為可繼續增加改善,甚至可改得比 fprintf 還快,請不吝指教。
3. fwrite (76MB 2.376 secs)
接下來這二個方法事實上和上述之二個方法並不相同,因輸出之資料並非為文本模式,亦即,無法使用記事本直接開啟,此仍其缺點。但優點為速度快 (為 fprintf 之 7~8 倍),且在讀檔時可進行隨機讀檔。如,假設只要讀取第 1000 筆資料,可直接用 fsetpos 移到第 1000 筆資料直接讀出;但若用 fscanf 方式,則需先讀走前面 999 筆,方可再讀取第 1000 筆資料。
4. fwrite + array (76MB 0.768 secs)
這部份事實上便是 fwrite 之加強版。上述之 fwrite 是一次寫入一個數值進去,但事實上可以把資料先都丟到一個 array 裡面,待 array 滿之後,再一次寫入檔案之中。值得注意的是,array size 設到某個上限後,再設大效能並不會有所明顯增加。以此例,array size 設 100 與設 10000,實際上只增進 1ms (當然也不排除為計時誤差)。
結論是,用 fwrite + array 較快,若此份檔案並非予以他人參閱,直接用 fwrite 方式較為佳。但於程式碼中必須為該檔定義其欄位。
以下即為各方法之核心程式碼
1. fprintf (110 MB 13.202 secs)
t1 = clock(); for(i=0; i!=TIMES; ++i){ fprintf(fp,"%d ", rand()); } t2 = clock();
2. sprintf + fprintf + memcpy (11 MB 14.304 secs)
t1 = clock(); for(i=0; i!=TIMES; ++i){ /* sprintf 將傳回 tmp 字串結果之長度 */ len_tmp = sprintf(tmp, "%d ", rand()); /* buffer 已放不下, 將 buffer 輸出清空, 再將 tmp 丟給 buffer*/ if(len_tmp+len_buf >= BUF_SIZE){ fprintf(fp, "%s", buffer); /* 先將 buffer 輸出 */ memcpy(buffer, tmp, len_tmp); /* 再將 tmp 給 buffer,以 memcpy 取代 strcpy */ len_buf = len_tmp; /* 更新 buffer 長度紀錄 */ } else{ /* buffer 還放得下 */ memcpy(buffer+len_buf, tmp, len_tmp); /* 使用 memcpy 取代 strcat */ len_buf+= len_tmp; /* 更新 buffer 長度紀錄 */ } } /* 結束時,將剩下的 buffer 一次輸出 */ fprintf(fp, "%s", buffer); t2 = clock();
3. fwrite (76MB 2.376 secs)
t1 = clock(); for(i=0; i!=TIMES; ++i){ x = rand(); fwrite(&x, sizeof(int), 1, fp); } t2 = clock();
4. fwrite + array (76MB 0.768 secs)
#define ARRAY_SIZE 10000 int i, counter=0 t1 = clock(); for(i=0; i!=TIMES; ++i){ x[counter++] = rand(); if(counter==ARRAY_SIZE){ fwrite(x, sizeof(int), ARRAY_SIZE, fp); counter=0; } } t2 = clock();
在寫檔時,速度可探討的方法不多;但在讀檔時,可探討的方法有許多。吾人實做過近 10 種讀取數值之方式,最好之方法與最差之方法效能相差2倍!如果檔案單純為數值,檔案很大,且使用的方法為 fscanf,那代表該換一種方法了 - fscanf 讀數值是最慢的方法。
留言列表