2011-10-07 76 views
2

在开始,这看起来很简单的字节回数,但是这是一个面试问题,唯一的办法如下:拷贝一个文件到另一个复制

我写了一个简单的代码来按字节复制从一个文件到另一个并返回在while(!feof)循环中递增的计数。但是,我的采访者说,执行这个循环来复制1GB文件需要1个小时的时间,导致它的复制Bytewise,但是这在现实生活中不会发生。有人能告诉我如何在计算机上实际复制大文件,底层算法是什么?另外,请记住我需要返回复制的字节数。

+0

最好至少复制字大小的块。你可以通过做大块来减少在循环中花费的时间,但实际上磁盘无论如何都是瓶颈。但是浪费cpu没有意义,所以要分块。 – VoidStar

回答

5

他可能只是错误的。

除非您以类似汇编语言的方式编写代码,否则一次读取/写入一个字符几乎肯定对整体速度的影响相当小。原因很简单:几乎任何比汇编语言更高层次的东西(至少有一些)会为你做面向角色的I/O的缓冲。

只是为了例如,考虑C代码是这样的:

#include <stdio.h> 

int main(int argc, char **argv) { 
    FILE *infile = fopen(argv[1], "rb"); 
    FILE *outfile = fopen(argv[2], "wb"); 

    unsigned long count = 0; 
    int ch; 

    while (EOF != (ch=getc(infile))) { 
     ++count; 
     putc(ch, outfile); 
    } 
    printf("%lu bytes copied\n", count); 
    return 0; 
} 

现实情况是,这将可能运行速度比典型的文件副本有点慢,但只有一小。原因很简单:至少假设C,getcputc(以及大部分标准I/O的其余部分)的中途体面实现将在幕后为你缓冲。实际上,getc和putc通常会以宏的形式实现,因此大部分代码也将以内联方式扩展。虽然它从一个编译器的不同而不同,典型的代码会是这个样子:

#define getc(f) f->__pos<f->__len?f->__buf[f->__pos++]:__filbuf() 
#define putc(ch, f) f-__>pos<f->__len?f->__buf[f->__pos++]=ch:__flshbuf(f, ch) 

这将伴随着代码是这样的:

#define BUF_SIZE 4096 

typedef struct { 
    char __buf[BUF_SIZE]; 
    size_t __pos; 
    size_t __len=BUF_SIZE; 
    int __file_number; 
} FILE; 

现在,它肯定是真的,你可以在这个提高:

  1. 既然你知道你要使用整个文件顺序,您可以使用更大的缓冲区,以减少往返的次数/从内核模式。
  2. 由于您知道您将按照写入的内容准确写入数据,因此可以读入缓冲区,然后使用完全相同的缓冲区进行写入,而不是将数据从一个缓冲区复制到另一个缓冲区。
  3. 既然您知道您正在复制文件,并且很可能大部分数据不会很快再次使用,您可以告诉操作系统它不应该被缓存。
  4. 如果源和目标位于物理上分离的磁盘上,则异步I/O可以通过允许读取/写入同时发生而提供帮助。

但是,请注意,这些可能会增加开发时间,甚至在最好情况下,您也不应计划看到面试官提出的任何速度差异。即使是10倍的提高也不太可能,更不用说面试官建议的1000倍了。

+0

它在C中非常容易,不会结束任何缓冲 - 只需使用'open' /'read' /'write'而不是'fopen' /'fread' /'fwrite'。在这种情况下,它会慢得多,因为每个字节需要2个内核调用。说实话,除了* AIO之外,所有这些都不是很多代码行。 – derobert

+0

@derobert:是的,如果你想尽量避免缓冲,那(通常)很容易做到。尽管如此,这不太可能发生。 –

2

算法本质上是相同的,只是使用大于一个字节的缓冲区。基本上,你做这样的事情:

  1. 从文件中读取可达1MB(读取调用告诉你究竟有多少读)
  2. 该金额添加到您的总到目前为止复制
  3. 写的那个量数据到其他文件
  4. 重复,直到读告诉你EOF

这就是简单的方法。有时候更快的方法必须更加困难(例如,使用POSIX async io)。例如,下面是使用POSIX AIO(来自我编写的程序)的外观。谨防错误的,我写这个只是为了好玩:

