簡體   English   中英

更改緩沖區大小以復制 C 中的文件

[英]Changing buffer size to copy file in C

我創建了一個 function 來創建一個文件的副本:讀取 --> 緩沖區 --> 寫入。 我試圖多次增加緩沖區大小,看看是否會影響復制文件所需的時間(大約 50Mb)

# include <assert.h>
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <sys/wait.h>
# include <string.h>
# include <fcntl.h>
# include <time.h>
// Copy the file referred to by in to out 
void copy (int in, int out, char *buffer, long long taille) {
  int t;

  while ((t = read(in, &buffer, sizeof taille))> 0)
    write (out, &buffer, t);


  if (t < 0)
    perror("read");
}

int main(){
  
  clock_t timing;  //to time 
  int buffer_size = 1;
  char * buffer = NULL;
  
  // allocating memory for the buffer
  buffer = malloc(sizeof(char)*buffer_size);
  // test mémoire
  if (!buffer) {
    perror("malloc ini");
    exit(1);
  }

  // temporary buffer to be able to increase the siwe of the buffer 
  char * temp_buffer = NULL;

  // opening the files
  int fichier1 = open("grosfichier",O_RDONLY);
  int fichier2 = open("grosfichier_copy", O_WRONLY|O_CREAT);
  
  for (int i=0; buffer_size <= 1048576; i++){
    
    temp_buffer = realloc(buffer, buffer_size * sizeof(char));
    if(!temp_buffer) {
      perror("malloc temp_buffer");
      exit(1);
    }
    
    buffer = temp_buffer;

    timing = clock();
    copy(fichier1,fichier2, buffer, buffer_size); //recopie l'entree std dans la sortie std
    timing = clock() - timing;

    printf("%d, buffer size = %d, time : %ld\n", i, buffer_size, timing);
    remove("grosfichier_copie");

    buffer_size *= 2;
  }
  // free(temp_buffer);
  free(buffer);
  close(fichier1);
  close(fichier2);

  return 0;
}

代碼運行並復制文件,但時間問題無法正常工作

0, buffer size = 1, time : 6298363
1, buffer size = 2, time : 1
2, buffer size = 4, time : 1
3, buffer size = 8, time : 1
4, buffer size = 16, time : 1
5, buffer size = 32, time : 1
6, buffer size = 64, time : 1
7, buffer size = 128, time : 1
8, buffer size = 256, time : 1
9, buffer size = 512, time : 1
10, buffer size = 1024, time : 1
11, buffer size = 2048, time : 1
12, buffer size = 4096, time : 1
13, buffer size = 8192, time : 1
14, buffer size = 16384, time : 1
15, buffer size = 32768, time : 0
16, buffer size = 65536, time : 1
17, buffer size = 131072, time : 4
18, buffer size = 262144, time : 1
19, buffer size = 524288, time : 2
20, buffer size = 1048576, time : 2
[Finished in 6.5s]
  1. 為什么它在第一次運行后似乎沒有復制? (根據時間?)
  2. 我是否適當地使用免費? (我試着在循環中移動它,但它沒有運行)
  3. 我是否將緩沖區適當地傳遞給 function 副本?

謝謝!

EDIT1:感謝您的所有評論,我已經糾正了與循環內打開和關閉文件相關的主要缺陷,適當地使用了緩沖區。 以及建議的變量類型:我得到的結果更合乎邏輯:

0, buffer size = 1, time : 8069679
1, buffer size = 2, time : 4082421
2, buffer size = 4, time : 2041673
3, buffer size = 8, time : 1020645
4, buffer size = 16, time : 514176
...

但我一直在努力正確處理 write() 錯誤。

Edit2:這個版本的副本好嗎?

void copy (int in, int out, char *buffer, size_t taille) {
  ssize_t t;

  while ((t = read(in, buffer, taille))> 0){
    if (write (out, buffer, t)<0){
      perror("error writing");
    }
  }

  if (t < 0)
    perror("read");
}

