在开始,这看起来很简单的字节回数,但是这是一个面试问题,唯一的办法如下:拷贝一个文件到另一个复制
我写了一个简单的代码来按字节复制从一个文件到另一个并返回在while(!feof)循环中递增的计数。但是,我的采访者说,执行这个循环来复制1GB文件需要1个小时的时间,导致它的复制Bytewise,但是这在现实生活中不会发生。有人能告诉我如何在计算机上实际复制大文件,底层算法是什么?另外,请记住我需要返回复制的字节数。
在开始,这看起来很简单的字节回数,但是这是一个面试问题,唯一的办法如下:拷贝一个文件到另一个复制
我写了一个简单的代码来按字节复制从一个文件到另一个并返回在while(!feof)循环中递增的计数。但是,我的采访者说,执行这个循环来复制1GB文件需要1个小时的时间,导致它的复制Bytewise,但是这在现实生活中不会发生。有人能告诉我如何在计算机上实际复制大文件,底层算法是什么?另外,请记住我需要返回复制的字节数。
他可能只是错误的。
除非您以类似汇编语言的方式编写代码,否则一次读取/写入一个字符几乎肯定对整体速度的影响相当小。原因很简单:几乎任何比汇编语言更高层次的东西(至少有一些)会为你做面向角色的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,getc
和putc
(以及大部分标准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;
现在,它肯定是真的,你可以在这个提高:
但是,请注意,这些可能会增加开发时间,甚至在最好情况下,您也不应计划看到面试官提出的任何速度差异。即使是10倍的提高也不太可能,更不用说面试官建议的1000倍了。
它在C中非常容易,不会结束任何缓冲 - 只需使用'open' /'read' /'write'而不是'fopen' /'fread' /'fwrite'。在这种情况下,它会慢得多,因为每个字节需要2个内核调用。说实话,除了* AIO之外,所有这些都不是很多代码行。 – derobert
@derobert:是的,如果你想尽量避免缓冲,那(通常)很容易做到。尽管如此,这不太可能发生。 –
算法本质上是相同的,只是使用大于一个字节的缓冲区。基本上,你做这样的事情:
这就是简单的方法。有时候更快的方法必须更加困难(例如,使用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;
}
你不副本按字节。相反,你保留一个合理大小的内存缓冲区(参见dd中的“bs”选项),并以该缓冲区的粒度进行读写。我虽然这将是显而易见的
为什么downvote?我的答案实际上是正确的,与接受的答案几乎相同(减去代码)。 – chetan
最好至少复制字大小的块。你可以通过做大块来减少在循环中花费的时间,但实际上磁盘无论如何都是瓶颈。但是浪费cpu没有意义,所以要分块。 – VoidStar