int copy_file(int input, int output) { 
    struct stat statbuf; 
    off_t input_size, input_pos, output_pos, last_block; 
    struct aiocb read_cb, write_cb; 
    const struct aiocb *suspend_list[2]; 
    char *bufs[NR_BUFFERS]; 
    ssize_t bufs_size[NR_BUFFERS]; 
    int i, ex_code, reading, writing, read_depleted; 
    uint64_t read_buf, write_buf; 

    if (-1 == fstat(input, &statbuf)) { 
     perror("fstat(input)"); 
     return EXIT_FAILURE; 
    } 
    input_size = statbuf.st_size; 

    ex_code = 0; 
    for (i = 0; i < NR_BUFFERS; ++i) 
     bufs[i] = 0; 
    for (i = 0; i < NR_BUFFERS; ++i) { 
     if (!(bufs[i] = malloc(BUFFER_SIZE))) { 
      perror("malloc"); 
      ex_code = EXIT_FAILURE; 
      break; 
     } 
    } 


    memset(&read_cb, 0, sizeof(read_cb)); 
    memset(&write_cb, 0, sizeof(write_cb)); 

    output_pos = input_pos = last_block = 0; 
    read_buf = write_buf = 0; 
    read_depleted = reading = writing = 0; 

    while (!ex_code && (!read_depleted || write_buf != read_buf)) { 
     if (!read_depleted && !reading && ((read_buf - write_buf) < NR_BUFFERS)) { 
      read_cb.aio_fildes = input; 
      read_cb.aio_offset = input_pos; 
      read_cb.aio_buf = bufs[read_buf % NR_BUFFERS]; 
      read_cb.aio_nbytes = BUFFER_SIZE; 
      read_cb.aio_sigevent.sigev_notify = SIGEV_NONE; 

      if (-1 == aio_read(&read_cb)) { 
       perror("aio_read"); 
       ex_code = EXIT_FAILURE; 
       break; 
      } 
      suspend_list[0] = &read_cb; 
      reading = 1; 
     } 

     if (!writing && (read_buf > write_buf)) { 
      write_cb.aio_fildes = output; 
      write_cb.aio_offset = output_pos; 
      write_cb.aio_buf = bufs[write_buf % NR_BUFFERS]; 
      write_cb.aio_nbytes = bufs_size[write_buf % NR_BUFFERS]; 
      write_cb.aio_sigevent.sigev_notify = SIGEV_NONE; 

      if (-1 == aio_write(&write_cb)) { 
       perror("aio_write"); 
       ex_code = EXIT_FAILURE; 
       break; 
      } 
      suspend_list[1] = &write_cb; 
      writing = 1; 
     } 

     suspend_list[0] = reading ? &read_cb : NULL; 
     suspend_list[1] = writing ? &write_cb : NULL; 

     if (-1 == aio_suspend(suspend_list, 2, NULL)) { 
      if (EINTR != errno && EAGAIN != errno) { 
       perror("aio_suspend"); 
       ex_code = EXIT_FAILURE; 
       break; 
      } 
     } else { 
      int err; 
      if (reading && EINPROGRESS != (err = aio_error(&read_cb))) { 
       if (err) { 
        fprintf(stderr, "read error: %s\n", strerror(err)); 
        ex_code = EXIT_FAILURE; 
        break; 
       } 
       bufs_size[read_buf%NR_BUFFERS] = aio_return(&read_cb); 
       input_pos += bufs_size[read_buf%NR_BUFFERS]; 
       if (0 == bufs_size[read_buf % NR_BUFFERS]) 
        read_depleted = 1; 
       ++read_buf; 
       reading = 0; 
      } 
      if (writing && EINPROGRESS != (err = aio_error(&write_cb))) { 
       if (err) { 
        fprintf(stderr, "write error: %s\n", strerror(err)); 
        ex_code = EXIT_FAILURE; 
        break; 
       } 
       if (bufs_size[write_buf%NR_BUFFERS] != aio_return(&write_cb)) { 
        fprintf(stderr, "partial write, fuck, can't handle\n"); 
        ex_code = EXIT_FAILURE; 
        break; 
       } 
       output_pos += bufs_size[write_buf%NR_BUFFERS]; 
       ++write_buf; 
       writing = 0; 
      } 

      fprintf(stderr, 
       "\xd%5.1f%% (%llu of %llu; r: %i, w: %i, r-w: %llu)", 
       100*((double)output_pos)/input_size, 
       output_pos, input_size, reading, writing, 
       (read_buf - write_buf) 
      ); 
     } 

    } 
    fprintf(stderr, "\n"); 



    for (i = 0; i < NR_BUFFERS; ++i) 
     if (bufs[i]) 
      free(bufs[i]); 

    return ex_code; 
} 
-2

副本按字节。相反,你保留一个合理大小的内存缓冲区(参见dd中的“bs”选项),并以该缓冲区的粒度进行读写。我虽然这将是显而易见的

+0

为什么downvote?我的答案实际上是正确的,与接受的答案几乎相同(减去代码)。 – chetan

相关问题