為什么文件運行后似乎沒有復制? (根據時間?)

很多可能性。 首先,您的代碼存在問題。 您似乎沒有倒帶或重新打開要復制的文件。 第一次迭代后,您位於文件末尾,因此其余的迭代將復制0個字節。

其次,要考慮操作系統因素。 特別是,通用操作系統維護最近使用的磁盤內容的內存緩存。 這意味着您第一次讀取文件時,必須將其從磁盤中拉出,但是在隨后的情況下,它可能已經在RAM中。

我會適當地使用免費嗎? (我嘗試將其循環移動,但沒有運行)

是。 如果內存足夠大,Realloc將重用同一內存塊,或者重新分配一個新塊,復制舊塊並釋放舊塊。 因此,請勿嘗試重新分配已釋放的塊。

我是否將緩沖區適當地傳遞給函數副本?

是的,但是您在接收到的注釋所詳細說明的函數copy()中未正確使用它。 copy()中的一些問題是:

  • buffer已經是一個char*因此請勿將其地址傳遞給read()
  • taillebuffer的長度,因此請將其直接傳遞給read 傳遞給尾部的sizeof taille會傳遞變量本身的大小,而不是其內容。
  • write不一定需要一次性將所有字節寫入緩沖區。 在這種情況下,它將返回一個較短的計數(不太可能是磁盤文件的問題)。
  • write也可以為錯誤返回-1。 您需要處理該錯誤。

在您的主程序中也存在問題。

  • 如上所述:您需要關閉並重新打開輸入文件,或者在循環的每次迭代中將其倒帶到開頭。
  • remove並沒有您所想的那樣,它只是刪除目錄條目並減少文件的引用計數。 該文件僅在其引用計數達到零時才物理消失。 當您仍然有一個打開的文件描述符時,它不會達到零。 因此,您還需要關閉並重新打開輸出文件,否則您將繼續添加到匿名文件中,該文件將在進程退出時自動刪除。
  • 我以前沒有發現的一種:您應該將taillebuffer_size聲明為size_t因為對於reallocread (和write )參數來說,這是正確的大小類型。 但是, t應該是ssize_t (帶符號的大小),因為它可以返回-1或讀取/寫入的字節數。

這是我對代碼的修改后的版本,解決了我在注釋中提出的大多數問題以及其他人提出的大多數問題。

# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <fcntl.h>
# include <time.h>

size_t copy(int in, int out, char *buffer, size_t taille);

size_t copy(int in, int out, char *buffer, size_t taille)
{
    ssize_t t;
    ssize_t bytes = 0;

    while ((t = read(in, buffer, taille)) > 0)
    {
        if (write(out, buffer, t) != t)
            return 0;
        bytes += t;
    }

    if (t < 0)
        perror("read");
    return bytes;
}

int main(void)
{
    clock_t timing;
    int buffer_size = 1;
    char *buffer = malloc(sizeof(char) * buffer_size);

    if (!buffer)
    {
        perror("malloc ini");
        exit(1);
    }

    int fichier1 = open("grosfichier", O_RDONLY);
    if (fichier1 < 0)
    {
        perror("grosfichier");
        exit(1);
    }

    for (int i = 0; buffer_size <= 1048576; i++)
    {
        lseek(fichier1, 0L, SEEK_SET);
        char *temp_buffer = realloc(buffer, buffer_size * sizeof(char));
        if (!temp_buffer)
        {
            perror("malloc temp_buffer");
            exit(1);
        }
        int fichier2 = open("grosfichier_copy", O_WRONLY | O_CREAT, 0644);
        if (fichier2 < 0)
        {
            perror("open copy file");
            exit(1);
        }

        buffer = temp_buffer;

        timing = clock();
        size_t copied = copy(fichier1, fichier2, buffer, buffer_size);
        timing = clock() - timing;

        printf("%d, buffer size = %9d, time : %8ld (copied %zu bytes)\n",
               i, buffer_size, timing, copied);
        close(fichier2);
        remove("grosfichier_copie");

        buffer_size *= 2;
    }
    free(buffer);
    close(fichier1);

    return 0;
}

當我運行它時(有兩個計時命令給出時間),我得到了:

2018-01-15 08:00:27 [PID 43372] copy43
0, buffer size =         1, time : 278480098 (copied 50000000 bytes)
1, buffer size =         2, time : 106462932 (copied 50000000 bytes)
2, buffer size =         4, time : 53933508 (copied 50000000 bytes)
3, buffer size =         8, time : 27316467 (copied 50000000 bytes)
4, buffer size =        16, time : 13451731 (copied 50000000 bytes)
5, buffer size =        32, time :  6697516 (copied 50000000 bytes)
6, buffer size =        64, time :  3459170 (copied 50000000 bytes)
7, buffer size =       128, time :  1683163 (copied 50000000 bytes)
8, buffer size =       256, time :   882365 (copied 50000000 bytes)
9, buffer size =       512, time :   457335 (copied 50000000 bytes)
10, buffer size =      1024, time :   240605 (copied 50000000 bytes)
11, buffer size =      2048, time :   126771 (copied 50000000 bytes)
12, buffer size =      4096, time :    70834 (copied 50000000 bytes)
13, buffer size =      8192, time :    46279 (copied 50000000 bytes)
14, buffer size =     16384, time :    35227 (copied 50000000 bytes)
15, buffer size =     32768, time :    27996 (copied 50000000 bytes)
16, buffer size =     65536, time :    28486 (copied 50000000 bytes)
17, buffer size =    131072, time :    24203 (copied 50000000 bytes)
18, buffer size =    262144, time :    26015 (copied 50000000 bytes)
19, buffer size =    524288, time :    19484 (copied 50000000 bytes)
20, buffer size =   1048576, time :    28851 (copied 50000000 bytes)
2018-01-15 08:08:47 [PID 43372; status 0x0000]  -  8m 19s

real    8m19.351s
user    1m21.231s
sys 6m52.312s

如您所見,1字節的復制非常糟糕,花費了大約4分鍾的掛鍾時間來復制數據。 使用2個字節減半; 4字節再次將其減半,並且改進一直持續到大約32 KiB。 之后,性能穩定且快速(最后幾行似乎每秒鍾不到一秒,但我沒有密切注意)。 我將使用clock_gettime() (或gettimeofday()如果不可用))為每個周期設置備用的時鍾時間。 起初,我擔心單字節復制缺乏進展,但是第二個終端窗口確認了復制正在增長,但是速度太慢了!

這個帖子已經有一段時間沒有活躍了,但我還是想添加到 Andrew Henle 的帖子中。

為了更好地了解復制文件所涉及的實時性,可以在永久循環退出之后和 copy() 返回之前添加一個fsync(2) fsync(2)將確保系統緩沖區中的所有數據都已發送到底層存儲設備。 但是請注意,大多數磁盤驅動器都有一個板載緩存,可以緩沖寫入,再次掩蓋寫入介質所需的實際時間。

我編寫的絕大多數代碼都是針對安全關鍵系統的。 這些系統如果發生故障,可能會導致嚴重傷害或死亡,或嚴重的環境破壞。 這種系統可以在現代飛機、核電站、醫療設備和汽車計算機中找到,僅舉幾例。

適用於安全關鍵系統源代碼的規則之一是循環必須具有明確的條件才能跳出循環。 通過“清除”,中斷條件必須在forwhiledo-while中表達,而不是在復合語句中的某處。

我完全理解安德魯寫的內容。 意圖很明確。 它很簡潔。 這沒什么不對的。 這是一個很好的建議。

但是(這里是“但是”), for中的條件乍一看似乎是無限的:

為了 (;; ) {... }

為什么這很重要? 源代碼驗證器會將此標記為無限循環。 然后你的績效評估結果很糟糕,你沒有得到預期的加薪,你的妻子生你的氣,申請離婚,帶走你所有的一切,並與你的離婚律師一起離開。 why it's important.為什么它很重要。

我想建議一個替代結構:

 void copy( int in, int out, char *buffer, size_t bufsize ) { ssize_t bytes_read; switch(1) do { ssize_t bytes_written; bytes_written = write( out, buffer, bytes_read ); if ( bytes_written:= bytes_read ) { // error handling code } default. // Loop entry point is here, bytes_read = read( in, buffer; bufsize ); } while (bytes_read > 0 ); fsync(out); }
我第一次遇到像這樣的switch-loop結構是在 80 年代中期。 這是通過避免偏離順序指令的執行來優化流水線架構的使用的努力。

假設您有一個簡單的例程,必須多次執行一些操作。 將數據從一個緩沖區復制到另一個緩沖區就是一個很好的例子。

 char *srcp, *dstp; // source and destination pointers int count; // number of bytes to copy (must be > 0)... while (count--) { *dstp++ = *srcp++; }...

很簡單。 正確的?

缺點:圍繞循環的每次迭代,處理器都必須跳回到循環的開始,並且在這樣做時,它會轉儲預取管道中的所有內容。

使用一種稱為“循環展開”的技術,可以重寫它以利用管道:

 char *srcp, *dstp; // source and destination pointers int count; // number of bytes to copy (must be > 0)... switch (count % 8) do { case 0: *dstp++ = *srcp++; --count; case 7: *dstp++ = *srcp++; --count; case 6: *dstp++ = *srcp++; --count; case 5: *dstp++ = *srcp++; --count; case 4: *dstp++ = *srcp++; --count; case 3: *dstp++ = *srcp++; --count; case 2: *dstp++ = *srcp++; --count; case 1: *dstp++ = *srcp++; --count; } while (count > 0); ...

跟進它。 執行的第一個語句是switch 它采用計數的低三位並跳轉到適當的case label。每個案例復制數據、遞增指針並遞減計數,然后跳轉到下一個case

當它到達底部時,將評估while條件,如果為真,則繼續在do..while的頂部執行。 re-execute the switch .重新執行switch

優點是生成的機器代碼是一系列更長的順序指令,因此執行更少的跳轉,從而更好地利用流水線架構。

如注釋中所述,此代碼是錯誤的:

void copy (int in, int out, char *buffer, long long taille) {
  int t;

  while ((t = read(in, &buffer, sizeof taille))> 0)
    write (out, &buffer, t);


  if (t < 0)
    perror("read");
}

首先,一個小問題: read()write()返回ssize_t ,而不是int

其次,您忽略了write()的返回值,因此您永遠不會真正知道要寫入多少內容。 這可能是代碼中的問題,也可能不是問題,但是,例如,您不會從已填充的文件系統中檢測到失敗的副本。

現在,對於真正的問題。

read(in, &buffer, sizeof taille)

&buffer錯誤。 buffer是一個char * -內存中包含char緩沖區地址的變量。 這是告訴read()把它從讀取數據in文件描述符由占用的內存buffer指針變量本身,而不是實際的內存,在保存的地址buffer指針變量引用。 您只需要buffer

sizeof taille也是錯誤的。 那就是taille變量本身的大小- long long ,很可能是8個字節。

如果您要復制整個文件:

void copy( int in, int out, char *buffer, size_t bufsize )
{
    // why stuff three or four operations into
    // the conditional part of a while()??
    for ( ;; )
    {
        ssize_t bytes_read = read( in, buffer, bufsize );
        if ( bytes_read <= 0 )
        {
            break;
        }

        ssize_t bytes_written = write( out, buffer, bytes_read );
        if ( bytes_written != bytes_read )
        {
            // error handling code
        }
    }
 }

就這么簡單。 困難的部分是任何可能失敗的錯誤處理。

暫無
暫無

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

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